我已经获得了一个证据,证明了关于0/1背包问题的普遍看法,我真的很难说服自己我是对的,因为我找不到任何东西任何地方支持我的主张,所以我首先陈述我的主张然后证明他们,我会感谢任何人试图进一步证实我的主张或取消他们。任何合作都表示赞赏。
预先假设:bnb算法倾向于无效节点(如果剩余容量小于当前项的权重,我们不会扩展它。此外,bnb算法已完成以深度优先的方式。
以下是解决背包问题的递归公式:
现在想象一下这个例子:
K = 9
N = 3
V W:
5 4
6 5
3 2
现在这里是针对此问题的动态解决方案和表格:
现在想象一下,无论是否一个好主意,我们都希望通过memoization而不是表格使用递归公式,使用类似地图/字典或简单数组来存储访问过的单元格。为了使用memoization解决这个问题,我们应该解决所表示的单元格:
现在这就像我们使用bnb方法获得的树一样:
现在为了草率的证据:
如果无论如何我的证据都是正确的,那么会出现一些有趣的问题:
答案 0 :(得分:3)
首先,由于你正在应用记忆,你仍然在做DP。这基本上是DP的定义:递归+记忆。这也很好。没有记忆,你的计算成本就会爆炸。试想一下,如果两个项目同时具有权重2,并且第三个和第四个项目具有权重1.它们都会在树中的同一节点处结束,您将不得不多次进行计算,并且您最终会得到指数运行时间。
主要区别在于计算顺序。计算整个矩阵的方式被称为"自下而上的DP",因为你从(0,0)开始并向上工作。你自己的方式(树方法)被称为"自上而下的DP",因为你从目标开始并在树下工作。但他们都在使用动态编程。
现在回答你的问题:
你高估了你真正节省了多少钱。 N = 3
是一个非常小的例子。我很快尝试了一个更大的例子,N = 20
,K=63
(仍然非常小)和随机值和随机权重。这是我创造的第一张照片:
values: [4, 10, 9, 1, 1, 2, 1, 2, 6, 4, 8, 9, 8, 2, 8, 8, 4, 10, 2, 6]
weights: [6, 4, 1, 10, 1, 2, 9, 9, 1, 6, 2, 3, 10, 7, 2, 4, 10, 9, 8, 2]
111111111111111111111111111111111111111111111111111111111111111
111111111111111111111111111111111111111111111111111111111111111
111111111111111111111111111111111111111111111111111111111111111
111111111111111111111111111111111111111111111111111111111111111
111111111111111111111111111111111111111111111111111111111111111
111111111111111111111111111111111111111111111111111111111111111
111111111111111111111111111111111111111111111111111111111111111
111111111111111111111111111111111111111111111111111111111111111
111111111111111111111111111111111111111111111111111111111111111
011111111111111111111111111111111111111111111111111111111111101
000001011111111111111111111111111111111111111111111111111111101
000000010111111111111111111111111111111111111111111111111111101
000000000010101011111111111111111111111111111111111111111010101
000000000000000000001010101111111111111111111111111111111010101
000000000000000000000000000101010101111111111111111111101010101
000000000000000000000000000001010101011111111111111111101010101
000000000000000000000000000000000101000001111100001111100000101
000000000000000000000000000000000000000000010100000111100000101
000000000000000000000000000000000000000000000000000010100000101
000000000000000000000000000000000000000000000000000000000000101
000000000000000000000000000000000000000000000000000000000000001
此图片是您显示的矩阵的转置版本。行代表i
值(数组中的第一个i
元素),cols代表k
值(允许的权重)。 1是DP矩阵中的位置,您将在树木进近期间访问。当然,您会在矩阵的底部看到很多0
,但您将访问上半部分的每个位置。访问矩阵中大约68%的位置。在这种情况下,自下而上的DP解决方案会更快。递归调用较慢,因为您必须为每个递归调用分配一个新的堆栈帧。使用循环而不是递归调用来加速2倍并不是非典型的,这已经足以使自下而上的方法更快。我们甚至还没有谈到树方法的记忆成本。
请注意,我在这里没有使用过实际的bnb。我不太确定你将如何处理绑定部分,因为一旦你通过访问它的孩子来计算它,你实际上只知道它的价值。
凭借我的输入数据,自下而上的方法显然是赢家。但这并不意味着你的做法很糟糕。恰恰相反。它实际上可能非常好。这一切都取决于输入数据。让我们想象K = 10^18
和你所有的权重都是10^16
。自下而上的方法甚至找不到足够的内存来分配矩阵,而你的方法很快就会成功。
但是,你可能通过执行A *而不是bnb来改善你的版本。您可以使用(i, k)
估算每个节点int(k / max(weight[1..i]) * min(values[1..i])
的最佳值,并使用此启发式修剪大量节点。
答案 1 :(得分:3)
我认为你的方面存在一种误解,即动态编程是背包问题的最先进解决方案。该算法在大学教授,因为它是动态编程和伪多项式时间算法的一个简单而好的例子。
我没有该领域的专业知识,也不知道现在最先进的是什么,但分支定界方法已经使用了相当长的一段时间来解决背包问题:本书Knapsak-Problems by Martello and Toth已经很老了,但是广泛地对待分支和界限。
尽管如此,这是一个很好的观察,你可以使用分支和绑定方法进行背包 - 唉,你出生太晚了,不能成为第一个有这个想法的人:)
您的证明中有一些我不理解的内容,在我看来需要更多解释:
O(2^N)
个节点(显然会有这种情况,否则背包不会是NP难的)。我没有在您的证明中看到任何内容,这确保了记忆内存/计算步骤小于O(NK)
。O(K)
内存空间,所以我不明白为什么你可以声称&b; bnb算法在时间和空间上总是优于动态"。也许你的说法属实,但我现在无法按照证据的方式来看待它。
另一个问题是"更好"的定义。如果对于大多数问题或常见问题更好或者对于wost-case(在现实生活中不起任何作用)它是否更好,分支定界方法是否更好?
我链接的书也对算法的运行时间进行了一些比较。基于动态编程的算法(显然比在学校教授的算法更复杂)对于某些问题甚至更好 - 见2.10.1节。一个完全开玩笑还不错!
答案 2 :(得分:2)
实际上,对于整数0/1背包,动态编程可能更好,因为: