我想在theta表示法中找到以下背包自顶向下算法的运行时间
KNAPSACK-TOP-DOWN(T, U )
if U < 0 then return −∞
else if T = ∅ or U = 0 then return 0
else
Let t = v, w be the last entry in T
m1 ← KNAPSACK-TOP-DOWN(T − {t}, U )
m2 ← v + KNAPSACK-TOP-DOWN(T − {t}, U − w)
return max{m1 , m2 }
我认为这个问题的上限是O(2 ^ n)。如何找到渐近紧束缚?
答案 0 :(得分:4)
你的伪代码代表了背包问题的天真解决方案。看它的结构很像天真的Fibonacci实现,O(2^n)
。事实上,背包的天真实施是指数级的。请注意,Unbounded knapsack problem可以通过动态编程在O(uT)
中解决。
至于为什么这个实现是指数级的,首先要记住,随着T
和U
的增加,递归调用的数量也会增加。然后注意算法的每次迭代将递归地调用KNAPSACK-TOP-DOWN两次,在最坏的情况下,将两次调用KNAPSACK-TOP-DOWN,依此类推。
(T, U) 1
/ \
/ \
/ \
/ \
/ \
(T-{t},U) (T-{t},U-w) 2
/ \ / \
/ \ / \
(T-{t}-{t'},U) (T-{t}-{t'},U-w) (T-{t}-{t'},U-w) (T-{t}-{t'},U-w-w') 4
8
16
...
正如您所看到的,递归调用的数量会根据背包的项目数量和容量呈指数级增长。另请注意,T
中的新元素将在最坏的情况下引入一个全新的“级别”,其计算量是前一级别的两倍......当然,只有在有足够的容量时才会发生这种情况。留在背包里,所以T和U都会影响计算次数。看看上面的树,显然天真的实现确实是O(2^n)
。
但是,如果你仔细观察“堆栈跟踪”,你会注意到(T-{t}-{t'},U-w)
(因此,它的所有子问题)被调用了两次。这就是动态编程的用武之地。我们可以将计算结果存储在某种数据结构中,并进一步查找它们,以便不需要计算相同的子问题两次。当使用这种方法时,事情会急剧加快,整个问题可以按照与背包的容量*要考虑的项目数量成比例的时间计算,或者用符号表示,O(uT)
This article提供了幼稚和O(uT)
版本的C实现。如果您的问题与家庭作业有关,那么您应该明确地看一下它。最后,搜索“背包问题”将教会你很多关于NP完全,NP-Hard,伪多项式时间等等。