考虑下面针对典型背包问题的输入。
V = [10,8,12]
W = [2,3,7]
i = 1,2,3
C = 10
我尝试使用memoization进行递归来解决此示例但发现没有重叠的子问题。
递归程序的签名:
knapsack(int c, int i)
最初称为knapsack(10,1)
解决方案的方法与https://www.youtube.com/watch?v=6h6Fi6AQiRM和https://www.youtube.com/watch?v=ocZMDMZwhCY中解释的相同。
动态编程如何帮助减少这些背包样本的时间复杂度?如果它无助于提高此案例的时间复杂度,那么DP解决方案的最坏情况复杂性也与基于跟踪搜索,即2,功率n [忽略修剪,好像修剪应用,然后复杂性将减少解决方案,再次DP将不会优于非记忆递归解决方案]
**上面的示例中是否真的缺少子问题,或者我遗漏了什么?**
答案 0 :(得分:5)
DP对您的特定问题实例上的所有都没有帮助。但总的来说,即在所有可能的输入实例上,它永远不会解决更多子问题而不是纯递归,并且在许多情况下它解决的问题要少得多。 那是为什么DP很好。
你所有的DP配方保证是它可以通过解决最多n(c+1)
个子问题来解决任何问题的实例,事实上它也适用于你的例子:{{1} } = 3和n
= 10,它通过解决14< = 33子问题(包括原始问题)来解决问题。
类似地,纯递归解决方案保证它可以通过解决最多c
个子问题来解决任何问题的实例。
您似乎认为DP算法应该比递归算法更快地解决每个问题实例,但事实并非如此,并且没有人提出这种说法。存在没有重叠子问题的实例(如您的实例),对于这些实例,DP使用与递归解决方案完全相同数量的子问题来解决问题。这并没有说明两种算法的行为。通常,DP解决每个问题最多使用与递归解决方案一样多的子问题,有时甚至更少 - 因为存在问题实例,递归算法需要多次解决相同的子问题。
简而言之:DP永远不会比递归更糟糕,并且比最坏情况下的递归更好。这不意味着它在每个实例上都更好。
答案 1 :(得分:5)
0/1背包问题有一个pseudo-polynomial-time solution。该解决方案的运行时间为 O(nW),其中n
是可供选择的项目数,W
是背包可以容纳的权重。运行时间被描述为 伪 多项式,因为W
不是n
的函数。
或者是吗?给定n
个项目列表(按重量和值),一个项目存在最大权重,称之为k
。 (最大权重可以在 O(n)时间内确定。)如果W
大于或等于kn
,则问题很简单,所有项目都适合在背包里。因此,我们只需要考虑W < kn
的情况。因此,出于复杂性分析的目的,W
可以表示为n
的函数。
鉴于nW <= k n^2
,算法的运行时间为 O(k n ^ 2)。
现在,观众中的学生将(正确地)认为这仍然是伪多项式时间,因为k
和n
之间没有明确的关系。但是问题的任何具体陈述(按权重和值列出项目)必须在问题陈述本身中具有k
的显式常量值。
足够理论。我们如何将此应用于问题中的示例。
n
显然是3 k
显然是7 因此,预测的运行时间是 O(kn ^ 2) = O(7x3x3) = 63.但是预测的指数运行时间(没有DP)是< em> O(2 ^ n) = O(2 ^ 3) = 8。
你的例子存在问题。 Big-O分析描述了大n
值的算法的渐近行为。它告诉你关于n
的小值的性能几乎没有。如果n
的值足够小2^n < k n^2
,则不会期望DP会改善算法的运行时间,或者根本不会产生任何影响。
您面临的挑战是找到2^n > k n^2
的示例,并且您仍然没有重叠的子问题。
答案 2 :(得分:2)
如果没有任何权重加到任何其他权重并且容量足够大以包括总权重,则带有memoization解决方案的递归的运行时间可以接近2^n
。
动态编程解决方案虽然是O(c*n)
。这是多项式的容量而不是项目数。
在您的示例n=3
和c=10
中,2^n = 8
和c*n = 30
。此处c*n
大于2^n
,因此dp解决方案没有帮助。如果你有更多的项目和一个小容量,那么dp解决方案会更好。这些是dp解决方案可以很好地工作的约束种类。