Алгоритм "переносить воду из набора бутылок в другую" (метафорически)
Хорошо, у меня проблема. У меня есть набор "А" из бутылок разных размеров, полный воды.
Тогда у меня есть другой набор "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