这是一个问题,我认为已经有一个算法 - 但我不知道谷歌似乎使用的正确单词:)。
问题:我想创建一个小程序,我将选择一个包含任何文件的目录(但我的目的是媒体文件,音频和视频)。之后,我想以MB输入不得超过的最大文件总大小。此时,您将点击“计算最佳拟合”按钮。
此按钮应比较目录中的所有文件,并提供一个文件列表,这些文件放在一起时最接近最大文件总大小而不超过限制。
通过这种方式,您可以在刻录CD或DVD时找出要合并的文件,以便您能够尽可能多地使用光盘。
我试图为此自己提出算法 - 但失败了:(。
任何人都知道一些很好的算法吗?
提前致谢:)
答案 0 :(得分:5)
正如其他人指出的那样,这是一个组合优化问题的背包问题。这意味着您要查找集合的某些子集或置换,以最小化(或最大化)某个成本。另一个众所周知的问题是Traveling Salesman Problem。
这些问题通常很难解决。但是,如果您对几乎最优的解决方案感兴趣,则可以使用非确定性算法,例如simulated annealing。您很可能无法获得最佳解决方案,但几乎是最佳解决方案。
This link解释了模拟退火如何解决背包问题,因此应该对你感兴趣。
答案 1 :(得分:4)
为了好玩,我试用了精确的动态编程解决方案。用Python写的,因为我非常自信你不应该优化,直到你必须; - )
这可以提供一个开始,或者粗略地了解在接近近似之前你可以接近多少。
代码基于http://en.wikipedia.org/wiki/Knapsack_problem#0-1_knapsack_problem,因此信息量较少的变量名为m
,W
,w
,v
。
#!/usr/bin/python
import sys
solcount = 0
class Solution(object):
def __init__(self, items):
object.__init__(self)
#self.items = items
self.value = sum(items)
global solcount
solcount += 1
def __str__(self):
#return str(self.items) + ' = ' + str(self.value)
return ' = ' + str(self.value)
m = {}
def compute(v, w):
coord = (len(v),w)
if coord in m:
return m[coord]
if len(v) == 0 or w == 0:
m[coord] = Solution([])
return m[coord]
newvalue = v[0]
newarray = v[1:]
notused = compute(newarray, w)
if newvalue > w:
m[coord] = notused
return notused
# used = Solution(compute(newarray, w - newvalue).items + [newvalue])
used = Solution([compute(newarray, w - newvalue).value] + [newvalue])
best = notused if notused.value >= used.value else used
m[coord] = best
return best
def main():
v = [int(l) for l in open('filesizes.txt')]
W = int(sys.argv[1])
print len(v), "items, limit is", W
print compute(v, W)
print solcount, "solutions computed"
if __name__ == '__main__':
main()
为简单起见,我只考虑文件大小:一旦你有了你想要使用的大小列表,你可以通过搜索列表找到一些具有这些大小的文件名,所以没有必要在文件名中加入文件名。核心,程序的缓慢部分。我也用块大小的倍数表示所有内容。
正如您所看到的,我已经注释掉了提供实际解决方案的代码(而不是解决方案的价值)。那是为了节省内存 - 存储所用文件列表的正确方法不是每个解决方案中的一个列表,而是让每个解决方案都指向它所源自的解决方案。然后,您可以通过返回链计算最终的文件大小列表,输出每一步的值之间的差异。
列出100个随机生成的文件大小,范围在2000-6000(我假设有2k块,所以文件大小为4-12MB),这可以在我的笔记本电脑上100秒内解决W = 40K。这样做可以计算出2.6M的可能的4M解决方案。
复杂度为O(W * n),其中n是文件数。这与问题是NP完全的事实并不矛盾。所以我至少要接近一个解决方案,这只是在未经优化的Python中。
显然现在需要进行一些优化,因为实际上需要解决W = 4M(8GB DVD)和你拥有的许多文件(比方说几千)。假设程序允许花费15分钟(与写入DVD所需的时间相当),这意味着当前性能大约为10 ^ 3。因此,我们遇到的问题很难在PC上快速准确地解决,但不会超出技术范围。
内存使用是主要问题,因为一旦我们开始进行交换,我们就会放慢速度,如果我们用完了虚拟地址空间,我们就会遇到麻烦,因为我们必须在磁盘上实现自己的解决方案存储。我的测试运行峰值为600MB。如果您在32位计算机上用C编写代码,则每个“解决方案”的固定大小为8个字节。因此,你可以生成一个巨大的二维数组,而不需要在循环中进行任何内存分配,但在2GB的RAM中,你只能处理W = 4M和n = 67。哎呀 - DVD出来了。它可以解决2-k块大小的CD,但是:W = 350k,n = 766。
编辑:MAK建议以自下而上的方式迭代计算,而不是递归自上而下,应该大规模地减少内存需求。首先计算所有0< = w< = W的m(1,w)。从该数组,你可以计算所有0< = w< = W的m(2,w)然后你可以扔掉所有m(1,w)值:你不需要它们来计算m(3,w)等。
顺便说一下,我怀疑你想要解决的问题实际上可能是bin packing problem,而不仅仅是如何让最接近填充DVD的问题。如果你有一堆文件,你想把它们全部写入DVD,尽可能少用DVD。在某些情况下,解决垃圾箱包装问题非常容易,但解决这个问题很难。例如,假设您有8GB磁盘和15GB小文件。它需要进行一些搜索才能找到最接近8GB的匹配,但是通过在每个磁盘上放置大约一半的文件来简单地解决bin-packing问题 - 因为你是真的如何划分它们并不重要无论你做什么都会浪费1GB的空间。
所有这一切都说,有极快的启发式方法可以在很多时候提供不错的结果。最简单的方法是浏览文件列表(可能按大小递减顺序),如果适合则包含每个文件,否则将其排除。如果快速近似解决方案不够“足够”,你只需要回到任何缓慢的状态,选择“足够”。
答案 2 :(得分:2)
听起来你有hard问题。这个问题众所周知,但没有有效的解决方案(可以?)。
答案 3 :(得分:0)
然后,尝试所有大小为<<你还可以看一下bucketizer perl模块的实现,它完全符合你的要求。我不确定它到底是做什么的,但手册提到有一种“蛮力”方式,所以我假设还必须进行某种优化。
答案 4 :(得分:0)
感谢您的回答。
在给定答案的指导下,我现在更多地研究了这个问题。我发现这个网页http://www.mathmaniacs.org/lessons/C-subsetsum/index.html。它讲述了子集求和问题,我认为这是我在这里描述的问题。
网页上的一句是:
-
你可能想要指出像2300这样的数字是如此之大,以至于计算机每秒钟的速度超过一百万或十亿,直到我们的太阳已经烧毁很久才会达到2300。
-
就个人而言,当比较更大量的文件大小时,我会更多地使用这个算法,比如10或更少,因为如果文件数量很少,只需通过手动试验和错误就可以轻松达到可能的最大数量
带有mp3:s的CD可以很容易地拥有100个mp3和更多DVD,这导致太阳在我得到答案之前就烧掉了。)。
随机尝试找到最佳总和显然可以让你非常接近,但它永远不能保证是最佳答案,而且运气不好也可能相当遥远。蛮力是获得最佳答案的唯一真实方式,而且时间过长。
所以我想我只是继续手动估算一个好的文件组合来刻录CD和DVD。 :)
感谢您的帮助。 :)
答案 5 :(得分:0)
如果您正在寻找合理的启发式方法,并且目标是最大限度地减少所需的磁盘数量,那么您可以考虑使用这个简单的方法。它类似于我最近用于工作车间问题的那个。我能够将它与已知的最佳值进行比较,并发现它提供了最佳或非常接近最佳的分配。
假设B是所有文件组合的大小,C是每个磁盘的容量。那么你至少需要n = roundup(B / C)磁盘。尝试将所有文件放在n个磁盘上。如果您能够这样做,那么您已经完成并拥有最佳解决方案。否则,尝试将所有文件放在n + 1个磁盘上。如果你能够这样做,你就有了一个启发式解决方案;否则尝试将文件放在n + 2个磁盘上,依此类推,直到你能够这样做。
对于以下磁盘的任何给定文件分配(可能超过某些磁盘容量),设si为分配给磁盘i的文件的总大小,并且t = max si。我们在t< = C时完成。
首先,命令(和索引)文件从大到小。
对于m> = n个磁盘,
以前后的方式将文件分配给磁盘:1-> 1,2-> 2,...... m-> m,m + 1> m-1, m + 2-> m-2,...... 2m-> 1,2m + 1-> 2,2m + 2-> 3 ... 3m-> m,3m + 1-> m -1,依此类推,直到分配完所有文件,而不考虑磁盘容量。如果t <= C,我们就完成了(如果m = n,则分配是最佳的);否则请转到#2。
尝试通过将文件从具有si = t的磁盘i移动到另一个磁盘来减少t,而不增加t。继续这样做直到t <= C,在这种情况下我们完成(并且如果m = n则分配是最佳的),或者t不能进一步减小,在这种情况下转到#3。
尝试通过在磁盘之间执行成对交换来减少t。继续这样做直到t <= C,在这种情况下我们完成(并且如果m = n则分配是最佳的),或者对于成对交换不能进一步减小t。在后一种情况下,重复#2,除非在最后一次重复#2时没有进行改进,在这种情况下,将m递增1并重复#1。
在#2和#3中,当然有不同的方式来订购可能的重新分配和成对交换。