Эта задача является NP-полной, поскольку она представляет собой комбинацию из двух NP-полных задач:
- поиск одного подмножества с суммой
0
называется суммой подмножества Задача - Когда вы найдете все подмножества, сумма которых составляет
0
, вы должны решить задачу точного покрытия со специальным условием: вы хотите максимизировать количество подмножеств .
Следующие шаги обеспечат решение:
Несколько замечаний:
Во-первых, мы знаем, что существует точное покрытие, потому что список число имеет сумму 0.
Во-вторых, мы можем использовать только те подмножества, которые не являются надмножествами других подмножеств. Потому что, если A
является надмножеством X
(сумма равна 0
), A
не может быть в обложке с наибольшим количеством подмножеств. Пусть A
, B
, C
, ... будет обложкой с максимальным числом подмножеств, тогда мы можем заменить A
на X
и A\X
(легко увидеть, что сумма из A\X
элементов 0
) и мы получаем покрытие X
, A\X
, B
, C
, ... что лучше.
В-третьих, когда мы используем алгоритм X, все пути в дереве поиска приведут к успеху. Пусть A
, B
, C
, ... будут путями, состоящими из непересекающихся подмножеств, каждая из которых имеет сумму 0
. Тогда у дополнения также есть сумма 0
(которая может быть надмножеством другого подмножества, и тогда мы будем использовать 2.).
Как видите, ничего нового здесь , и я буду использовать только хорошо известные методы / алгоритмы.
Найти подмножества, имеющие сумму 0
.
Алгоритм хорошо известен. Вот реализация Python, основанная на объяснениях Википедии
class Q:
def __init__(self, values):
self.len = len(values)
self.min = sum(e for e in values if e <= 0)
self.max = sum(e for e in values if e >= 0)
self._arr = [False] * self.len * (self.max - self.min + 1)
def __getitem__(self, item):
index, v = item
return self._arr[v * self.len + index]
def __setitem__(self, item, value):
index, v = item
self._arr[v * self.len + index] = value
class SubsetSum:
def __init__(self, values):
self._values = values
self._q = Q(values)
def prepare(self):
for s in range(self._q.min, self._q.max + 1):
self._q[0, s] = (self._values[0] == s)
for i in range(self._q.len):
self._q[i, 0] = True
for i in range(1, self._q.len):
v = self._values[i]
for s in range(self._q.min, self._q.max + 1):
self._q[i, s] = (v == s) or self._q[i - 1, s] or self._q[
i - 1, s - v]
def subsets(self, target=0):
yield from self._subsets(self._q.len - 1, target, [])
def _subsets(self, i, target, p):
assert i >= 0
v = self._values[i]
c = self._q[i - 1, target]
b = self._q[i - 1, target - v]
if i == 0:
if target == 0:
if p:
yield p
elif self._q[0, target]:
yield p + [i]
else:
if self._q.min <= target - v <= self._q.max and self._q[
i - 1, target - v]:
yield from self._subsets(i - 1, target - v, p + [i])
if self._q[i - 1, target]:
yield from self._subsets(i - 1, target, p)
Вот как это работает:
arr = [-10, 1, 2, 20, 5, -100, -80, 10, 15, 15, 60, 100, -20, -18]
arr = sorted(arr)
s = SubsetSum(arr)
s.prepare()
subsets0 = list(s.subsets())
print(subsets0)
Вывод:
[[13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0], [13, 12, 11, 10, 9, 7, 6, 5, 3, 2, 1, 0], [13, 12, 11, 10, 9, 4, 2, 1, 0], [13, 12, 11, 10, 8, 7, 4, 2, 1, 0], [13, 12, 11, 10, 8, 6, 5, 4, 3, 1, 0], [13, 12, 11, 10, 7, 2, 1, 0], [13, 12, 11, 10, 6, 5, 3, 1, 0], [13, 12, 11, 9, 8, 7, 4, 2, 1, 0], [13, 12, 11, 9, 8, 6, 5, 4, 3, 1, 0], [13, 12, 11, 9, 7, 2, 1, 0], [13, 12, 11, 9, 6, 5, 3, 1, 0], [13, 12, 11, 8, 7, 6, 5, 3, 1, 0], [13, 12, 11, 8, 4, 1, 0], [13, 12, 11, 1, 0], [13, 12, 10, 9, 8, 7, 6, 5, 4, 3, 1, 0], [13, 12, 10, 9, 8, 2, 1, 0], [13, 12, 10, 9, 7, 6, 5, 3, 1, 0], [13, 12, 10, 9, 4, 1, 0], [13, 12, 10, 8, 7, 4, 1, 0], [13, 12, 10, 7, 1, 0], [13, 12, 9, 8, 7, 4, 1, 0], [13, 12, 9, 7, 1, 0], [13, 11, 10, 8, 6, 5, 4, 3, 2, 0], [13, 11, 10, 6, 5, 3, 2, 0], [13, 11, 9, 8, 6, 5, 4, 3, 2, 0], [13, 11, 9, 6, 5, 3, 2, 0], [13, 11, 8, 7, 6, 5, 3, 2, 0], [13, 11, 8, 4, 2, 0], [13, 11, 7, 6, 5, 4, 3, 2, 1], [13, 11, 7, 6, 5, 4, 3, 0], [13, 11, 2, 0], [13, 10, 9, 8, 7, 6, 5, 4, 3, 2, 0], [13, 10, 9, 7, 6, 5, 3, 2, 0], [13, 10, 9, 4, 2, 0], [13, 10, 8, 7, 4, 2, 0], [13, 10, 8, 6, 5, 4, 3, 2, 1], [13, 10, 8, 6, 5, 4, 3, 0], [13, 10, 7, 2, 0], [13, 10, 6, 5, 3, 2, 1], [13, 10, 6, 5, 3, 0], [13, 9, 8, 7, 4, 2, 0], [13, 9, 8, 6, 5, 4, 3, 2, 1], [13, 9, 8, 6, 5, 4, 3, 0], [13, 9, 7, 2, 0], [13, 9, 6, 5, 3, 2, 1], [13, 9, 6, 5, 3, 0], [13, 8, 7, 6, 5, 3, 2, 1], [13, 8, 7, 6, 5, 3, 0], [13, 8, 4, 2, 1], [13, 8, 4, 0], [13, 7, 6, 5, 4, 3, 1], [13, 2, 1], [13, 0], [12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1], [12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 0], [12, 11, 10, 9, 8, 2, 0], [12, 11, 10, 9, 7, 6, 5, 3, 2, 1], [12, 11, 10, 9, 7, 6, 5, 3, 0], [12, 11, 10, 9, 4, 2, 1], [12, 11, 10, 9, 4, 0], [12, 11, 10, 8, 7, 4, 2, 1], [12, 11, 10, 8, 7, 4, 0], [12, 11, 10, 8, 6, 5, 4, 3, 1], [12, 11, 10, 7, 2, 1], [12, 11, 10, 7, 0], [12, 11, 10, 6, 5, 3, 1], [12, 11, 9, 8, 7, 4, 2, 1], [12, 11, 9, 8, 7, 4, 0], [12, 11, 9, 8, 6, 5, 4, 3, 1], [12, 11, 9, 7, 2, 1], [12, 11, 9, 7, 0], [12, 11, 9, 6, 5, 3, 1], [12, 11, 8, 7, 6, 5, 3, 1], [12, 11, 8, 4, 1], [12, 11, 1], [12, 10, 9, 8, 7, 6, 5, 4, 3, 1], [12, 10, 9, 8, 2, 1], [12, 10, 9, 8, 0], [12, 10, 9, 7, 6, 5, 3, 1], [12, 10, 9, 4, 1], [12, 10, 8, 7, 4, 1], [12, 10, 7, 1], [12, 9, 8, 7, 4, 1], [12, 9, 7, 1], [11, 10, 8, 6, 5, 4, 3, 2], [11, 10, 6, 5, 3, 2], [11, 9, 8, 6, 5, 4, 3, 2], [11, 9, 6, 5, 3, 2], [11, 8, 7, 6, 5, 3, 2], [11, 8, 4, 2], [11, 7, 6, 5, 4, 3], [11, 2], [10, 9, 8, 7, 6, 5, 4, 3, 2], [10, 9, 7, 6, 5, 3, 2], [10, 9, 4, 2], [10, 8, 7, 4, 2], [10, 8, 6, 5, 4, 3], [10, 7, 2], [10, 6, 5, 3], [9, 8, 7, 4, 2], [9, 8, 6, 5, 4, 3], [9, 7, 2], [9, 6, 5, 3], [8, 7, 6, 5, 3], [8, 4]]
Уменьшите количество подмножеств
У нас есть 105 подмножеств, которые составляют 0
, но мы можем удалить подмножества, которые являются надмножеством других подмножеств. Нам нужна функция, чтобы определить, содержит ли список элементов все элементы другого списка. В Python:
import collections
def contains(l1, l2):
"""
Does l1 contain all elements of l2?
"""
c = collections.Counter(l1)
for e in l2:
c[e] -= 1
return all(n >= 0 for n in c.values())
Теперь мы можем удалить подмножества, которые являются надмножествами другого подмножества.
def remove_supersets(subsets):
subsets = sorted(subsets, key=len)
new_subsets = []
for i, s1 in enumerate(subsets):
for s2 in subsets[:i]: # smaller subsets
if contains(s1, s2):
break
else: # not a superset
new_subsets.append(s1)
return new_subsets
В нашей ситуации:
subsets0 = remove_supersets(subsets0)
print(len(subsets0))
Вывод:
[[13, 0], [11, 2], [8, 4], [13, 2, 1], [12, 11, 1], [10, 7, 2], [9, 7, 2], [12, 10, 7, 1], [12, 9, 7, 1], [10, 9, 4, 2], [10, 6, 5, 3], [9, 6, 5, 3], [12, 11, 10, 7, 0], [12, 11, 9, 7, 0], [12, 10, 9, 8, 0], [12, 10, 9, 4, 1], [8, 7, 6, 5, 3], [12, 11, 10, 9, 4, 0], [12, 10, 9, 8, 2, 1], [11, 7, 6, 5, 4, 3], [13, 7, 6, 5, 4, 3, 1]]
[[0, 2, 10, 6, 4], [0, 2, 10, 8, 1], [0, 2, 11, 5, 4], [0, 2, 11, 7, 1], [0, 16, 9, 4], [0, 16, 15, 1], [0, 18, 19], [3, 2, 12, 11], [3, 2, 13, 10], [3, 17, 16], [3, 19, 14], [20, 14, 1]]
Нам удалось сократить количество подмножеств до 21, что является хорошим улучшением, поскольку нам нужно изучить все возможности для нахождения точного покрытия.
Алгоритм X
Я не использую здесь ссылки для танцев (я думаю, что эта техника предназначена для языков низкого уровня, таких как C, но вы можете реализовать их в Python, если хотите). Нам просто нужно отслеживать оставшиеся подмножества:
class Matrix:
def __init__(self, subsets, ignore_indices=set()):
self._subsets = subsets
self._ignore_indices = ignore_indices
def subset_values(self, i):
assert i not in self._ignore_indices
return self._subsets[i]
def value_subsets_indices(self, j):
return [i for i, s in self._subsets_generator() if j in s]
def _subsets_generator(self):
return ((i, s) for i, s in enumerate(self._subsets) if
i not in self._ignore_indices)
def rarest_value(self):
c = collections.Counter(
j for _, s in self._subsets_generator() for j in s)
return c.most_common()[-1][0]
def take_subset(self, i):
s = self._subsets[i]
to_ignore = {i2 for i2, s2 in self._subsets_generator() if
set(s2) & set(s)}
return Matrix(self._subsets,
self._ignore_indices | to_ignore)
def __bool__(self):
return bool(list(self._subsets_generator()))
И, наконец, функцию cover
:
def cover(m, t=[]):
if m: # m is not empty
j = m.rarest_value()
for i in m.value_subsets_indices(j):
m2 = m.take_subset(i)
yield from cover(m2, t + [i])
else:
yield t
Наконец, мы имеем:
m = Matrix(subsets0)
ts = list(cover(m))
t = max(ts, key=len)
print([[arr[j] for j in subsets0[i]] for i in t])
Вывод:
[[100, -100], [10, -10], [15, 2, 1, -18], [15, 5, -20], [60, 20, -80]]