好吧,我有问题。我有一组“A”各种尺寸的瓶子,里面装满了水。
然后我还有另一组“B”各种尺寸的瓶子,全是空的。
我想将水从A转移到B,知道每组的总容量是相同的。 (即:A 组含有与 B 组相同量的水)。
这本身当然是微不足道的,只需将 B 中的第一个瓶子倒入 A 中的第一个瓶子中,直到装满即可。然后,如果 B 中的瓶子里还有水,则继续 A 中的第二个瓶子,依此类推。
但是,我想最小化倒水的总数(从一个瓶子倒到另一个瓶子的动作,每个动作计 1,独立于它涉及多少水)
我想找到一种贪婪算法来做到这一点,或者如果不可能的话至少是一种有效的算法。然而,效率对于算法的正确性来说是次要的(我不想要次优的解决方案)。
当然,这个问题只是计算机程序中管理个人开支的实际问题的一个隐喻。
Ok, I have a problem. I have a set "A" of bottles of various sizes, all full of water.
Then I have another set "B" of bottles of various sizes, all empty.
I want to transfer the water from A to B, knowing that the total capacity of each set is the same. (i.e.: Set A contains the same amount of water as set B).
This is of course trivial in itself, just take the first bottle in B, pour it in the first in A until this is full. Then if the bottle from B has still water in it, go on with the second bottle in A, etc.
However, I want to minimize the total number of pours (the action of pouring from a bottle into another, each action counts 1, independently from how much water it involves)
I'd like to find a greedy algorithm to do this, or if not possible at least an efficient one. However, efficiency is secondary to correctness of the algorithm (I don't want a suboptimal solution).
Of course this problem is just a metaphor for a real problem in a computer program to manage personal expenses.
发布评论
评论(3)
坏消息:这个问题是 NP 困难的,因为子集和减少了。给定数字 x1, …, xn, S,子集和的目的是确定 xi 是否存在某个子集s 总和为 S。我们制作容量为 x1, …, xn 的 A 瓶,以及容量为 S 和 (x1 的 B 瓶> + … + xn - S) 并确定 n 次浇注是否足够。
好消息:任何贪婪策略(即,选择任何非空 A,选择任何未填充的 B,倾倒直到我们必须停止)都是 2 近似(即,最多使用最佳倾倒次数的两倍)。最优解至少使用 max(|A|, |B|) 次,而贪婪则最多使用 |A| + |B|,因为贪婪每次倾倒时,要么 A 被排空,要么 B 被填充,并且不需要再次倒出或倒入。
可能有一个近似方案(a (1 + ε)-对任何 ε > 0 的近似)。我认为现在更有可能存在不可近似结果 - 获得近似方案的常用技巧并不适用似乎不适用这里。以下是一些可能会产生实用的精确算法的想法。
给定一个解决方案,绘制一个二分图,其左顶点
A
和右顶点B
以及从a
到b 的(无向)边
当且仅当将a
倒入b
中时。如果解决方案是最佳的,我认为没有循环——否则我们可以消除循环中的最小倾注并补充循环中损失的体积。例如,如果我倒了,那么我可以通过
a1 ->; 来消除。 b1
像这样浇注:现在,由于该图没有循环,我们可以将边(浇注)的数量计算为
|A| + |B| - #(连接的组件)
。这里唯一的变量是连接组件的数量,我们希望最大化它。我声称贪心算法形成的图没有循环。如果我们知道最优解的连通分量是什么,我们就可以对每个分量使用贪心算法并获得最优解。
解决这个子问题的一种方法是使用动态编程枚举 A 的所有子集对 X 和 B 的 Y ,使得 sum(X) == sum(Y) ,然后将它们输入精确覆盖算法。这两个步骤当然都是指数级的,但它们可能适用于真实数据。
Bad news: this problem is NP-hard by a reduction from subset sum. Given numbers x1, …, xn, S, the object of subset sum is to determine whether or not some subset of the xis sum to S. We make A-bottles with capacities x1, …, xn and B-bottles with capacities S and (x1 + … + xn - S) and determine whether n pours are sufficient.
Good news: any greedy strategy (i.e., choose any nonempty A, choose any unfilled B, pour until we have to stop) is a 2-approximation (i.e., uses at most twice as many pours as optimal). The optimal solution uses at least max(|A|, |B|) pours, and greedy uses at most |A| + |B|, since every time greedy does a pour, either an A is drained or a B is filled and does not need to be poured out of or into again.
There might be an approximation scheme (a (1 + ε)-approximation for any ε > 0).I think now it's more likely that there's an inapproximability result – the usual tricks for obtaining approximation schemes don't seem to apply here.Here are some ideas that might lead to a practical exact algorithm.
Given a solution, draw a bipartite graph with left vertices
A
and right verticesB
and an (undirected) edge froma
tob
if and only ifa
is poured intob
. If the solution is optimal, I claim that there are no cycles – otherwise we could eliminate the smallest pour in the cycle and replace the lost volume going around the cycle. For example, if I have poursthen I can eliminate by
a1 -> b1
pour like so:Now, since the graph has no cycle, we can count the number of edges (pours) as
|A| + |B| - #(connected components)
. The only variable here is the number of connected components, which we want to maximize.I claim that the greedy algorithm forms graphs that have no cycle. If we knew what the connected components of an optimal solution were, we could use a greedy algorithm on each one and get an optimal solution.
One way to tackle this subproblem would be to use dynamic programming to enumerate all subset pairs X of A and Y of B such that sum(X) == sum(Y) and then feed these into an exact cover algorithm. Both steps are of course exponential, but they might work well on real data.
我的看法是:
更新:在步骤 2 中每次浇注后,重复步骤 1。(史蒂夫·杰索普)。冲洗并重复直至所有水都被转移。
Here's my take:
Update: After each pour in step 2, repeat step 1. (Optimization step suggested by Steve Jessop). Rinse and repeat until all water is transferred.
我认为这给出了最小的倒数:
用英语它读作:
sum(A) > sum(B)
或sum(A) is true)
在 A 不为空且 B 不为空时对它们进行排序:
i think this gives the minimum number of pours:
in English it reads:
sum(A) > sum(B)
orsum(A) < sum(B)
is true)while A isn't empty and B isn't empty: