学号sha256(哈希):2a3979ec090637531da9c78f3a1e562a1ef858ec2caec000bf4e0f943124a699
拉丁正交矩阵,已用于国科大算法实验作业,原创烂码,只是个人纪录,别喷我。
模式为先生成,后回溯对比所以复杂度较高。期末时间紧迫没有时间深入探索,先这样,后面再来降低计算复杂度。
#实验
import sys # 导入sys模块
sys.setrecursionlimit(30000) # 将默认的递归深度修改为30000
import copy
ndimen=4#计算维度
a=b=list(range(1,ndimen+1))
import numpy as np
import time
#start = time.time()
start = time.perf_counter()
lnegth=ndimen
test=[]
listqpl = []
#下面为全排列函数
def quanpl(n,begin):#n是需要排序的列表,begin是本次需要排列的头指针(下标),也可以看作是前面已固定的元素个数
"""
以三阶为例[[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 2, 1], [3, 1, 2]]
:param n: 需要排列的阶数
:param begin: 回溯使用的位置标记
:return: 全排列列表
"""
end=len(n)#记录元素个数
i=0
if begin==end: #递归的结束条件,全部都已固定,可以输出
listqpl.append(copy.copy(n))#全局最坑的点,需要浅拷贝,n的父类对象,子对象改变可以被拷贝出去
return listqpl
else:
i=begin #指向本次需要排列的第一个位置(本轮需要固定的位置)
for num in range(begin,end):#num遍历本次需要排列的序列中的每一个数,
n[num],n[i]=n[i],n[num]#依次把这些数放在固定的位置上,方法是交换
quanpl(n,begin+1) #不看那个已经固定的数,进行递归
n[num],n[i]=n[i],n[num] #回溯,还原上一布操作,回到上一步,再进行下一次for循环
def jiechenghanshu (x):#求N!
a=1
for i in range(1, x + 1):
a = a * i
return a
quanpl(a,0)
def liebiaofenge(testlist):
"""
以三阶为例[[[1, 2, 3], [1, 3, 2]], [[2, 1, 3], [2, 3, 1]], [[3, 2, 1], [3, 1, 2]]]
:param testlist: 为了下面的行全组合,以首元素进行分割
:return: 返回首元素相同的分割矩阵
"""
dim = len(testlist[0])#探索组合矩阵维度
linesample=[]
cou=jiechenghanshu(dim-1)#分割长度,因为首元素固定,后面全排列,所以有dim-1!个首元素相同
"""
也可以用下面函数,可能速率会慢
from functools import reduce
#使用lambda,匿名函数,迭代
num = reduce(lambda x,y:x*y,range(1,dim))
"""
duan=int(len(testlist)/cou)#分割段数,首元素固定,后面全排列
for i in range(duan):#按照段数分割为首元素相同的行
linesample.append(testlist[cou*i:cou*(i+1)])
return linesample#返回分割的元素
#liebiaofenge(listqpl)
coutlit=[]
#
linesample=liebiaofenge(listqpl)
def zuhefunction(testlit,cout,num):#矩阵组合函数,输入(全组合列表),cout为计数器
"""
采用先分割成单个拉丁矩阵,然后开始一一对比,此为拉丁方阵搜索器
:param testlit: 需要组合的全排列列表
:param cout: 组合过程中,已组合的行数
:param num: 回溯使用的临时矩阵,用来存储组合的列表
:return: 返回全组合的拉丁方阵,不强调行排列,只强调列全排列组合
"""
if cout==len(testlit) :#将最底层的递归传入列表
if len(num)==len(num[0]):
coutlit.append(copy.copy(num))
num =[]
#print(coutlit)
return coutlit
for i in testlit[cout]:#以分相同首元素列表元素为循环次数
#linecopare()
if num == []:
num.append(i) # 将每一层的列表传入
else:
width = len(num)
linesnum = len(i)
tap=0
for k in range(1, linesnum): # 首列元素相同
for m in range(width):
if i[k]== num[m][k]:
tap=1
break
if tap == 1: # 中断循环
break
if tap==0:
num.append(i)
zuhefunction(testlit,cout+1,num)#递归函数
if i in num:
num.remove(i) # 控制变量,参与组合的直接删除
lastladin=[]
def pipei(tex1,tex2,lasttem):
if tex2 == [] and tex1==[] and len(lasttem)==lnegth*lnegth:#判断是否匹配完成,完成则返回拉丁正交矩阵
lastladin.append(copy.copy(lasttem))
lasttem=[]
return lastladin
for i in range(len(tex1)):#第一个矩阵按顺序循环提取
tempos=copy.copy(tex1[i])
tex1.remove(tex1[i])
for j in range(len(tex2)):#第二个矩阵按顺序与前一个匹配
sum=0
for k in range(len(tex2[j])):
if [tempos[k],tex2[j][k]] not in lasttem:#判断是否已经存在
sum+=1
else:
break
if sum==len(tex2[j]):
for m in range(len((tex2[j]))):
lasttem.append([tempos[m], tex2[j][m]])
temnum=tex2[j]
tex2.remove(tex2[j])
pipei(tex1,tex2,lasttem)#递归
tex2.insert(j,temnum)#第二个矩阵回溯
lasttem = []#第一个矩阵的一行与下游矩阵匹配跑完,代表以此行为开头的拉丁矩阵搜索结束,临时匹配矩阵置空
tex1.insert(i,tempos)#第一个矩阵回溯
#
def sousuolading(coutlit):#循环对比,使用全排列对比N!+N-1!+....1
"""
下面是打破重复计算的过程,把相同的比较矩阵只比较对角线相同的矩阵,其余矩阵均是/
对角线矩阵经过列变换得来的
:param coutlit:需要搜索的拉丁方阵集合
:return:搜索的正交拉丁方阵
"""
freecopy=copy.deepcopy(coutlit)
for i in range(len(coutlit)):
for j in freecopy[i+1:len(coutlit)]:
pipei(coutlit[i], j, [])
sum = 0
coutnum=0
for m in coutlit[i]:
if m[coutnum] == 1: # 只重复和对角为1的矩阵自身对比
sum += 1
else:
break#中断程序,防止不必要重复计算,
coutnum += 1
if sum==lnegth:
pipei(coutlit[i], freecopy[i], [])#此处即使相同的矩阵也需要使用深拷贝,如果使用相同coutlit[i],则可能会同时改变,无法出现结果
return lastladin
if __name__=="__main__":
quanpl(a, 0)
liebiaofenge(listqpl)
zuhefunction(linesample,0,[])
sousuolading(coutlit)
#print(lastladin)
print(len(lastladin))
#print(np.array(lastladin).reshape(len(lastladin),ndimen,2*ndimen))
delta = time.perf_counter() - start
print("程序运行的时间是:{}秒".format(delta))