背包动态规划解决方案的时间复杂度

Posted

技术标签:

【中文标题】背包动态规划解决方案的时间复杂度【英文标题】:Time Complexity for Knapsack Dynamic Programming solution 【发布时间】:2014-06-15 21:54:00 【问题描述】:

我看到了0-1背包问题的递归动态规划解决方案here。我记住了解决方案并想出了以下代码。

private static int knapsack(int i, int W, Map<Pair<Integer, Integer>>, Integer> cache)

 if (i < 0) 
    return 0;
 
Pair<Integer, Integer> pair = new Pair<>(i,W);
if (cache.contains(pair)) 
  return cache.get(pair)

int result = -1;
if (weights[i] > W) 
    result = knapsack(i-1, W);
 else 
    result = Math.max(knapsack(i-1, W), knapsack(i-1, W - weights[i]) + values[i]);

cache.put(pair, result);
return result;

有人可以向我解释为什么时间复杂度应该是 O(nW),其中 n 是项目数,W 是对重量的限制。

【问题讨论】:

【参考方案1】:

如果您仔细考虑一下表格在 DP 的表格实现中的样子,就会更加明显。它在一个轴上有项目,在另一个轴上有最大可实现的重量,每个可能的整数重量有一行。对于某些权重集,表格必须密集填满才能找到最佳答案。这些相同的权重集将需要您的备忘录哈希中的 Omega(nW) 对,因为每个条目都是一个恒定时间计算,同时计算所有条目。思考如何获得昂贵的重量组是一个很好的练习,所以我会把它交给你。

【讨论】:

非常感谢。我现在明白了很多。另外,如果你能得到一个 O(nW) 的解决方案,为什么这个问题会被认为是 NP-Hard? NP hard 是根据输入长度的运行时间来定义的。 W 有长度上限(log W)。所以 O(W) 与 O(2^# bits in W) 相同,都是指数时间。这类算法称为“弱 NP 难”。【参考方案2】:

是的,这就是我不喜欢递归的原因之一。您几乎总是可以将递归算法重写为只使用循环而不使用递归的算法。以下是仅使用 for 循环的算法:

A[i,j]=0 for j=0, 1, ..., W

For i=1, 2, ..., n
    For j=0, 1, ..., W
        A[i,j]=max(A[i-1,j], A[i-1,j-weight[i]]+value[i] // or 0 if the index is invalid

Return A[n,W]

这里,很明显复杂度是O(nW)。我会留给您将这段代码与您的代码进行比较。

【讨论】:

谢谢阿里。我似乎出于不同的原因喜欢递归。一旦你想出了典型动态规划问题的递归关系,它就很容易实现。 @Lee 无论如何,我希望我的回答有助于理解它的复杂性。【参考方案3】:

递归动态规划算法可以用子问题图表示。

子问题图由类似于非重叠子问题的顶点组成。顶点中的有向边表示递归调用。边实际上代表子问题的依赖关系。

The runtime of the dynamic algorithm = (time to solve each subproblem)*(number of unique subproblems)

通常,

the cost = (outdegree of each vertex)*(number of vertices)

对于背包,

每个顶点的出度最多为 2=O(1)。这是因为在每个子问题中,我们最多尝试用两种方式来解决它。

现在如果我们检查子问题,我们可以找到一些模式,

The subproblem `(n,W)` depends on `(n-1,W)` and `(n-1,W-w(n))`.

`(n-1,W)` depends on `(n-2,W)` and `(n-2,W-w(n-1))`
`(n-1,W-w(n))` depends on `(n-2,W-w(n))` and `(n-2,W-w(n)-w(n-1))`

请注意,对于每个n 项目,权重最多可以变化1 to W。 (我们可以将此扩展模式与添加维度的动态斐波那契问题模式进行比较。)

所以最多有n*W 唯一的子问题。

因为我们只解决每个子问题一次,

运行时间 = O(1)O(nW) = O(n*W)。

【讨论】:

这个解释很好!【参考方案4】:
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class Knapsack 
        double price;double kg;double pricePerKg;
Knapsack(double price,double kg, double pricePerKg)
    this.price=price;
    this.kg=kg;
    this.pricePerKg=pricePerKg;

public static List<Double> pricePerKg(List<Double> price,List<Double> kg)
    List<Double> pricePerKg = new ArrayList<Double>();
    for(int i=0;i<price.size();i++) 
        pricePerKg.add(price.get(i)/kg.get(i));
    
    return pricePerKg;

public String toString()  return String.format("%s,%s,%s",price,kg,pricePerKg); 
    public static void main(String[] args) 
    List<Knapsack> list = new ArrayList<Knapsack>();
    double W=50;
    List<Double> price = new ArrayList<Double>();
    price.add(60.00);
    price.add(120.00);
    price.add(100.00);
    price.add(1000.00);
    ArrayList<Double> kg = new ArrayList<Double>();
    kg.add(10.00);
    kg.add(30.00);
    kg.add(20.00);
    kg.add(100.00);
    List<Double> pricePerKg =pricePerKg(price,kg);
    double weightCarry=0,currentWeight=0;
    double sum=0;
    for(int i=0;i<pricePerKg.size();i++) 
        list.add(new Knapsack(price.get(i),kg.get(i),pricePerKg.get(i)));
    
    Collections.sort(list,new Comparator<Knapsack>() 
        @Override
        public int compare(Knapsack o1, Knapsack o2)           
             return o1.pricePerKg > o2.pricePerKg ? -1 : o1.pricePerKg == o2.pricePerKg ? 0 : 1;
        
    );
    System.out.println(list.toString().replaceAll(",", "\n"));
    for(int i=0;i<list.size();i++) 
        Knapsack li=list.get(i);
        weightCarry+=Double.valueOf(li.toString().split(",")[1]);
            if(weightCarry<W) 
                sum+=Double.valueOf(li.toString().split(",")[1])*Double.valueOf(li.toString().split(",")[2]);
              currentWeight=weightCarry;
            
            else 
                sum+=(W-currentWeight)*Double.valueOf(li.toString().split(",")[2]);
            break;
            
    
    System.out.println("Fractional knapsack="+sum);


时间复杂度接近 O(nlogn)

【讨论】:

以上是关于背包动态规划解决方案的时间复杂度的主要内容,如果未能解决你的问题,请参考以下文章

动态规划——背包问题

动态规划——背包问题

笨办法理解动态规划算法

动态规划本质理解:01背包问题

算法笔记:动态规划——背包问题(上)

算法笔记:动态规划——背包问题(上)