0/1 背包 - 对 Wiki 伪代码的一些说明

Posted

技术标签:

【中文标题】0/1 背包 - 对 Wiki 伪代码的一些说明【英文标题】:0/1 Knapsack - A few clarification on Wiki's pseudocode 【发布时间】:2013-06-12 19:33:47 【问题描述】:

这是 en.wikipedia 关于背包问题的文章中的代码:

// Input:
// Values (stored in array v)
// Weights (stored in array w)
// Number of distinct items (n)
// Knapsack capacity (W)
for w from 0 to W do
  m[0, w] := 0
end for 
for i from 1 to n do
  for j from 0 to W do
    if j >= w[i] then
      m[i, j] := max(m[i-1, j], m[i-1, j-w[i]] + v[i])
    else
      m[i, j] := m[i-1, j]
    end if
  end for
end for

我有两点我疲惫的大脑无法解决。我敢肯定,它们很小,但我真的可以使用帮助。

•m[] 数组的大小是多少? m[n,W]?如果是,是忽略最后一行和最后一列的伪代码,因为它用零填充整个第一行(使用 m[0,w] := 0 的 for() 循环),然后从 1 循环到 n,和 0 到 W。例如,对于 3 个不同的项目 (n==3) 和容量为 4 (W==3),是 m[3,4] 还是 m[4,5]?

•在某处是否有更好的动态背包算法示例?

【问题讨论】:

【参考方案1】:

数组的大小为 (n + 1) × (W + 1),因为值的范围从 [0, 0] 到 [n, W] (含)。

对网格的解释如下: position [k, w] 表示使用前 k 个项目可以得到的最大价值量(假设项目编号为 1, 2, ..., n)并且总重量不超过 w。

第一行完全设置为 0 的原因是因为 [0, w] 形式的任何条目都对应于使用前 0 个项目可以得到的最大值并且最多承载 w 权重。这始终为零,因为您永远无法通过不选择任何项目来获得任何价值。这对应于递归的基本情况。

第一个之后的行使用以下思想填充:如果您想尝试选择第 k 个项目,您首先需要确保您有能力握住它(这意味着 w 必须至少为 w [k])。如果你不能持有它,你最好的选择是充分利用前 k - 1 件受当前重量限制的物品(所以你最好取对应于 m[k - 1, w] 的值. 如果你能持有该项目,你的选择是要么不接受它(并且像以前一样,充分利用其他项目,产生 m[k - 1, w]),要么接受它并最大化剩余的剩余物品的承载能力。这会给你价值 v[k] + m[k - 1, w - w[k]]。

希望这会有所帮助!

【讨论】:

你是疲倦甜点中的绿洲,卡丽熙。【参考方案2】:

我知道已经回答了,只是在c#中添加代码版本,仅供参考(对于简化的背包您可以参考:How do I solve the 'classic' knapsack algorithm recursively?):

版本 1. 使用动态编程求解(类似于 wiki) - 自下而上(表格方法)

第 2 版。 使用动态编程求解,但从上到下(记忆化 - 懒惰)

版本 3. 递归(只是递归解决方案,不使用重叠子问题或能够使用 DP 的最佳子结构属性)

第 4 版。蛮力(遍历所有组合)

参考:http://en.wikipedia.org/wiki/Knapsack_problem、How do I solve the 'classic' knapsack algorithm recursively?

表格 - DP - 版本 (O(nW) - 伪多项式) - O(nW) 内存 - 自下而上

public int Knapsack_0_1_DP_Tabular_Bottom_Up(int[] weights, int[] values, int maxWeight)
        
            this.ValidataInput_Knapsack_0_1(weights, values, maxWeight);
            int[][] DP_Memoization_Max_Value_Cache = new int[values.Length + 1][];
            for (int i = 0; i <= values.Length; i++)
            
                DP_Memoization_Max_Value_Cache[i] = new int[maxWeight + 1];
                for (int j = 0; j <= maxWeight; j++)
                
                    DP_Memoization_Max_Value_Cache[i][j] = 0; //yes, its default - 
                
            
            /// f(i, w) = f(i-1, w) if Wi > w
            ///         Or, max (f(i-1, w), f(i-1, w-Wi) + Vi
            ///         Or 0 if i < 0 
            for(int i = 1; i<=values.Length; i++)
            
                for(int w = 1; w <= maxWeight; w++)
                
                    //below code can be refined - intentional as i just want it
                    //look similar to other 2 versions (top_to_bottom_momoization
                    //and recursive_without_resuing_subproblem_solution
                    int maxValueWithoutIncludingCurrentItem =
                            DP_Memoization_Max_Value_Cache[i - 1][w];
                    if (weights[i - 1] > w)
                    
                        DP_Memoization_Max_Value_Cache[i][w] = maxValueWithoutIncludingCurrentItem;
                    
                    else
                    
                        int maxValueByIncludingCurrentItem =
                            DP_Memoization_Max_Value_Cache[i - 1][w - weights[i - 1]]
                            + values[i-1];
                        int overAllMax = maxValueWithoutIncludingCurrentItem;
                        if(maxValueByIncludingCurrentItem > overAllMax)
                        
                            overAllMax = maxValueByIncludingCurrentItem;   
                        
                        DP_Memoization_Max_Value_Cache[i][w] = overAllMax;
                    
                
            
            return DP_Memoization_Max_Value_Cache[values.Length][maxWeight];
        

DP - 记忆 - 自上而下 - 懒惰评估

/// <summary>
        /// f(i, w) = f(i-1, w) if Wi > w
        ///         Or, max (f(i-1, w), f(i-1, w-Wi) + Vi
        ///         Or 0 if i < 0 
        /// </summary>
        int Knapsack_0_1_DP_Memoization_Top_To_Bottom_Lazy_Recursive(int[] weights, int[] values,
            int index, int weight, int?[][] DP_Memoization_Max_Value_Cache)
        
            if (index < 0)
            
                return 0;
            
            Debug.Assert(weight >= 0);
#if DEBUG
            if ((index == 0) || (weight == 0))
            
                Debug.Assert(DP_Memoization_Max_Value_Cache[index][weight] == 0);
            
#endif
            //value is cached, so return
            if(DP_Memoization_Max_Value_Cache[index][weight] != null)
            
                return DP_Memoization_Max_Value_Cache[index][weight].Value;
            
            Debug.Assert(index > 0);
            Debug.Assert(weight > 0);
            int maxValueWithoutIncludingCurrentItem = this.Knapsack_0_1_DP_Memoization_Top_To_Bottom_Lazy_Recursive
                (weights, values, index - 1, weight, DP_Memoization_Max_Value_Cache);
            if (weights[index-1] > weight)
            
                DP_Memoization_Max_Value_Cache[index][weight] = maxValueWithoutIncludingCurrentItem;
                //current item weight is more, so we cant include - so, just return
                return maxValueWithoutIncludingCurrentItem;
            
            int overallMaxValue = maxValueWithoutIncludingCurrentItem;
            int maxValueIncludingCurrentItem = this.Knapsack_0_1_DP_Memoization_Top_To_Bottom_Lazy_Recursive
                (weights, values, index - 1, weight - weights[index-1],
                    DP_Memoization_Max_Value_Cache) + values[index - 1];
            if(maxValueIncludingCurrentItem > overallMaxValue)
            
                overallMaxValue = maxValueIncludingCurrentItem;
            
            DP_Memoization_Max_Value_Cache[index][weight] = overallMaxValue;
            return overallMaxValue;
        

以及调用的公共方法(调用者的详细信息,请参阅下面的单元测试)

public int Knapsack_0_1_DP_Tabular_Bottom_Up(int[] weights, int[] values, int maxWeight)
        
            this.ValidataInput_Knapsack_0_1(weights, values, maxWeight);
            int[][] DP_Memoization_Max_Value_Cache = new int[values.Length + 1][];
            for (int i = 0; i <= values.Length; i++)
            
                DP_Memoization_Max_Value_Cache[i] = new int[maxWeight + 1];
                for (int j = 0; j <= maxWeight; j++)
                
                    DP_Memoization_Max_Value_Cache[i][j] = 0; //yes, its default - 
                
            
            /// f(i, w) = f(i-1, w) if Wi > w
            ///         Or, max (f(i-1, w), f(i-1, w-Wi) + Vi
            ///         Or 0 if i < 0 
            for(int i = 1; i<=values.Length; i++)
            
                for(int w = 1; w <= maxWeight; w++)
                
                    //below code can be refined - intentional as i just want it
                    //look similar to other 2 versions (top_to_bottom_momoization
                    //and recursive_without_resuing_subproblem_solution
                    int maxValueWithoutIncludingCurrentItem =
                            DP_Memoization_Max_Value_Cache[i - 1][w];
                    if (weights[i - 1] > w)
                    
                        DP_Memoization_Max_Value_Cache[i][w] = maxValueWithoutIncludingCurrentItem;
                    
                    else
                    
                        int maxValueByIncludingCurrentItem =
                            DP_Memoization_Max_Value_Cache[i - 1][w - weights[i - 1]]
                            + values[i-1];
                        int overAllMax = maxValueWithoutIncludingCurrentItem;
                        if(maxValueByIncludingCurrentItem > overAllMax)
                        
                            overAllMax = maxValueByIncludingCurrentItem;   
                        
                        DP_Memoization_Max_Value_Cache[i][w] = overAllMax;
                    
                
            
            return DP_Memoization_Max_Value_Cache[values.Length][maxWeight];
        

递归 - 有 DP 子问题 - 但不重复使用重叠子问题(这应该描述如何更容易将递归版本更改为 DP 从上到下(记忆版本)

public int Knapsack_0_1_OverlappedSubPromblems_OptimalSubStructure(int[] weights, int[] values, int maxWeight)
        
            this.ValidataInput_Knapsack_0_1(weights, values, maxWeight);
            int v = this.Knapsack_0_1_OverlappedSubPromblems_OptimalSubStructure_Recursive(weights, values, index: weights.Length-1, weight: maxWeight);
            return v;
        
        /// <summary>
        /// f(i, w) = f(i-1, w) if Wi > w
        ///         Or, max (f(i-1, w), f(i-1, w-Wi) + Vi
        ///         Or 0 if i < 0 
        /// </summary>
        int Knapsack_0_1_OverlappedSubPromblems_OptimalSubStructure_Recursive(int[] weights, int[] values, int index, int weight)
        
            if (index < 0)
            
                return 0;
            
            Debug.Assert(weight >= 0);
            int maxValueWithoutIncludingCurrentItem = this.Knapsack_0_1_OverlappedSubPromblems_OptimalSubStructure_Recursive(weights,
                values, index - 1, weight);
            if(weights[index] > weight)
            
                //current item weight is more, so we cant include - so, just return
                return maxValueWithoutIncludingCurrentItem;
            
            int overallMaxValue = maxValueWithoutIncludingCurrentItem;
            int maxValueIncludingCurrentItem = this.Knapsack_0_1_OverlappedSubPromblems_OptimalSubStructure_Recursive(weights,
                values, index - 1, weight - weights[index]) + values[index];
            if(maxValueIncludingCurrentItem > overallMaxValue)
            
                overallMaxValue = maxValueIncludingCurrentItem;
            
            return overallMaxValue;
        

蛮力(遍历所有组合)

private int _maxValue = int.MinValue;
        private int[] _valueIndices = null;
        public void Knapsack_0_1_BruteForce_2_Power_N(int[] weights, int[] values, int maxWeight)
        
            this.ValidataInput_Knapsack_0_1(weights, values, maxWeight);
            this._maxValue = int.MinValue;
            this._valueIndices = null;
            this.Knapsack_0_1_BruteForce_2_Power_N_Rcursive(weights, values, maxWeight, 0, 0, 0, new List<int>());
        
        private void Knapsack_0_1_BruteForce_2_Power_N_Rcursive(int[] weights, int[] values, int maxWeight, int index, int currentWeight, int currentValue, List<int> currentValueIndices)
        
            if(currentWeight > maxWeight)
            
                return;
            
            if(currentValue > this._maxValue)
            
                this._maxValue = currentValue;
                this._valueIndices = currentValueIndices.ToArray();
            
            if(index == weights.Length)
            
                return;
            
            Debug.Assert(index < weights.Length);
            var w = weights[index];
            var v = values[index];
            //see if the current value, conributes to max value
            currentValueIndices.Add(index);
            Knapsack_0_1_BruteForce_2_Power_N_Rcursive(weights, values, maxWeight, index + 1, currentWeight + w,
                currentValue + v, currentValueIndices);
            currentValueIndices.Remove(index);
            //see if current value, does not contribute to max value
            Knapsack_0_1_BruteForce_2_Power_N_Rcursive(weights, values, maxWeight, index + 1, currentWeight, currentValue,
                currentValueIndices);
        

单元测试 1

[TestMethod]
        public void Knapsack_0_1_Tests()
        
            int[] benefits = new int[]  60, 100, 120 ;
            int[] weights = new int[]  10, 20, 30 ;
            this.Knapsack_0_1_BruteForce_2_Power_N(weights, values: benefits, maxWeight: 50);
            Assert.IsTrue(this._maxValue == 220);
            int v = this.Knapsack_0_1_OverlappedSubPromblems_OptimalSubStructure(weights, 
                values: benefits, maxWeight: 50);
            Assert.IsTrue(v == 220);
            v = this.Knapsack_0_1_DP_Memoization_Top_To_Bottom_Lazy(weights,
                values: benefits, maxWeight: 50);
            Assert.IsTrue(v == 220);
            v = this.Knapsack_0_1_DP_Tabular_Bottom_Up(weights,
                values: benefits, maxWeight: 50);
            Assert.IsTrue(v == 220);
            benefits = new int[]  3, 4, 5, 8, 10 ;
            weights = new int[]  2, 3, 4, 5, 9 ;
            this.Knapsack_0_1_BruteForce_2_Power_N(weights, values: benefits, maxWeight: 20);
            Assert.IsTrue(this._maxValue == 26);
            v = this.Knapsack_0_1_OverlappedSubPromblems_OptimalSubStructure(weights, values: benefits,
                maxWeight: 20);
            Assert.IsTrue(v == 26);
            v = this.Knapsack_0_1_DP_Memoization_Top_To_Bottom_Lazy(weights,
                values: benefits, maxWeight: 20);
            Assert.IsTrue(v == 26);
            v = this.Knapsack_0_1_DP_Tabular_Bottom_Up(weights,
                values: benefits, maxWeight: 20);
            Assert.IsTrue(v == 26);
            benefits = new int[]  3, 4, 5, 6;
            weights = new int[]  2, 3, 4, 5 ;
            this.Knapsack_0_1_BruteForce_2_Power_N(weights, values: benefits, maxWeight: 5);
            Assert.IsTrue(this._maxValue == 7);
            v = this.Knapsack_0_1_OverlappedSubPromblems_OptimalSubStructure(weights, values: benefits,
                maxWeight: 5);
            Assert.IsTrue(v == 7);
            v = this.Knapsack_0_1_DP_Memoization_Top_To_Bottom_Lazy(weights,
                values: benefits, maxWeight: 5);
            Assert.IsTrue(v == 7);
            v = this.Knapsack_0_1_DP_Tabular_Bottom_Up(weights,
                values: benefits, maxWeight: 5);
            Assert.IsTrue(v == 7);
        

单元测试 2

[TestMethod]
        public void Knapsack_0_1_Brute_Force_Tests()
        
            int[] benefits = new int[]  60, 100, 120 ;
            int[] weights = new int[]  10, 20, 30 ;
            this.Knapsack_0_1_BruteForce_2_Power_N(weights, values: benefits, maxWeight: 50);
            Assert.IsTrue(this._maxValue == 220);
            Assert.IsTrue(this._valueIndices.Contains(1));
            Assert.IsTrue(this._valueIndices.Contains(2));
            Assert.IsTrue(this._valueIndices.Length == 2);
            this._maxValue = int.MinValue;
            this._valueIndices = null;
            benefits = new int[]  3, 4, 5, 8, 10 ;
            weights = new int[]  2, 3, 4, 5, 9 ;
            this.Knapsack_0_1_BruteForce_2_Power_N(weights, values: benefits, maxWeight: 20);
            Assert.IsTrue(this._maxValue == 26);
            Assert.IsTrue(this._valueIndices.Contains(0));
            Assert.IsTrue(this._valueIndices.Contains(2));
            Assert.IsTrue(this._valueIndices.Contains(3));
            Assert.IsTrue(this._valueIndices.Contains(4));
            Assert.IsTrue(this._valueIndices.Length == 4);
        

【讨论】:

以上是关于0/1 背包 - 对 Wiki 伪代码的一些说明的主要内容,如果未能解决你的问题,请参考以下文章

0-1背包问题详解1(伪背包问题)

0-1背包问题的回溯法中,剪枝用的上界函数问题

用回溯法做0-1背包问题,这两行(程序中标注)是干嘛?为啥又要减?

c_cpp 以递归方式做河内塔问题的基准.n = 20来自维基百科的伪代码:https://zh.wikipedia.org/wiki/汉诺塔

[ctf wiki pwn] stackoverflow: 2016-360春秋杯 srop wp

[ctf wiki pwn] stackoverflow: 2016-360春秋杯 srop wp