Алгоритм "переносить воду из набора бутылок в другую" (метафорически)

Хорошо, у меня проблема. У меня есть набор "А" из бутылок разных размеров, полный воды. Тогда у меня есть другой набор "B" бутылок разных размеров, все пустые.

Я хочу передать воду от А до Б, зная, что общая емкость каждого набора одинакова. (т.е.: в наборе А содержится такое же количество воды, как установлено B).

Это, конечно, тривиально само по себе, просто возьмите первую бутылку в B, вылейте ее в первом в A, пока она не будет заполнена. Тогда, если бутылка из B еще остается в ней, продолжайте со второй бутылкой в ​​A и т.д.

Однако я хочу свести к минимуму общее количество ливней (действие наливания из бутылки в другое, каждое действие считается 1, независимо от того, сколько воды оно включает)

Я хотел бы найти жадный алгоритм, чтобы сделать это, или, если не возможно, по крайней мере, эффективный. Однако эффективность является вторичной по отношению к правильности алгоритма (я не хочу субоптимальное решение).

Конечно, эта проблема - всего лишь метафора реальной проблемы в компьютерной программе для управления личными расходами.

Ответ 1

Плохая новость: эта проблема NP-жесткая по сокращению от суммы подмножества. Приведенные числа x 1,..., x n, S, объект подмножества sum должен определить, будет ли какое-либо подмножество x i s суммой к S. Мы делаем A-бутылки емкостью x 1,..., x n и B-бутылки с емкостями S и (x 1 +... + x n - S) и определить, достаточны ли n разливов.

Хорошие новости: любая жадная стратегия (т.е. выберите любой непустой A, выберите любой незаполненный B, вылейте до тех пор, пока мы не остановимся) является 2-приближением (т.е. использует не более двух раз больше, чем количество оптимальных). Оптимальное решение использует как минимум max (| A |, | B |), а жадные используют не более | A | + | B |, так как каждый раз, когда жадный ливает листы, либо A сливается, либо заполняется B, и его не нужно изливать или снова.

Может быть схема аппроксимации (a (1 + ε) -приближение для любого ε > 0). Я думаю, теперь более вероятно, что результат недобросовестности - обычные трюки для получения аппроксимации схемы, похоже, не применяются здесь.


Вот некоторые идеи, которые могут привести к практическому точному алгоритму.

Для решения возьмите двудольный граф с левыми вершинами A и правыми вершинами B и (неориентированным) ребром от A до B тогда и только тогда, когда 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| - #(connected components). Единственная переменная здесь - это количество подключенных компонентов, которые мы хотим максимизировать.

Я утверждаю, что жадный алгоритм формирует графики, которые не имеют цикла. Если бы мы знали, какими являются связанные компоненты оптимального решения, мы могли бы использовать жадный алгоритм на каждом из них и получить оптимальное решение.

Одним из способов решения этой подзадачи было бы использовать динамическое программирование для перечисления всех пар подмножеств X из A и Y из B, таких, что sum (X) == sum (Y), а затем подавать их в алгоритм точного покрытия. Оба этапа, конечно, экспоненциальны, но они могут хорошо работать с реальными данными.

Ответ 2

Вот мой прием:

  • Определите бутылки с одинаковым размером в обоих наборах. Это перевести на один-на-один для этих бутылок такого же размера.
  • Отсортируйте оставшиеся бутылки в в порядке убывания по емкости и отсортируйте оставшиеся бутылки в B в порядке возрастания. Вычислите количество разливов, которое вам нужно, когда вы выливаете отсортированный список в A-B.

Обновление:. После каждого заливки на шаге 2 повторите шаг 1. (Шаг оптимизации, предложенный Стив Джессоп). Промыть и повторить до тех пор, пока не будет перенесена вся вода.

Ответ 3

Я думаю, что это дает минимальное количество ливней:

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 (наибольший) из B
  • если я равно j, вылить я в j и считать 1 pour
  • если я больше j, введите я в j, поместите i-j остаток назад в (используя сортировку вставки), count 1 pour
  • если я меньше j, вылейте я в j, поместите j-i остаток назад в B (используя сортировку вставки), count 1 pour