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

时间:2011-02-27 13:40:31

标签: algorithm computer-science greedy

好的,我有一个问题。我有一套各种尺寸的瓶装“A”,里面装满了水。 然后我又买了一套各种尺寸的“B”瓶,都是空的。

我想将水从A转移到B,知道每组的总容量是相同的。 (即:A组含有与B组相同的水量)。

这当然是微不足道的,只需拿B中的第一个瓶子,倒入A中的第一个瓶子直到它满了。然后,如果B中的瓶子中还有水,请继续使用A中的第二个瓶子等

但是,我希望尽量减少灌注总量(从一个瓶子倒入另一个瓶子的动作,每个动作计数1,与其涉及的水量无关)

我想找到一个贪婪的算法来做到这一点,或者如果不可能,至少是一个有效的算法。然而,效率是算法正确性的次要因素(我不想要一个次优的解决方案)。

当然,这个问题只是计算机程序中管理个人开支的真实问题的隐喻。

3 个答案:

答案 0 :(得分:8)

坏消息:通过减少子集和来解决这个问题。给定数字x 1 ,...,x n ,S,子集和的对象是确定x i 的某个子集总和为S.我们生产容量为x 1 ,...,x n 的A瓶和容量为S且(x 1 + ... + x n - S)并确定n次浇注是否足够。

好消息:任何贪婪的策略(即选择任何非空的A,选择任何未填充的B,倾倒直到我们必须停止)是2近似(即,最多使用两倍于最佳的倾倒)。最优解决方案至少使用max(| A |,| B |)pours,而greedy最多使用| A | + | B |,因为每当贪婪倾倒时,无论是A排水还是B充满,都不需要倒出或倒入。

可能有一个近似方案(任何ε> 0的a(1 +ε) - 近似。)我认为现在更有可能出现不可接近性结果 - 通常的获取技巧近似方案似乎不适用于此。


以下是一些可能导致实用精确算法的想法。

给定一个解决方案,绘制一个左顶点A和右顶点B以及ab的(无向)边的二分图,当且仅当{ {1}}被填入a。如果解决方案是最佳的,我声称没有循环 - 否则我们可以消除循环中最小的灌注并替换循环周围的丢失体积。例如,如果我已经倾倒

b

然后我可以像a1 -> b1: 1 a1 -> b2: 2 a2 -> b1: 3 a2 -> b3: 4 a3 -> b2: 5 a3 -> b3: 6 一样消灭:

a1 -> b1

现在,由于图表没有周期,我们可以将边数(pours)计算为a2 -> b1: 4 (+1) a2 -> b3: 3 (-1) a3 -> b3: 7 (+1) a3 -> b2: 4 (-1) a1 -> b2: 3 (+1) 。这里唯一的变量是连接组件的数量,我们希望最大化。

我声称贪婪算法形成没有循环的图形。如果我们知道最佳解决方案的连接组件是什么,我们可以在每个组件上使用贪婪算法并获得最佳解决方案。

解决这个子问题的一种方法是使用动态编程来枚举B的A和Y的所有子集对X,使得sum(X)== sum(Y),然后将它们馈送到精确的覆盖算法中。这两个步骤当然都是指数级的,但它们可能在真实数据上运行良好。

答案 1 :(得分:3)

这是我的看法:

  1. 识别两组中具有完全相同尺寸的瓶子。这转化为这些相同尺寸瓶子的一对一倾倒。
  2. 按容量降序排列A中剩余的瓶子,然后按升序对B中剩余的瓶子进行分类。计算在A到B中倾倒排序列表时所需的浇筑次数。
  3. 更新:在步骤2中每次浇筑后,重复步骤1.(Steve Jessop建议的优化步骤)。冲洗并重复,直到所有的水都被转移。

答案 2 :(得分:0)

我认为这给出了最小的注意次数:

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) < sum(B)为真,算法仍然有效)
  • 取两个列表A和B,对它们进行排序

当A不为空且B不为空时:

  • 从A(j)中取i(最大)来自B
  • 如果我等于j,倒入j并计算1倒
  • 如果i大于j,请将i倒入j,将i-j余数放回A(使用插入排序),计数1倒
  • 如果我小于j,倒入j,将j-i余数放回B(使用插入排序),计数1倒