
将1,2,–,n这n个数填入n*n矩阵使得每行每列的数两两不同(都是1,2,–,n的全排列)这样的n阶方阵是拉丁方阵。如果一对n阶拉丁方阵对应的元素构成的有序对两两不同,则称这一对n阶拉丁方阵是正交的。现要编程用计算机构造出所有的n阶拉丁方阵和n阶正交拉丁方阵组该怎么做一个显然的事实是,n阶拉丁方阵中各行i元素i1,2—n是两两不同列的。所以按a1,—,an(为1,2–n的置换)顺序向各行填入ai(i1,2,–,n)使其两两不同列,就能得到一个拉丁方阵。我们可以按1,2,—,n的顺序填入各数得到一系列拉丁方阵,然后从中剔除可以由其中的其他拉丁方阵置换得到的拉丁方阵经剔除后剩下拉丁方阵姑且叫基拉丁方阵。各基拉丁方阵通过置换{1,2,–,n}-{a1,a2,–,an}就能得到所有n阶拉丁方阵。且每个基拉丁方阵通过置换得到的所有拉丁方阵包括基拉丁方阵在内两两不正交,若两个不同的基拉丁方阵相互正交,则对于由这两个基拉丁方阵通过置换{1,2,–,n}-{a1,a2,–,an}衍生出的所有拉丁方阵包括这两个基拉丁方阵来说衍生自其中一个拉丁方正的拉丁方阵和衍生自另一基拉丁方阵的拉丁方阵相互正交反之亦然。因此只要找出所有的正交基拉丁方阵对就可找出所有的正交拉丁方阵组。具体实现时我们需要一个存放找到的拉丁方阵的二维数组Lad(回溯试探操作就对它进行),二维数组position-其第i行第j列的元素表示试探过程中数字j在Lad第i行的位置的列标我称它为占位矩阵以及二维数组hold其第i行第j列的元素为1时表示Lad的第i行第j列已被填充,为0时表示Lad的第i行第j列未被填充。我们用回溯法反复试探将n个数字全部填入Lad中使其为拉丁方阵。通过回溯法找到的拉丁方阵及对应的占位矩阵被存放在B1类型的链表中。随后根据以上分析我们从找到的的拉丁方阵中剔除可由其中其他拉丁方阵置换得到的拉丁方阵,从而得到存放在B1类型链表中的所有基拉丁方阵,剔除过程中用到了一条性质:若两拉丁方阵对应的占位矩阵的所有列构成的集合相等,则两拉丁方阵中的每一个可以通过置换得到另一个。得到基拉丁方阵后就可以轻而易举地通过置换输出所有的n阶拉丁方阵然后通过判断任意两个基拉丁方阵是否正交来求得并输出对两个基拉丁方阵作置换正交拉丁方阵组问题就解决了。以下是构造拉丁方阵和正交拉丁方阵组的C语言代码。程序当n4时运行时间较短几乎可立即得出结果,n5时就是漫长的等待#include stdio.h #include malloc.h #include string.h using namespace std; #define N 3 struct B { int** Pa; //结构体B中Pa指针用于指向找到的拉丁方阵 int** Pb; //结构体B中Pb指针用于指向该拉丁方阵对应的占位矩阵 struct B* next; }; typedef struct B B1; void L(int** p1, int row, int col, int* p2, int n, int factor_result[], int currentN); //求所有全排列的递归函数 void output(B1* psnew, int fac, int** factor); //输出基拉丁方阵对应的所有拉丁方阵的函数 bool fill(int Lad[][N], int position[][N], int i, bool assist[][N]); //将i填充入方阵的函数,填充成功返回1否则返回0 int find(int i, int position[][N], int k, bool assist[][N], int Lad[][N]); //寻找方阵的k1行可以放置i的位置,找到返回列标否则返回0 bool place(int i, int j, int k, bool assist[][N], int Lad[][N]); //函数判断方阵的k1行,j列是否可以放置i可以返回1否则返回0 int main() { int Lad[N][N], position[N][N]; //回溯试探针对Lad方阵进行试探成功后Lad中保存有找到的拉丁方阵.position为占位矩阵,hold为标志方阵中各位置是否已被填充的矩阵 for (int i 0; i N; i) { for (int j 0; j N; j) { Lad[i][j] N; position[i][j] N; } } bool assist[N][N] { false }; int que[N]; int** factor; //指向存放所有全排列的方阵 int fac; int i, j, k, flag; //i在第一个while循环中表示填充的数字,flag表示两占位矩阵列集合是否相等 B1* head, * tail, * psnew, * pm; head (B1*)malloc(sizeof(B1)); tail head; head-next NULL; for (i 0; i N; i) que[i] i 1; int factor_result[N]; factor_result[0] 1; for (int i 1; i N; i) { factor_result[i] factor_result[i - 1] * (i 1); } factor (int**)malloc((fac factor_result[N - 1]) * sizeof(int*)); for (i 0; i fac; i) factor[i] (int*)malloc(N * sizeof(int)); //建立用来保存所有全排列的矩阵 L(factor, 0, 0, que, N, factor_result, N - 1); //将所有全排列填入上述矩阵 i 0; while (true) //回溯试探开始,往Lad方阵中按一定规则填充各数 { if (i 0) //回溯至或开始填充第一个数 { if (fill(Lad, position, i, assist) false) { break; //第一个数填充失败,所有依次填1,2,--n的拉丁方阵均已找到循环结束 } i; //自增1准备填充第二个数 } else { if (fill(Lad, position, i, assist) false) //第i个数填充失败 { i--; //回溯至上一个数 } else { if (i ! N - 1) //第i个数填充成功,但所有数没有填完 { i; //准备填充下一个数 } else //所有数填充成功找到一个拉丁方阵 { psnew (B1*)malloc(sizeof(B1)); psnew-next NULL; psnew-Pa (int**)malloc(N * sizeof(int*)); //建立B1类型节点,令节点的Pa,Pb指向该拉丁方阵和对应的占位矩阵 psnew-Pb (int**)malloc(N * sizeof(int*)); for (j 0; j N; j) { psnew-Pa[j] (int*)malloc(N * sizeof(int)); psnew-Pb[j] (int*)malloc(N * sizeof(int)); for (k 0; k N; k) { *(psnew-Pa[j] k) Lad[j][k]; //用找到的拉丁方阵和占位矩阵填充Pa,Pb指向的拉丁方阵和占位矩阵 *(psnew-Pb[j] k) position[j][k]; } } tail-next psnew; tail psnew; } } } } if (head-next ! NULL) //从找到的依次填入1,2---n的拉丁方阵中剔除可由其中其他拉丁方阵置换得到的拉丁方阵 { for (psnew head-next; psnew-next ! NULL; psnew psnew-next) { for (tail psnew-next, pm psnew; tail ! NULL; tail tail-next, pm pm-next) { flag 0; loop: for (i 0; i N; i) { for (j 0; j N; j) { for (k 0; k N; k) { if (*(psnew-Pb[k] i) ! *(tail-Pa[k] j)) { flag 1; break; //判断B1类型链表中两拉丁方阵对应的占位矩阵的所有列构成的两个集合是否相等 } } if (flag 1) { flag 0; continue; } flag 1; break; } if (flag 1) { flag 0; continue; } flag 1; break; } if (flag 0) // flag1 则两占位矩阵列集合不相等否则相等 { for (i 0; i N; i) //两占位矩阵的列集合相等相应的两拉丁方阵可以通过置换相互得到,故删除tail指向的拉丁方阵占位矩阵和节点 { free(tail-Pa[i]); free(tail-Pb[i]); } free(tail-Pa); free(tail-Pb); pm-next tail-next; //让pm的指针域指向tail所指节点的后继节点 free(tail); //删除tail所指节点 if (pm-next ! NULL) { tail pm-next; //tail指向被删节点的后继节点 goto loop; //立即开始该后继节点和psnew指向的节点的比较 } else { tail pm; if (psnew-next NULL) goto exit; //所有比较删除工作均已完成退出循环 } } } } exit:; if (head-next NULL) //没有基拉丁方阵,所以没有拉丁方阵 { printf(不存在N阶拉丁方阵\n); printf(不存在N阶正交拉丁方阵组\n); } else { printf(所有N阶拉丁方阵为:\n); for (psnew head-next; psnew ! NULL; psnew psnew-next) //输出所有拉丁方阵 { output(psnew, fac, factor); } psnew head-next; if (psnew-next NULL) //只有一个基拉丁方阵所以不存在正交拉丁方阵组 printf(不存在N阶正交拉丁方阵组\n); else { k 0; for (psnew head-next; psnew-next ! NULL; psnew psnew-next) //判断正交拉丁方阵组的存在性,并输出正交拉丁方阵组 { for (tail psnew-next; tail ! NULL; tail tail-next) { int hold[N][N] { 0 }; flag 0; for (i 0; i N; i) { for (j 0; j N; j) { hold[*(psnew-Pa[i] j)][*(tail-Pa[i] j)]; if (hold[*(psnew-Pa[i] j)][*(tail-Pa[i] j)] 1) //判断基拉丁方阵是否正交 { flag 1; break; } } if (flag 1) break; } if (flag 0) //找到一对正交拉丁方阵组输出 { k 1; printf(正交拉丁组:\n); printf(第一组\n); output(psnew, fac, factor); printf(第二组\n); output(tail, fac, factor); } } } if (k 0) printf(不存在N阶正交拉丁方阵组\n); } } } return 0; } bool fill(int Lad[][N], int position[][N], int i, bool assist[][N]) { int k; if (assist[i][0] false) k 0; //从头开始填充i,行号置1 else { k N - 1; //在当前数字i回溯,行号置尾行号 } while (true) { int temp find(i, position, k, assist, Lad); if (k 0 temp N || position[i][k] ! N) { Lad[k][position[i][k]] N; assist[i][position[i][k]] false; if (temp N) { position[i][k] N; if (k 0) return false; } } if (temp N) --k; else { position[i][k] temp; Lad[k][temp] i; //填充i assist[i][temp] true; if (k ! N - 1) k; else return true; } } } int find(int i, int position[][N], int k, bool assist[][N], int Lad[][N]) { int j; if (position[i][k] N) j 0; else j position[i][k] 1; for (; j N; j) {//搜索k1行下一个可以填充i的位置 if (place(i, j, k, assist, Lad)) return (j); //找到返回该位置的列标 } return N; //没有下一个可以填充i的位置,返回0 } bool place(int i, int j, int k, bool assist[][N], int Lad[][N]) { if (assist[i][j]) //检查k1行以上各行的i是否都和k1行j列不在同一列 return false; //有i在同一列,k1行j列位置不合法 if (Lad[k][j] ! N) //k1行j列已被填充,位置不合法 return false; //位置不合法 return true; //位置合法 } void L(int** p1, int row, int col, int* p2, int n, int factor_result[], int currentN) //将p2指向的数组中的n个数的所有全排列存放在p1所指向的二维数组中 { int i; if (currentN 0) { for (i 0; i n; i) { if (*(p2 i) ! 0) break; } *(p1[row] col) *(p2 i); } else { int c, f, j; c factor_result[currentN - 1]; j row; f row; for (i 0; i n; i) { if (*(p2 i) 0) continue; f f c; *(p2 i) 0; L(p1, j, col 1, p2, n, factor_result, currentN - 1); *(p2 i) i 1; while (j f) { *(p1[j] col) *(p2 i); j; } } } } void output(B1* psnew, int fac, int** factor) //输出psnew指向的节点指向的基拉丁方阵和置换得到的所有其他拉丁方阵 { int i, j, k; for (i 0; i N; i) { for (j 0; j N; j) printf(%d , *(psnew-Pa[i] j) 1); //输出基拉丁方阵 printf(\n); } printf(\n); for (k 1; k fac; k) { for (i 0; i N; i) { for (j 0; j N; j) printf(%d , *(factor[k] *(psnew-Pa[i] j))); //输出置换得到的所有其他拉丁方阵 printf(\n); } printf(\n); } }