如何为所有4x4“魔方”优化递归置换算法

时间:2019-03-30 20:48:33

标签: python

“幻方”由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”的组合)时,该函数需要花费很长时间才能打印正方形。我最终希望通过排除不必要的排列来加快此过程。我将如何实施这项检查?

1 个答案:

答案 0 :(得分:1)

诀窍是依靠已经生成的单元格。例如,您无需生成4元素的置换,因为您知道它们的总和为34,因此即使在第一行中的第4个元素也必须为34-sum(first 3 elements)。如果此类元素从不存在(如1,2,3,28)或已被使用(如1,3,15,15),则该尝试将不起作用,您可以继续进行下一个而不生成表的其余部分。实际上,前两个元素已经可以为其余元素创建一个上限/下限,例如1,2意味着其余两个元素将是1516。可以预先生成/过滤总和为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循环)。