这绝对有一种NP难的感觉。特别是是否可以进行 2 个分区与 1 个分区的问题是一个问题,即除最后一个之外的所有元素的适当子集是否加起来是最后一个的负数,这是子集和问题的一个变体。因此,即使通过拆分其中一个分区来验证无法进一步改进答案也可能是 NP 完全的!
但在实践中如何解决这个问题?
第 1 步是生成一个数据结构,表示所有可能的分区,包括总和为 0 的第一个元素。这可以像标准子集总和算法一样解决。有了一个双向链表的想法,我们可以从中获取信息。(双向链表通常与数组的大小乘以找到的不同和的数量成正比,但在解码时,它可能会导致指数级的分区数。
双向链表会让您头晕目眩,因此您可能希望使用如下语法糖:
from collections import namedtuple
Node = namedtuple('Node', ['ind', 'prev'])
Paths = namedtuple('Paths', ['node', 'prev'])
ind
数组的索引在哪里,node
始终是 a Node
,并且prev
始终是 a Paths
or None
。
现在 aNode
说“在以下先前的选择中包含此索引和任何有效路径”。并且 aPaths
说“这是一个通向这里的节点,还有一条通往其他方式的先前路径。”
有了这个,我们可以做到以下几点:
# We only want paths that take the 0th index.
ways_to_get_to_n = {elements[0], Paths(Node(0, None), None)}
for i in range(1, len(elements)):
element = elements[i]
new_ways_to_get_n = {}
for value, ways in ways_to_get_n.items():
new_ways_to_get_n[value] = ways
for value, ways in ways_to_get_n.items():
if value + element not in new_ways_to_get_n:
new_ways_to_get_n[value + element] = Paths(Node(i, ways), None)
else:
new_ways_to_get_n[value + element] = Paths(Node(i, ways), new_ways_to_get_n[value + element])
ways_to_get_n = new_ways_to_get_n
完成后,ways_to_get_n[0]
这是一个相当简洁的数据结构,可以使用双递归遍历包含第一个元素的所有分区。然而,有一个挑战。这些分区内部可能有 0 个分区。因此,当您沿着双递归进行时,请携带您可以达到的所有值的数据结构(相同的旧子集求和技巧),并在出现 0 时提前终止。(这种簿记可能感觉像是很多额外的工作,但它实际上会为您节省更多。)
现在您可以递归地找到具有第一个元素的最小分区。然后递归寻找如何划分剩余元素。每次你这样做时,你都会与你目前拥有的最好的进行比较,如果它是一种改进,请记录下来。
当你通过各种方式分解它时,你就完成了。
假设整数数组(因此子集和伪多项式可能对您有利),这将使您能够相当有效地在最小分区上进行递归。与幼稚的方法相比,这是一种更好的递归爆炸。但它会比你想象的要大得多。
我怀疑作为第一步以递增的绝对值对数组进行排序将使该算法更有效,因为当您仍然有很多元素时,“不可约分区的过滤器”可能会提前脱离递归。