Решите рюкзак с множественным выбором (MCKP) с помощью Dynami c Программирование? - PullRequest
2 голосов
/ 24 марта 2020

Пример данных

Для этого вопроса предположим следующие пункты:

  • Элементы: яблоко, банан, морковь, стейк, лук
  • Значения: 2 , 2, 4, 5, 3
  • Вес: 3, 1, 3, 4, 2
  • Макс. Вес: 7

Объектив:

MCKP относится к типу Задача о ранце с дополнительным ограничением, что «[T] предметы подразделяются на k классов ... и ровно один предмет должен быть взято из каждого класса"

Я написал код для решения проблемы 0/1 KS с динамическим программированием c с использованием рекурсивных вызовов и запоминания. У меня вопрос, возможно ли добавить это ограничение к моему текущему решению? Скажем, мои классы: Фрукты, Овощи, Мясо (из примера), я должен был бы включить 1 каждого типа. Классы также могут быть типа 1, 2, 3.

Кроме того, я думаю, что это можно решить с помощью линейного программирования и решателя, но, если возможно, я хотел бы понять ответ здесь.

Текущий код:

<code><?php
$value = array(2, 2, 4, 5, 3);
$weight = array(3, 1, 3, 4, 2);

$maxWeight = 7;
$maxItems = 5;

$seen = array(array()); //2D array for memoization
$picked = array();

//Put a dummy zero at the front to make things easier later.
array_unshift($value, 0);
array_unshift($weight, 0);

//Call our Knapsack Solver and return the sum value of optimal set
$KSResult = KSTest($maxItems, $maxWeight, $value, $weight);
$maxValue = $KSResult; //copy the result so we can recreate the table

//Recreate the decision table from our memo array to determine what items were picked
//Here I am building the table backwards because I know the optimal value will be at the end
for($i=$maxItems; $i > 0; $i--) {
        for($j=$maxWeight; $j > 0; $j--) {
            if($seen[$i][$j] != $seen[$i-1][$j]
                && $maxValue == $seen[$i][$j]) {
                array_push($picked, $i);
                $maxValue -= $value[$i];
                break;
            }
        }
}

//Print out picked items and max value
print("<pre>".print_r($picked,true)."
"); echo $ KSResult; // Рекурсивная формула для решения проблемы KS // $ n = количество проверяемых элементов // $ c = общая емкость функция мешка KSTest ($ n, $ c, & $ value, & $ weight) {global $ seen; if (isset ($ seen [$ n] [$ c])) {// Мы видели эту подзадачу перед возвратом $ увидено [$ n] [$ c];} if ($ n === 0 || $ c === 0) {// Больше нет элементов для проверки или больше нет емкости $ result = 0; } elseif ($ weight [$ n]> $ c) {// Этот элемент слишком тяжелый, проверьте следующий элемент без этого $ result = KSTest ($ n-1, $ c, $ value, $ weight );} else {// Возьмите более высокий результат сохранения или не сохранения элемента $ tempVal1 = KSTest ($ n-1, $ c, $ value, $ weight); $ tempVal2 = $ value [$ n] + KSTest ($ n-1, $ c - $ weight [$ n], $ value, $ weight); if ($ tempVal2> = $ tempVal1) {$ result = $ tempVal2; // некоторые условия Dtions может go здесь? в противном случае используйте max ()} else {$ result = $ tempVal1; }} // запоминаем результаты и возвращаем $ seen [$ n] [$ c] = $ result; вернуть $ результат; }?>

Что я пробовал:

  1. Моей первой мыслью было добавить массив класса (k), отсортировать элементы по классу (k) и когда мы выбираем элемент, который совпадает со следующим, проверьте, лучше ли сохранить текущий элемент или элемент без следующего элемента. Выглядело многообещающе, но развалилось после проверки нескольких предметов. Примерно так: $ tempVal3 = $ value [$ n] + KSTest ($ n-2, $ c - $ weight [$ n]); max ($ tempVal2, $ tempVal3);
  2. Другая мысль состоит в том, что при вызове функции я мог бы вызвать al oop для каждого типа класса и решить KS только с 1 элементом за один раз этого типа + остальные значения. Это определенно заставит задуматься о некоторых предположениях, потому что результаты множества 1 могут все еще предполагать, например, кратные множеству множества 2.

Это выглядит как уравнение (Если вы хорошо читать все эти символы?) :) и реализация C ++? но я не могу понять, где происходит ограничение класса?

Ответы [ 3 ]

1 голос
/ 25 марта 2020

Реализация c ++ выглядит нормально.

Ваши значения и веса, которые являются одномерным массивом в вашей текущей реализации PHP, станут 2-мерными.

Так, например,

values[i][j] будет значением j й предмет в классе i. Аналогично в случае weights[i][j]. Вы будете брать только один элемент для каждого класса i и продвигаться вперед, максимизируя условие.

Реализация c ++ также выполняет оптимизацию в memo. Он содержит только 2 массива размера, соответствующих условию max_weight, которые являются текущими и предыдущими состояниями. Это потому, что вам нужны только эти 2 состояния одновременно для вычисления текущего состояния.

Ответы на ваши сомнения:

1)

Моей первой мыслью было добавить массив class (k), отсортировать элементы по классу (k), и, когда мы решим выбрать элемент, который совпадает со следующим, проверить, лучше ли сохранить текущий элемент или элемент без следующего элемента. Выглядело многообещающе, но развалилось после проверки нескольких предметов. Примерно так: $ tempVal3 = $ value [$ n] + KSTest ($ n-2, $ c - $ weight [$ n]); max ($ tempVal2, $ tempVal3);

Это не сработает, потому что в классе k + 1 может быть какой-то элемент, в котором вы берете оптимальное значение и для соблюдения ограничения вам нужно принять неоптимальный значение для класса к. Таким образом, сортировка и выбор лучших не будет работать, когда ограничение достигнуто. Если ограничение не выполнено, вы всегда можете выбрать лучшее значение с лучшим весом.

2)

Другая мысль состоит в том, что при вызове функции я мог бы вызвать al oop для каждого типа класса и решить KS только с 1 элементом за один раз этого типа + остальные значения.

Да, вы находитесь на правильном пути здесь. Вы будете предполагать, что вы уже решили для первых k классов. Теперь вы попробуете расширить, используя значения класса k + 1 с учетом ограничения по весу.

3)

... но я не могу понять, где находится ограничение класса что происходит?

for (int i = 1; i < weight.size(); ++i) {
    fill(current.begin(), current.end(), -1);
    for (int j = 0; j < weight[i].size(); ++j) {
        for (int k = weight[i][j]; k <= max_weight; ++k) {
            if (last[k - weight[i][j]] > 0)
                current[k] = max(current[k],
                                 last[k - weight[i][j]] + value[i][j]);
        }
    }
    swap(current, last);
}

В приведенном выше фрагменте кода c ++ первый l oop выполняет итерации по классу, второй l oop выполняет итерации по значениям класса, а третий l oop расширяет текущее состояние current с использованием предыдущего состояния last и только 1 элемент j с классом i одновременно. Поскольку вы используете только предыдущее состояние last и 1 элемент текущего класса для расширения и максимизации, вы соблюдаете ограничение.

Сложность времени:

O (total_items x max_weight) , что эквивалентно O (класс *) 1060 * x max_number_of_items_in_a_class x max_weight)

0 голосов
/ 27 марта 2020

Это мое PHP решение. Я пытался комментировать код так, чтобы за ним было легко следовать.

Обновление: Я обновил код, потому что старый скрипт давал ненадежные результаты. Это чище и было тщательно проверено. Ключевым выводом является то, что я использую два массива памяток, один на уровне группы, чтобы ускорить выполнение, и один на уровне элемента, чтобы восстановить результаты. Я обнаружил, что любые попытки отследить, какие предметы выбираются, поскольку вы go ненадежны и намного менее эффективны. Кроме того, isset () вместо if ($ var) необходим для проверки массива memo, поскольку предыдущие результаты могли быть 0;)

<?php
/**
* Multiple Choice Knapsack Solver
*
* @author Michael Cruz
* @version 1.0 - 03/27/2020
**/
class KS_Solve {
    public $KS_Items;
    public $maxValue;
    public $maxWeight;
    public $maxItems;
    public $finalValue;
    public $finalWeight;
    public $finalItems;
    public $finalGroups;
    public $memo1 = array(); //Group memo
    public $memo2 = array(); //Item memo for results rebuild

    public function __construct() {
        //some default variables as an example.

        //KS_Items = array(Value, Weight, Group, Item #)
        $this->KS_Items = array(
            array(2, 3, 1, 1),
            array(2, 1, 1, 2),
            array(4, 3, 2, 3),
            array(5, 4, 2, 4),
            array(3, 2, 3, 5)
        );

        $this->maxWeight = 7;
        $this->maxItems = 5;
        $this->KS_Wrapper();
    }

    public function KS_Wrapper() {
        $start_time = microtime(true); 

        //Put a dummy zero at the front to make things easier later.
        array_unshift($this->KS_Items, array(0, 0, 0, 0));

        //Call our Knapsack Solver
        $this->maxValue = $this->KS_Solver($this->maxItems, $this->maxWeight);

        //Recreate the decision table from our memo array to determine what items were picked
        //ksort($this->memo2); //for debug
        for($i=$this->maxItems; $i > 0; $i--) {
            //ksort($this->memo2[$i]); //for debug
            for($j=$this->maxWeight; $j > 0; $j--) {
                if($this->maxValue == 0) {
                    break 2;
                }
                if($this->memo2[$i][$j] == $this->maxValue
                    && $j == $this->maxWeight) {
                    $this->maxValue -= $this->KS_Items[$i][0];
                    $this->maxWeight -= $this->KS_Items[$i][1];
                    $this->finalValue += $this->KS_Items[$i][0];
                    $this->finalWeight += $this->KS_Items[$i][1];
                    $this->finalItems .= " " . $this->KS_Items[$i][3];
                    $this->finalGroups .= " " . $this->KS_Items[$i][2];
                    break;
                }
            }
        }

        //Print out the picked items and value. (IMPLEMENT Proper View or Return!)
        echo "<pre>";
        echo "RESULTS: <br>";
        echo "Value: " . $this->finalValue . "<br>";
        echo "Weight: " . $this->finalWeight . "<br>";
        echo "Item's in KS:" . $this->finalItems . "<br>";
        echo "Selected Groups:" . $this->finalGroups . "<br><br>";
        $end_time = microtime(true); 
        $execution_time = ($end_time - $start_time); 
        echo "Results took " . sprintf('%f', $execution_time) . " seconds to execute<br>";

    }

    /**
    *  Recursive function to solve the MCKS Problem
    *  $n = number of items to check
    *  $c = total capacity of KS   
    **/
    public function KS_Solver($n, $c) {
        $group = $this->KS_Items[$n][2];
        $groupItems = array();
        $count = 0;
        $result = 0;
        $bestVal = 0;

        if(isset($this->memo1[$group][$c])) {
            $result = $this->memo1[$group][$c];
        }
        else {
            //Sort out the items for this group
            foreach($this->KS_Items as $item) {
                if($item[2] == $group) {
                    $groupItems[] = $item;
                    $count++;
                }
            }
            //$k adjusts the index for item memoization
            $k = $count - 1;

            //Find the results of each item + items of other groups
            foreach($groupItems as $item) {
                if($item[1] > $c) {
                    //too heavy
                    $result = 0;
                }
                elseif($item[1] >= $c && $group != 1) {
                    //too heavy for next group
                    $result = 0;
                }
                elseif($group == 1) {
                    //Just take the highest value
                    $result = $item[0];
                }
                else {
                    //check this item with following groups
                    $result = $item[0] + $this->KS_Solver($n - $count, $c - $item[1]);
                }

                if($result == $item[0] && $group != 1) {
                    //No solution with the following sets, so don't use this item.
                    $result = 0;
                }

                if($result > $bestVal) {
                    //Best item so far
                    $bestVal = $result;
                }
                //memo the results
                $this->memo2[$n-$k][$c] = $result;
                $k--;
            }
            $result = $bestVal;
        }

        //memo and return
        $this->memo1[$group][$c] = $result;
        return $result;
    }
}
new KS_Solve();
?>
0 голосов
/ 24 марта 2020

Так что я не php программист, но я постараюсь написать псевдокод с хорошим объяснением.

В исходной задаче каждая ячейка i, j означала: «Значение наполнения рюкзака предметами От 1 до i до достижения емкости j ", решение в предоставленной вами ссылке определяет каждую ячейку как" Значение заполнения рюкзака предметами из ведер от 1 до i до достижения емкости j ". Обратите внимание, что в этом варианте нет такого, как не брать элемент из класса.

Так что на каждом шаге (каждый вызов для KSTest с $n, $c) нам нужно найти какой элемент выбрать из n-го класса так, чтобы вес этого элемента был меньше c , а его значение + KSTest(n - 1, c - w) самое большое.

Так что я думаю, что вы следует изменить только операторы else if и else на что-то вроде:

else {
    $result = 0
    for($i=0; $i < $number_of_items_in_nth_class; $i++) {
        if ($weight[$n][$i] > $c) {
            //This item is too heavy, check next item
            continue;
        }
        $result = max($result, KSTest($n-1, $c - $weight[$n][$i], $value, $weight));
    }
}

Теперь два заявления об отказе:

  1. Я не кодирую в php, поэтому этот код не будет работать:)

  2. Это не реализация, приведенная в ссылке, которую вы предоставили, TBH Я не понял, почему временная сложность их алгоритма настолько мала (и что C), но эта реализация должна работать, поскольку она соответствует определению заданной рекурсивной формулы.

Сложность по времени должна составлять O(max_weight * number_of_classes * size_of_largerst_class) .

...