“幻方”由n x n矩阵组成,其中行,列和对角线均等于常数。对于4 x 4幻方,该常数为34。我正在尝试优化我的置换算法,该算法当前使用1-16的数字列出所有可能的4 x 4矩阵,如果当前矩阵不满足,则跳过某些置换。不符合魔方标准。
我已经有了递归算法来置换所有组合。此函数采用长度为16的数组(代表正方形),并打印符合“魔术”条件的所有可能组合。我不确定如何在递归调用中实现检查以优化它。例如,我想要这样,以便如果矩阵的第一行未总计为34,则跳过该排列并继续进行下一个排列(对于后续行,依此类推)。
def permute(a, lo, hi):
if(lo == hi) and (isMagic(a)):
print(a)
else:
for i in range(lo, hi):
# this is where I imagine the exceptions would be made
a[lo], a[i] = a[i], a[lo]
permute4(a, lo + 1, hi, count, n)
a[lo], a[i] = a[i], a[lo]
取消“ isMagic”检查(仅打印所有组合,包括那些不是“ magic”的组合)时,该函数需要花费很长时间才能打印正方形。我最终希望通过排除不必要的排列来加快此过程。我将如何实施这项检查?
答案 0 :(得分:1)
诀窍是依靠已经生成的单元格。例如,您无需生成4元素的置换,因为您知道它们的总和为34,因此即使在第一行中的第4个元素也必须为34-sum(first 3 elements)
。如果此类元素从不存在(如1,2,3,28
)或已被使用(如1,3,15,15
),则该尝试将不起作用,您可以继续进行下一个而不生成表的其余部分。实际上,前两个元素已经可以为其余元素创建一个上限/下限,例如1,2
意味着其余两个元素将是15
和16
。可以预先生成/过滤总和为34的所有单个排列,然后根据平方中已经存在的内容选择候选对象。
与此相关的另一个技巧是:如果您始终生成行,那么您总是会尝试很多事情,例如3 + 1全新元素。但是,如果在放置一行后生成一列,则仅处理2元素置换(因为第一个元素已知,最后一个元素已计算出)。
示例实现(无预生成):
import itertools,time
def check(attempt):
for i in range(0,4):
if sum(attempt[i*4:i*4+4]) != 34:
return False
if sum(attempt[i+j*4] for j in range(0,4)) != 34:
return False
if sum(attempt[i+i*4] for i in range(0,4)) != 34:
return False
if sum(attempt[3-i+i*4] for i in range(0,4)) != 34:
return False
return True
def row(pos,field,rest):
base=34-sum(field[pos*4:pos*4+pos])
for p in itertools.permutations(rest,3-pos):
r=base-sum(p)
s=rest-set(p)
if r in s:
for i in range(pos,3):
field[pos*4+i]=p[i-pos]
field[pos*4+3]=r
column(pos,field,s-{r})
count = 0
def column(pos,field,rest):
if len(rest) == 0:
if check(field):
global count
count+=1
print("{} {}".format(count,field))
return
base=34-sum([field[pos+4*i] for i in range(0,pos+1)])
for p in itertools.permutations(rest,2-pos):
r=base-sum(p)
s=rest-set(p)
if r in s:
for i in range(pos+1,3):
field[pos+i*4]=p[i-pos-1]
field[pos+4*3]=r
row(pos+1,field,s-{r})
start=time.time()
row(0,[0]*16,set(range(1,17)))
print(time.time()-start)
它在1-2分钟内生成7040解决方案(在我的计算机上为61秒,但这是一个相对较新的解决方案)。可能是正确的,因为期望使用880唯一的解决方案,但是此代码也会生成旋转和镜像的正方形(7040 = 8 * 880)。
实际上,check()
的实现过于谨慎:由于生成方法的原因,它足以检查对角线(可以删除带有两个for
-s的if
循环)。>