“将水从一组瓶子转移到另一组瓶子”的算法(比喻地讲)

发布于 2024-10-19 23:37:19 字数 384 浏览 4 评论 0 原文

好吧,我有问题。我有一组“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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

凉栀 2024-10-26 23:37:19

坏消息:这个问题是 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 以及从 ab 的(无向)边 当且仅当将a 倒入b 中时。如果解决方案是最佳的,我认为没有循环——否则我们可以消除循环中的最小倾注并补充循环中损失的体积。例如,如果我倒了

a1 -> b1: 1
a1 -> b2: 2
a2 -> b1: 3
a2 -> b3: 4
a3 -> b2: 5
a3 -> b3: 6

,那么我可以通过 a1 ->; 来消除。 b1 像这样浇注:

a2 -> b1: 4 (+1)
a2 -> b3: 3 (-1)
a3 -> b3: 7 (+1)
a3 -> b2: 4 (-1)
a1 -> b2: 3 (+1)

现在,由于该图没有循环,我们可以将边(浇注)的数量计算为 |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 vertices B and an (undirected) edge from a to b if and only if a is poured into b. 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 pours

a1 -> b1: 1
a1 -> b2: 2
a2 -> b1: 3
a2 -> b3: 4
a3 -> b2: 5
a3 -> b3: 6

then I can eliminate by a1 -> b1 pour like so:

a2 -> b1: 4 (+1)
a2 -> b3: 3 (-1)
a3 -> b3: 7 (+1)
a3 -> b2: 4 (-1)
a1 -> b2: 3 (+1)

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.

幸福不弃 2024-10-26 23:37:19

我的看法是:

  1. 找出两组中尺寸完全相同的瓶子。这意味着对这些相同尺寸的瓶子进行一对一的倾倒。
  2. 将 A 中剩余的瓶子按容量降序排序,将 B 中剩余的瓶子按容量升序排序。计算将 A 中的排序列表浇注到 B 时所需的浇注次数。

更新:在步骤 2 中每次浇注后,重复步骤 1。(史蒂夫·杰索普)。冲洗并重复直至所有水都被转移。

Here's my take:

  1. Identify bottles having the exact same size in both sets. This translate to one-to-one pour for these same-size bottles.
  2. Sort the remaining bottles in A in descending order by capacity, and sort remaining bottles in B in ascending order. Compute the number of pours you need when pouring sorted list in A to B.

Update: After each pour in step 2, repeat step 1. (Optimization step suggested by Steve Jessop). Rinse and repeat until all water is transferred.

下雨或天晴 2024-10-26 23:37:19

我认为这给出了最小的倒数:

import bisect

def pours(A, B):
    assert sum(A) == sum(B)
    count = 0
    A.sort()
    B.sort()
    while A and B:
        i = A.pop()
        j = B.pop()
        if i == j:
            count += 1
        elif i > j:
            bisect.insort(A, i-j)
            count += 1
        elif i < j:
            bisect.insort(B, j-i)
            count += 1
    return count

A=[5,4]
B=[4,4,1]
print pours(A,B)
# gives 3

A=[5,3,2,1] 
B=[4,3,2,1,1]
print pours(A,B)
# gives 5

用英语它读作:

  • 断言两个列表具有相同的总和(我认为如果 sum(A) > sum(B)sum(A) is true)
  • 取两个列表 A 和 B,

在 A 不为空且 B 不为空时对它们进行排序:

  • 取 i(最大的)从 A 和 j(最大的)从 B
  • 如果 i 等于 j,将 i 倒入 j 并计数 1
  • 如果 i 大于 j,将 i 倒入 j,将 ij 余数放回 A(使用插入排序),计数 1 pour
  • 如果 i 小于 j,则将 i 倒入 j,将 ji 余数放回到 B 中(使用插入排序),计数 1 pour

i think this gives the minimum number of pours:

import bisect

def pours(A, B):
    assert sum(A) == sum(B)
    count = 0
    A.sort()
    B.sort()
    while A and B:
        i = A.pop()
        j = B.pop()
        if i == j:
            count += 1
        elif i > j:
            bisect.insort(A, i-j)
            count += 1
        elif i < j:
            bisect.insort(B, j-i)
            count += 1
    return count

A=[5,4]
B=[4,4,1]
print pours(A,B)
# gives 3

A=[5,3,2,1] 
B=[4,3,2,1,1]
print pours(A,B)
# gives 5

in English it reads:

  • assert that both lists have the same sum (i think the algorithm will still work if sum(A) > sum(B) or sum(A) < sum(B) is true)
  • take the two lists A and B, sort both them

while A isn't empty and B isn't empty:

  • take i (the largest) from A and j (the largest) from B
  • if i equals j, pour i in j and count 1 pour
  • if i is larger than j, pour i in j, place i-j remainder back in A (using an insertion sort), count 1 pour
  • if i is smaller than j, pour i in j, place j-i remainder back in B (using an insertion sort), count 1 pour
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文