Алгоритм упаковки в корзину нуждается в ускорении

Я ищу разумный способ подойти к варианту распространенной проблемы с упаковкой в ​​мусорное ведро. Учитывая количество сумок (как я их называю) определенной вместимости и список предметов, занимающих определенное пространство, задача состоит в том, чтобы определить, все ли предметы могут поместиться в сумки; и если да то как. Прямо сейчас у меня работает исчерпывающий DFS, но это занимает... целую вечность. Моя DFS является итеративной и требует копирования целых состояний на каждом этапе, что очень дорого. Вот мой код для конкретной проблемы с 4 сумками по 10 вместимостью (действительно важные части этого кода — это просто метод pack() и класс State, если вы не хотите смотреть на все это):

import java.util.ArrayList;
import java.util.Stack;

public class BagProblem {
    int numBags;
    int bagCapacity;
    ArrayList<Item> items = new ArrayList<Item>();

    public static void main(String[] args) {
        BagProblem bp = new BagProblem(4, 10);
        bp.pack();
    }

    public BagProblem(int numBags, int bagCapacity) {
        this.numBags = numBags;
        this.bagCapacity = bagCapacity;
        items = new ArrayList<Item>();
        items.add(new Item("item0", 6));
        items.add(new Item("item1", 6));
        items.add(new Item("item2", 6));
        items.add(new Item("item5", 3));
        items.add(new Item("item6", 3));
        items.add(new Item("item7", 3));
        items.add(new Item("item8", 2));
        items.add(new Item("item9", 2));
        items.add(new Item("item10", 2));
        items.add(new Item("item11", 2));
        items.add(new Item("item12", 2));
        items.add(new Item("item13", 2));
        items.add(new Item("item14", 1));
    }

    // find a valid way to pack and print the items in each Bag, or
    // print failure
    public void pack() {
        Stack <State> s = new Stack<State>();
        Bag[] currBags = new Bag[numBags];
        for (int i = 0; i < numBags; i++) {
            currBags[i] = new Bag(bagCapacity);
        }
        s.push(new State(currBags));
        while(!s.isEmpty()) {
            State currState = s.pop();
            for (Item i : items) {
                if (!currState.containsItem(i)) {
                    State newState = new State(currState.bags);
                    newState.numItems = currState.numItems;
                    if (newState.addItem(i)) {
                        s.push(newState);
                        if (newState.numItems == items.size()) {
                            System.out.println("success");
                            System.out.println(newState);
                            return;
                        }
                    }
                }
            }
        }
        System.out.println("failure");
    }

    private class State {
        Bag[] bags;
        int numItems;

        public State(Bag[] currBags) {
            bags = new Bag[numBags];
            for (int i = 0; i < numBags; i++) {
                bags[i] = new Bag(bagCapacity);
            }

            // figure out how to actually copy this
            for (int j = 0; j < numBags; j++) {
                Bag bagToCopy = currBags[j];
                for (Item item : bagToCopy.contents) {
                    Item newItem = new Item(item.name, item.size);
                    bags[j].size = bagToCopy.size;
                    bags[j].contents.add(newItem);
                }
            }
        }

        public boolean addItem(Item i) {
            for (Bag b : bags) {
                if (b.addItem(i)) {
                    numItems++;
                    return true;
                }
            }
            return false;
        }

        public boolean containsItem(Item i) {
            for (Bag b : bags) {
                for (Item item : b.contents) {
                    if (item.name.equals(i.name))
                        return true;
                }
            }
            return false;
        }

        public String toString() {
            String output = "";
            for (Bag b : bags) {
                for (Item j : b.contents) {
                    output += j.name + " ";
                }
                output += "\n";
            }
            return output;
        }

    }

    private class Bag {
        int capacity;
        int size;
        ArrayList<Item> contents;

        public Bag(int capacity) {
            this.capacity = capacity;
            this.size = 0;
            contents = new ArrayList<Item>();
        }

        public boolean addItem(Item i) {
            if(size + i.size > capacity)
                return false;
            contents.add(i);
            size += i.size;
            return true;
        }

        public String toString() {
            String output = "";
            for (Item i : contents) {
                output += i.name + " ";
            }
            return output + "\n";
        }

    }

    private class Item {
        String name;
        int size;

        public Item(String name, int size) {
            this.name = name;
            this.size = size;
        }

        public String toString() {
            return name;
        }

    }
}

Примерно через миллион лет это выдает правильный ответ (вы, вероятно, не захотите ждать так долго, если попытаетесь запустить это):

success
item14 item7 item6 item5 
item13 item12 item2 
item11 item10 item1 
item9 item8 item0

Каждая строка указывает на отдельный мешок. Как я могу ускорить это? Я знаю, что существуют эвристики при попытке разместить самый большой элемент первым и т. д., но что меня действительно интересует, так это получить базовую DFS (или, может быть, мне следует попробовать вернуться?), чтобы иметь меньше накладных расходов; Позже постараюсь покрасивее.

Любая помощь будет принята с благодарностью.


person Community    schedule 12.11.2013    source источник
comment
Привет и добро пожаловать в ТАК. Вы пробовали искать алгоритмы в Google и Википедии? Например, алгоритм первого подгонки может дать ответ очень быстро, но он не будет оптимальным.   -  person Hugo Tunius    schedule 13.11.2013
comment
Проблема в том, что в этой проблеме с упаковкой есть небольшой поворот. Мне нужно знать, может ли заданное количество мешков определенной вместимости содержать набор предметов определенного размера; первый подходящий и другие неоптимальные алгоритмы не смогут сказать. Я думаю, что DFS или возврат должны быть единственным способом сделать это, но это так медленно. Конечно, другое расположение предметов и сумок, которые пробуются в первую очередь, может помочь, но сейчас я беспокоюсь, что копирую больше информации на каждом этапе, чем мне действительно нужно, что убивает время выполнения.   -  person    schedule 13.11.2013
comment
В чем поворот? Вы описываете именно стандартную проблему упаковки в мусорное ведро. Эта задача является NP-сложной, поэтому любое решение, гарантирующее нахождение оптимального ответа, будет медленным на больших экземплярах. Но если первый подход или другая эвристика упаковывают предметы в k мешков, вы знаете, что оптимальный ответ не более k, и вы можете доказать, что он не может быть k-1 или меньше (например, если общий размер предметов превышает (k-1)*bag_size), и в этом случае вы будете знать, что ответ является оптимальным.   -  person j_random_hacker    schedule 13.11.2013
comment
Что ж, обычно я встречал проблему, формулируемую как «Сколько бинов это займет?», что дает вам некоторое пространство для маневра в отношении оптимальности, а не «Может ли такое количество бинов сработать?» Я думаю, это не совсем поворот, но я не думаю, что есть какой-то короткий путь с первым подходом или другими алгоритмами, которые надежно дадут правильный ответ.   -  person    schedule 13.11.2013


Ответы (1)


Я не использую Java, но ваша реализация кажется довольно неэффективной (как вы сами упомянули) из-за ее чрезмерной сложности. Сам алгоритм тоже очень странный, я не пытался его воспроизвести, а просто использовал очевидный алгоритм перебора O(bags^items), который пытается положить первый предмет в каждый мешок, для каждого из этих случаев пытается положить второй предмет в каждую сумку и т. д.

Вместо многократного повторения всего состояния в стеке вы можете поместить элемент в пакет, исследовать ветку дерева с этим изменением, а затем достать элемент из пакета. эм>.

Вот пример, который мгновенно завершается для вашего тестового примера на C#.

    static int[] itemSize;
    static int[] bagFreeSpace;
    static bool[,] doesBagContainItem; // in case this looks weird, [,] is a matrix, in java it would be [][]

    static bool pack(int item)
    { 
        // output the solution if we're done
        if (item == itemSize.Length)
        {
            for (int i = 0; i < bagFreeSpace.Length; i++)
            {
                Console.WriteLine("bag" + i);
                for (int j = 0; j < itemSize.Length; j++)
                    if (doesBagContainItem[i, j])
                        Console.Write("item" + j + "(" + itemSize[j] + ") ");
                Console.WriteLine();
            }
            return true;
        }

        // otherwise, keep traversing the state tree
        for (int i = 0; i < bagFreeSpace.Length; i++)
        {
            if (bagFreeSpace[i] >= itemSize[item])
            {
                doesBagContainItem[i,item] = true; // put item into bag
                bagFreeSpace[i] -= itemSize[item];
                if (pack(item + 1))                 // explore subtree
                    return true;
                bagFreeSpace[i] += itemSize[item];  // take item out of the bag
                doesBagContainItem[i,item] = false;
            }
        }

        return false;
    }

    static void Main(string[] args)
    {
        itemSize = new int[] { 6, 6, 6, 3, 3, 3, 2, 2, 2, 2, 2, 2, 1 };
        bagFreeSpace = new int[] { 10, 10, 10, 10 };
        doesBagContainItem = new bool[bagFreeSpace.Length, itemSize.Length];

        if (!pack(0))
            Console.WriteLine("No solution");
    }

Примечание: если вы хотите распараллелить выполнение, вам нужно дать каждому воркеру свою собственную копию состояния (или 1 копию на задание), но только в точке ветвления, они все равно могут действовать, как указано выше, без репликации состояния.

person svinja    schedule 13.11.2013