这是constraint satisfaction problem的一种;有 16 个变量,每个变量都具有相同的域,8 个关于它们的总和的约束,以及一个约束,它们都应该具有与域不同的值。
可能存在大量的解决方案,因此任何生成更大的候选集然后检查哪些候选确实是解决方案的算法在很大程度上可能是低效的,因为真正的解决方案的比例可能非常低你的候选人。 backtracking search 通常更好,因为它允许部分候选者在违反任何约束时被拒绝,可能会消除许多完整的候选者,而不必首先生成它们。
您可以使用现有的约束求解器,例如python-constraint library,而不是编写自己的回溯搜索算法。这是一个例子:
numbers = [11, 16, 18, 19, 61, 66, 68, 69, 81, 86, 88, 89, 91, 96, 98, 99]
target = 264
from constraint import *
problem = Problem()
problem.addVariables(range(16), numbers)
for i in range(4):
# column i
v = [ i + 4*j for j in range(4) ]
problem.addConstraint(ExactSumConstraint(target), v)
# row i
v = [ 4*i + j for j in range(4) ]
problem.addConstraint(ExactSumConstraint(target), v)
problem.addConstraint(AllDifferentConstraint())
例子:
>>> problem.getSolution()
{0: 99, 1: 88, 2: 66, 3: 11, 4: 16, 5: 61, 6: 89, 7: 98, 8: 81, 9: 96, 10: 18, 11: 69, 12: 68, 13: 19, 14: 91, 15: 86}
>>> import itertools
>>> for s in itertools.islice(problem.getSolutionIter(), 10):
... print(s)
...
{0: 99, 1: 68, 2: 81, 3: 16, 4: 66, 5: 91, 6: 18, 7: 89, 8: 88, 9: 19, 10: 96, 11: 61, 12: 11, 13: 86, 14: 69, 15: 98}
{0: 99, 1: 68, 2: 81, 3: 16, 4: 66, 5: 91, 6: 18, 7: 89, 8: 11, 9: 86, 10: 69, 11: 98, 12: 88, 13: 19, 14: 96, 15: 61}
{0: 99, 1: 68, 2: 81, 3: 16, 4: 18, 5: 89, 6: 66, 7: 91, 8: 86, 9: 11, 10: 98, 11: 69, 12: 61, 13: 96, 14: 19, 15: 88}
{0: 99, 1: 68, 2: 81, 3: 16, 4: 18, 5: 89, 6: 66, 7: 91, 8: 61, 9: 96, 10: 19, 11: 88, 12: 86, 13: 11, 14: 98, 15: 69}
{0: 99, 1: 68, 2: 81, 3: 16, 4: 11, 5: 86, 6: 69, 7: 98, 8: 66, 9: 91, 10: 18, 11: 89, 12: 88, 13: 19, 14: 96, 15: 61}
{0: 99, 1: 68, 2: 81, 3: 16, 4: 11, 5: 86, 6: 69, 7: 98, 8: 88, 9: 19, 10: 96, 11: 61, 12: 66, 13: 91, 14: 18, 15: 89}
{0: 99, 1: 68, 2: 81, 3: 16, 4: 61, 5: 96, 6: 19, 7: 88, 8: 18, 9: 89, 10: 66, 11: 91, 12: 86, 13: 11, 14: 98, 15: 69}
{0: 99, 1: 68, 2: 81, 3: 16, 4: 61, 5: 96, 6: 19, 7: 88, 8: 86, 9: 11, 10: 98, 11: 69, 12: 18, 13: 89, 14: 66, 15: 91}
{0: 99, 1: 68, 2: 81, 3: 16, 4: 88, 5: 19, 6: 96, 7: 61, 8: 11, 9: 86, 10: 69, 11: 98, 12: 66, 13: 91, 14: 18, 15: 89}
{0: 99, 1: 68, 2: 81, 3: 16, 4: 88, 5: 19, 6: 96, 7: 61, 8: 66, 9: 91, 10: 18, 11: 89, 12: 11, 13: 86, 14: 69, 15: 98}
这是前十个解决方案。 problem.getSolutions() 方法返回一个包含所有它们的列表,但这需要相当长的时间来运行(在我的机器上大约需要 2 分钟),因为要找到其中的 6,912 个。
一个问题是每个解决方案都有许多对称的对应物;您可以置换行,置换列,并进行转置。可以通过添加更多约束来消除对称性,这样您就可以从每个对称类中获得一个解决方案。这使得搜索更加可行:
# permute rows/cols so that lowest element is in top-left corner
m = min(numbers)
problem.addConstraint(InSetConstraint([m]), [0])
from operator import lt as less_than
for i in range(3):
# permute columns so first row is in order
problem.addConstraint(less_than, [i, i+1])
# permute rows so first column is in order
problem.addConstraint(less_than, [4*i, 4*i + 4])
# break transpose symmetry by requiring grid[0,1] < grid[1,0]
problem.addConstraint(less_than, [1, 4])
这会破坏所有对称性,因此现在它会在大约 0.2 秒内返回 6,912 / (4! * 4! * 2) = 6 个解。