这绝对有一种 NP-hard 的感觉。特别是是否可以进行 2 个分区与 1 个分区的问题是一个问题,即除最后一个之外的所有元素的适当子集是否加起来是最后一个的负数,这是子集和问题的一个变体。因此,即使通过拆分其中一个分区来验证无法进一步改进答案也可能是 NP 完全的!
但是在实践中如何解决这个问题?
第 1 步是生成一个数据结构,表示所有可能的分区,包括总和为 0 的第一个元素。这可以像标准子集总和算法一样解决。有了一个双向链表的想法,我们可以从中获取信息。 (双向链表通常与您的数组大小乘以找到的不同和的数量成正比,但在解码时,它可能会产生指数级的分区数。
双向链表会让你头晕目眩,所以你可能想使用如下语法糖:
from collections import namedtuple
Node = namedtuple('Node', ['ind', 'prev'])
Paths = namedtuple('Paths', ['node', 'prev'])
ind 是数组的索引,node 始终是 Node,prev 始终是 Paths 或 None。
现在Node 表示“在以下先前选择中包含此索引和任何有效路径”。而Paths 则表示“这是一个通向这里的节点,还有一条通往其他方式的先前路径。”
有了这个,我们可以做到以下几点:
# 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 时提前终止。 (这种记账可能感觉像是很多额外的工作,但它实际上会为您节省更多。)
现在您可以递归地找到第一个元素的最小分区。然后递归寻找如何划分剩余元素。每次你这样做时,你都会与你目前拥有的最好的进行比较,如果是改进,请记录下来。
当你完成了所有分解它的方法时,你就完成了。
假设整数数组(因此子集和伪多项式可能对您有利),这将使您能够相当有效地在最小分区上进行递归。与幼稚的方法相比,这是一种更好的递归爆炸。但它会比你想象的要大很多。
我怀疑第一步以递增的绝对值对数组进行排序将使该算法更有效,因为当您仍然有很多元素时,“不可约划分的过滤器”可能会提前脱离递归。