【问题标题】:Solve Multiple Choice Knapsack (MCKP) With Dynamic Programming?用动态规划解决多项选择背包(MCKP)?
【发布时间】:2020-07-05 01:04:05
【问题描述】:

示例数据

对于这个问题,我们假设以下项目:

  • 项目:苹果、香蕉、胡萝卜、牛排、洋葱
  • 值:2、2、4、5、3
  • 权重:3、1、3、4、2
  • 最大重量:7

目标:

MCKP 是 Knapsack Problem 的一种类型,附加的约束是“[T]he items are subdivided into k classes...必须从每个类"

我已经编写了代码来使用递归调用和记忆化来解决动态编程的 0/1 KS 问题。我的问题是是否可以将此约束添加到我当前的解决方案中?假设我的课程是水果、蔬菜、肉类(来自示例),我需要包括每种类型的 1 个。这些类也可以是类型 1、2、3。

另外,我认为这可以通过线性规划和求解器来解决,但如果可能的话,我想在这里了解答案。

当前代码:

<?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)."</pre>");
echo $KSResult;


//  Recursive formula to solve the KS Problem
//  $n = number of items to check
//  $c = total capacity of bag
function KSTest($n, $c, &$value, &$weight) {
    global $seen;

    if(isset($seen[$n][$c])) {
        //We've seen this subproblem before
        return $seen[$n][$c];
    }
    if($n === 0 || $c === 0){
        //No more items to check or no more capacity
        $result = 0;
    }
    elseif($weight[$n] > $c) {
        //This item is too heavy, check next item without this one
        $result = KSTest($n-1, $c, $value, $weight);
    }
    else {
        //Take the higher result of keeping or not keeping the item
        $tempVal1 = KSTest($n-1, $c, $value, $weight);
        $tempVal2 = $value[$n] + KSTest($n-1, $c-$weight[$n], $value, $weight);

        if($tempVal2 >= $tempVal1) {
            $result = $tempVal2;
            //some conditions could go here? otherwise use max()
        }
        else {
            $result = $tempVal1;
        }
    }
    //memo the results and return
    $seen[$n][$c] = $result;
    return $result;
}
?>

我的尝试:

  1. 我的第一个想法是添加一个类(k)数组,通过类(k)对项目进行排序,当我们选择与下一个项目相同的项目时,检查是否保持当前更好项目或没有下一个项目的项目。看起来很有希望,但在检查了几项后就崩溃了。像这样的东西: $tempVal3 = $value[$n] + KSTest($n-2, $c-$weight[$n]); 最大值($tempVal2,$tempVal3);
  2. 另一个想法是,在函数调用中,我可以为每个类类型调用一个循环,并在该类型的时间只使用 1 个项目 + 其余值来解决 KS。这肯定会做出一些假设,因为例如第 1 组的结果可能仍然是第 2 组的倍数。

This looks to be the equation(如果您擅长阅读所有这些符号?):) 和 C++ 实现?但我真的看不出类约束发生在哪里?

【问题讨论】:

    标签: php algorithm recursion dynamic-programming knapsack-problem


    【解决方案1】:

    c++ 实现看起来不错。

    当前 PHP 实现中的一维数组的值和权重将变为二维。

    例如,

    values[i][j] 将是 ji 中的第一项的值。在weights[i][j] 的情况下也是如此。每个班级你将只拿一个项目i,并在最大化条件的同时继续前进。

    c++ 实现还在备忘录中进行了优化。它只保留 2 个符合 max_weight 条件的大小数组,它们是当前和以前的状态。这是因为您一次只需要这两种状态来计算当前状态。

    解答您的疑惑:

    1)

    我的第一个想法是添加一个类 (k) 数组,通过 类(k),当我们选择一个与 下一项,检查是否最好保留当前项或 没有下一个项目的项目。看起来很有希望,但经过一段时间后就崩溃了 正在检查的几个项目。像这样的东西: $tempVal3 = $value[$n] + KSTest($n-2, $c-$weight[$n]);最大值($tempVal2,$tempVal3);

    这行不通,因为在 k+1 类中可能有一些项目在其中您取最佳值,并且为了遵守约束,您需要为 k 类取次优值。因此,当遇到约束时,排序和挑选最好的将不起作用。如果没有达到约束,您始终可以选择具有最佳权重的最佳值。

    2)

    另一个想法是,在函数调用中,我可以调用一个循环 每个班级类型并一次仅用 1 个项目解决 KS 类型 + 其余的值。

    是的,你在正确的轨道上。您将假设您已经解决了前 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++ sn-p中,第一个循环迭代class,第二个循环迭代class的值,第三个循环扩展当前状态current使用之前的状态last和只有一个项目@987654330 @ 与班级 i 一次。由于您仅使用先前状态 last 和当前类的 1 项来扩展和最大化,因此您遵循约束。

    时间复杂度:

    O(total_items x max_weight) 相当于 O(class x max_number_of_items_in_a_class x最大重量)

    【讨论】:

      【解决方案2】:

      所以我不是 php 程序员,但我会尝试编写一个有很好解释的伪代码。

      在原始问题中,每个单元格i, j 的含义是:“用项目 1 到i 填充背包直到达到容量j 的值”,您提供的链接中的解决方案将每个单元格定义为“用桶 1 到i 中的物品填充背包直到达到容量j 的值。请注意,在此变体中,没有不从类中获取元素的情况。

      所以在每一步(每次调用KSTest$n$c),我们需要从第n 个类中找到要选择的元素,使得该元素的权重小于@987654330 @它的价值+KSTest(n - 1, c - w)是最大的。

      所以我认为你应该只将else ifelse 语句更改为:

      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)

      【讨论】:

      • 感谢您抽出宝贵时间提供帮助。我对实现仍然有些困惑。您是否建议 for 循环基本上将在我测试 $tempVal2 的地方进行? $weight 是变成一个二维数组还是你只是为那个类中的每个项目说 $weight[$i] ?如果有帮助,这是我要遵循的伪代码:youtu.be/xOlhR_2QCXY
      • 如果不清楚,我很抱歉,我建议使用代码而不是 else ifelse 语句。同样你是对的,权重现在是一个二维数组,但它可以是你想要实现它的任何方式,这样你就可以访问特定类的所有权重。
      【解决方案3】:

      这是我的 PHP 解决方案。我尝试以一种易于理解的方式对代码进行注释。

      更新: 我更新了代码,因为旧脚本给出了不可靠的结果。这更清洁,并且已经过彻底测试。关键要点是我使用了两个备忘录数组,一个在组级别加速执行,一个在项目级别重建结果。我发现任何尝试跟踪正在选择的项目都是不可靠的,效率也低得多。此外, 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();
      ?>
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-03-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-05-29
        相关资源
        最近更新 更多