带有项目组的背包方程

Posted

技术标签:

【中文标题】带有项目组的背包方程【英文标题】:Knapsack Equation with item groups 【发布时间】:2015-11-03 10:59:12 【问题描述】:

显然不能将其称为 Stack Overflow 上的问题,但是我目前正在尝试了解如何在背包问题中以项目组的形式集成约束。在这种情况下,我的数学技能被证明是相当有限的,但是我非常积极地既要按预期进行这项工作,又要弄清楚每个方面的作用(按此顺序,因为事情在工作时更有意义)。

话虽如此,我在Rosetta Code 找到了一个绝对漂亮的实现,并清理了一些变量名,以帮助自己从一个非常基本的角度更好地理解这一点。

不幸的是,我很难弄清楚如何应用此逻辑来包含项目组。我的目的是建立梦幻球队,为每个球员提供我自己的价值和体重(积分/薪水),但没有团队(在我的情况下是职位)我无法这样做。

谁能指出我正确的方向?我正在审查其他语言的代码示例以及对整个问题的其他描述,但是我希望通过任何可能的方式来实现这些组。

<?php

function knapSolveFast2($itemWeight, $itemValue, $i, $availWeight, &$memoItems, &$pickedItems)

    global $numcalls;
    $numcalls++;

    // Return memo if we have one
    if (isset($memoItems[$i][$availWeight]))
    
        return array( $memoItems[$i][$availWeight], $memoItems['picked'][$i][$availWeight] );
    
    else
    
        // At end of decision branch
        if ($i == 0)
        
            if ($itemWeight[$i] <= $availWeight)
             // Will this item fit?
                $memoItems[$i][$availWeight] = $itemValue[$i]; // Memo this item
                $memoItems['picked'][$i][$availWeight] = array($i); // and the picked item
                return array($itemValue[$i],array($i)); // Return the value of this item and add it to the picked list

            
            else
            
                // Won't fit
                $memoItems[$i][$availWeight] = 0; // Memo zero
                $memoItems['picked'][$i][$availWeight] = array(); // and a blank array entry...
                return array(0,array()); // Return nothing
            
           

        // Not at end of decision branch..
        // Get the result of the next branch (without this one)
        list ($without_i,$without_PI) = knapSolveFast2($itemWeight, $itemValue, $i-1, $availWeight,$memoItems,$pickedItems);

        if ($itemWeight[$i] > $availWeight)
         // Does it return too many?
            $memoItems[$i][$availWeight] = $without_i; // Memo without including this one
            $memoItems['picked'][$i][$availWeight] = array(); // and a blank array entry...
            return array($without_i,array()); // and return it
        
        else
        
            // Get the result of the next branch (WITH this one picked, so available weight is reduced)
            list ($with_i,$with_PI) = knapSolveFast2($itemWeight, $itemValue, ($i-1), ($availWeight - $itemWeight[$i]),$memoItems,$pickedItems);
            $with_i += $itemValue[$i];  // ..and add the value of this one..

            // Get the greater of WITH or WITHOUT
            if ($with_i > $without_i)
            
                $res = $with_i;
                $picked = $with_PI;
                array_push($picked,$i);
            
            else
            
                $res = $without_i;
                $picked = $without_PI;
            

            $memoItems[$i][$availWeight] = $res; // Store it in the memo
            $memoItems['picked'][$i][$availWeight] = $picked; // and store the picked item
            return array ($res,$picked); // and then return it
           
    


$items = array("map","compass","water","sandwich","glucose","tin","banana","apple","cheese","beer","suntan cream","camera","t-shirt","trousers","umbrella","waterproof trousers","waterproof overclothes","note-case","sunglasses","towel","socks","book");
$weight = array(9,13,153,50,15,68,27,39,23,52,11,32,24,48,73,42,43,22,7,18,4,30);
$value = array(150,35,200,160,60,45,60,40,30,10,70,30,15,10,40,70,75,80,20,12,50,10);

## Initialize
$numcalls = 0;
$memoItems = array();
$selectedItems = array();

## Solve
list ($m4, $selectedItems) = knapSolveFast2($weight, $value, sizeof($value)-1, 400, $memoItems, $selectedItems);

# Display Result 
echo "<b>Items:</b><br>" . join(", ", $items) . "<br>";
echo "<b>Max Value Found:</b><br>$m4 (in $numcalls calls)<br>";
echo "<b>Array Indices:</b><br>". join(",", $selectedItems) . "<br>";

echo "<b>Chosen Items:</b><br>";
echo "<table border cellspacing=0>";
echo "<tr><td>Item</td><td>Value</td><td>Weight</td></tr>";

$totalValue = 0;
$totalWeight = 0;

foreach($selectedItems as $key)

    $totalValue += $value[$key];
    $totalWeight += $weight[$key];

    echo "<tr><td>" . $items[$key] . "</td><td>" . $value[$key] . "</td><td>".$weight[$key] . "</td></tr>";


echo "<tr><td align=right><b>Totals</b></td><td>$totalValue</td><td>$totalWeight</td></tr>";
echo "</table><hr>";

?>

【问题讨论】:

你能清楚地定义问题和期望的最终结果吗?这将有助于及时理解代码,而不是手动弄清楚。 如果你为一个问题设置了赏金,你真的应该尝试更加活跃。 您是否尝试使用来自 [this] (***.com/questions/29729609/…) 帖子的信息? 期望的最终结果是基本上让项目属于组和每个组的多个点。这被用于确定最佳梦幻足球阵容,在我的情况下,将有一个 QB、2 RB、3 WR、1 TE、1 DEFENSE。每个项目(玩家)都有一个位置,并且需要适合该位置。 补充一点信息,在这种特殊情况下,每个“项目”基本上都是球员,权重是他们的薪水,价值是他们得分。上面的脚本工作得很好,只是我需要弄清楚如何将玩家分成几组。没有那个,我最终可能会得到 5 个 QB 球员,其中只允许 1 个。 【参考方案1】:

那个背包程序是传统的,但我认为它掩盖了正在发生的事情。让我向您展示如何从蛮力解决方案中更直接地推导出 DP。

在 Python 中(抱歉,这是我选择的脚本语言),蛮力解决方案可能如下所示。首先,有一个使用广度优先搜索生成所有子集的函数(这很重要)。

def all_subsets(S):  # brute force
    subsets_so_far = [()]
    for x in S:
        new_subsets = [subset + (x,) for subset in subsets_so_far]
        subsets_so_far.extend(new_subsets)
    return subsets_so_far

然后有一个函数返回True,如果解决方案是有效的(在预算范围内并且有适当的位置细分) - 称之为is_valid_solution - 一个函数,给定一个解决方案,返回总玩家价值(@ 987654324@)。假设players是可用玩家列表,最优解是这样。

max(filter(is_valid_solution, all_subsets(players)), key=total_player_value)

现在,对于 DP,我们将函数 cull 添加到 all_subsets

def best_subsets(S):  # DP
    subsets_so_far = [()]
    for x in S:
        new_subsets = [subset + (x,) for subset in subsets_so_far]
        subsets_so_far.extend(new_subsets)
        subsets_so_far = cull(subsets_so_far)  ### This is new.
    return subsets_so_far

cull 所做的是丢弃在我们寻找最佳解决方案时显然不会遗漏的部分解决方案。如果部分解决方案已经超出预算,或者某个位置已经有太多玩家,那么可以放心地丢弃它。让is_valid_partial_solution 成为一个测试这些条件的函数(它可能看起来很像is_valid_solution)。到目前为止,我们有这个。

def cull(subsets):  # INCOMPLETE!
    return filter(is_valid_partial_solution, subsets)

另一个重要的测试是某些部分解决方案比其他解决方案更好。如果两个部分解决方案具有相同的位置分解(例如,两个前锋和一个中锋)并且成本相同,那么我们只需要保留更有价值的那个。让cost_and_position_breakdown 采用一个解决方案并生成一个对指定属性进行编码的字符串。

def cull(subsets):
    best_subset =   # empty dictionary/map
    for subset in filter(is_valid_partial_solution, subsets):
        key = cost_and_position_breakdown(subset)
        if (key not in best_subset or
            total_value(subset) > total_value(best_subset[key])):
            best_subset[key] = subset
    return best_subset.values()

就是这样。这里有很多优化工作要做(例如,丢弃有更便宜和更有价值的部分解决方案的部分解决方案;修改数据结构,这样我们就不会总是从头开始计算价值和位置细分,并减少存储成本),但可以逐步解决。

【讨论】:

除了蛮力方法的指数复杂性之外,没有使用 Brett 难以解决的神秘“项目组”。 @AlexBlex 1. 我不建议使用蛮力。 2.项目组隐藏在cost_and_position_breakdown函数中。【参考方案2】:

在 PHP 中编写递归函数的一个潜在小优势是变量是按值传递(意味着复制)而不是引用,这可以节省一两步。

也许您可以通过包含示例输入和输出来更好地阐明您要查找的内容。这是一个从给定组进行组合的示例-我不确定这是否是您的意图...我使访问部分结果的部分允许在其权重较低时考虑价值较小的组合-所有这些都可以更改以您喜欢的特定方式修剪。

function make_teams($players, $position_limits, $weights, $values, $max_weight)
  $player_counts = array_map(function($x)
                     return count($x);
                   , $players);
  $positions = array_map(function($x) 
                 $positions[] = []; 
               ,$position_limits);
  $num_positions = count($positions);
  $combinations = [];
  $hash = [];
  $stack = [[$positions,0,0,0,0,0]];

  while (!empty($stack))
    $params = array_pop($stack);
    $positions = $params[0];
    $i = $params[1];
    $j = $params[2];
    $len = $params[3];
    $weight = $params[4];
    $value = $params[5];

    // too heavy
    if ($weight > $max_weight)
      continue;

    // the variable, $positions, is accumulating so you can access the partial result
     else if ($j == 0 && $i > 0)

      // remember weight and value after each position is chosen
      if (!isset($hash[$i]))
        $hash[$i] = [$weight,$value];

      // end thread if current value is lower for similar weight
       else if ($weight >= $hash[$i][0] && $value < $hash[$i][1])
        continue;

      // remember better weight and value
       else if ($weight <= $hash[$i][0] && $value > $hash[$i][1])
        $hash[$i] = [$weight,$value];
      
    

    // all positions have been filled
    if ($i == $num_positions)
      $positions[] = $weight;
      $positions[] = $value;

      if (!empty($combinations))
        $last = &$combinations[count($combinations) - 1];
        if ($weight < $last[$num_positions] && $value > $last[$num_positions + 1])
          $last = $positions;
         else 
          $combinations[] = $positions;
        
       else 
        $combinations[] = $positions;
      

    // current position is filled
     else if (count($positions[$i]) == $position_limits[$i])
      $stack[] = [$positions,$i + 1,0,$len,$weight,$value];

    // otherwise create two new threads: one with player $j added to
    // position $i, the other thread skipping player $j
     else 
      if ($j < $player_counts[$i] - 1)
        $stack[] = [$positions,$i,$j + 1,$len,$weight,$value];
      
      if ($j < $player_counts[$i])
        $positions[$i][] = $players[$i][$j];
        $stack[] = [$positions,$i,$j + 1,$len + 1
                   ,$weight + $weights[$i][$j],$value + $values[$i][$j]];
      
    
  
  return $combinations;

输出:

$players = [[1,2],[3,4,5],[6,7]];
$position_limits = [1,2,1];
$weights = [[2000000,1000000],[10000000,1000500,12000000],[5000000,1234567]];
$values = [[33,5],[78,23,10],[11,101]];
$max_weight = 20000000;

echo json_encode(make_teams($players, $position_limits, $weights, $values, $max_weight));

/*
[[[1],[3,4],[7],14235067,235],[[2],[3,4],[7],13235067,207]]
*/

【讨论】:

以上是关于带有项目组的背包方程的主要内容,如果未能解决你的问题,请参考以下文章

带有多项式的 C++ 方程

Unity中如何实现背包UI

Dp状态设计与方程总结

背包九讲

背包问题怎样给出符号说明,目标函数和约束条件

完全背包