数独问题的一种简单算法代码实现

五一期间无聊时想起去年考研复试有一道上机题目当时没作出来,于是一时兴起想重新拾起看看是当时太紧张,还是自己能力不足。然后发现这道题目还真稍微有些难度,相当于一道数独问题(sudoku)的简化版。自己想来想去也只能想到两种算法,一种是拿剩余元素做全排列测试,一种是回溯法测试。最后只实现了一个全排列测试的算法。然后又发现自己要写一个非递归的全排列(permutation)也有难度,想了两天,也没搞出来,自我BS了一把,感觉这么基本的算法都搞不定,还想参加参加ACM/ICPC什么的,真是不自量力。后来还是挑战自我思考失败,只能上网查了一下,发现组合数学(combinatorics)研究了这个东西,给出很多算法,照那些算法实现的话相当容易。

 

最后搞出来还是有一点成就感,代码贴出来:

/* HUST_IPRAI:2008考研复试题 有一个4×4的方阵。要求:每个元素必须是1,2,3,4其中的一个,而且每一行不能重 复,例如不能有2个1等,每一列也不能重复。而且将矩阵分成4个2×2的矩阵,每个小方阵 也不能有重复。 (1),给出方阵,如 1 4 2 3 2 3 4 1 4 1 3 2 3 2 1 4 编一程序,判断此矩阵是否满足要求 (2) 有一矩阵 3 ? ? ? ? 2 ? ? ? 4 1 ? ? ? 3 ? 编一程序,求出矩阵其他的元素。 author: MulinB date: 2009-05-01 */ #include <iostream> #include <iomanip> using namespace std; #define MATRIX_WIDTH 4 //矩阵阶数 #define SMALL_CELL_WIDTH 2 //小单元格 //打印矩阵 void DisplayMatrix(int matrix[MATRIX_WIDTH][MATRIX_WIDTH]) { cout << "-------------matrix--------------" << endl; for (int i=0; i<MATRIX_WIDTH; i++) { for (int j=0; j<MATRIX_WIDTH; j++) { cout << setw(2) << matrix[i][j] << " "; } cout << endl; } cout << "---------------------------------" << endl; } //检查是否满足行、列、每个小阵无重复元素 TODO:算法效率有待提高 bool VerifyMatrix(int matrix[MATRIX_WIDTH][MATRIX_WIDTH]) { int temp = 0; for (int i=0; i<MATRIX_WIDTH; i++) { for (int j=0; j<MATRIX_WIDTH; j++) { temp = matrix[i][j]; int k = 0; //counter for loops //检查数据是否合法 if (temp < 1 || temp > MATRIX_WIDTH) return false; //检查行里有无重复 for (k=0; k<j; k++) //当前元素之前的元素 { if (matrix[i][k] == temp) return false; } for (k=j+1; k<MATRIX_WIDTH; k++) //当前元素之后的元素 { if (matrix[i][k] == temp) return false; } //检查列里有无重复 for (k=0; k<i; k++) //当前元素之前的元素 { if (matrix[k][j] == temp) return false; } for (k=i+1; k<MATRIX_WIDTH; k++) //当前元素之后的元素 { if (matrix[k][j] == temp) return false; } //检查单元格里有无重复 int cell_pos_i = i / SMALL_CELL_WIDTH; //单元格的位置 int cell_pos_j = j / SMALL_CELL_WIDTH; //单元格的位置 for (int x=cell_pos_i*SMALL_CELL_WIDTH; x<(cell_pos_i+1)*SMALL_CELL_WIDTH; x++) { for (int y=cell_pos_j*SMALL_CELL_WIDTH; y<(cell_pos_j+1)*SMALL_CELL_WIDTH; y++) { if (x==i && y==j) continue; if (matrix[x][y] == temp) return false; } } } } return true; } //small tool function void SwapValue(int& a, int& b) { int temp; temp = a; a = b; b = temp; return; } //填充矩阵不完整元素 bool FillMatrix(int matrix[MATRIX_WIDTH][MATRIX_WIDTH]) { //算法1:全排列尝试,可以试出全部答案 int numCounter[MATRIX_WIDTH] = {0}; //用来计数,每个数字应该一共出现MATRIX_WIDTH次 //先统计每个数字出现次数 for (int i=0; i<MATRIX_WIDTH; i++) { for (int j=0; j<MATRIX_WIDTH; j++) { if (matrix[i][j] > 1 && matrix[i][j] < MATRIX_WIDTH) { numCounter[matrix[i][j]-1]++; //该数字的counter加1 continue; //已经填上 } } } //统计待全排列的数字,比如:如果2出现了1次,那么还有MATRIX_WIDTH-1个2需要排列 int elems[MATRIX_WIDTH*MATRIX_WIDTH] = {0}; //待全排列的数字的容器 int numOfElem = 0; for (int m=0; m<MATRIX_WIDTH; m++) { for (int n=0; n<MATRIX_WIDTH-numCounter[m]; n++) { elems[numOfElem++] = m+1; //加入待排列数字 } } //进行全排列测试 int tempMatrix[MATRIX_WIDTH][MATRIX_WIDTH] = {0}; //用来做输出的矩阵 //全排列非递归算法: 字典序法,递增进位制数法,递减进位制数法,邻位对换法. //全排列递归类算法:回溯、递归、循环移位法 /* 字典序法:把升序的排列(当然,也可以实现为降序)作为当前排列开始,然后依次计算当前排列的下一个字典序排列。 对当前排列从后向前扫描,找到一对为升序的相邻元素,记为i和j(i < j)。 如果不存在这样一对为升序的相邻元素,则所有排列均已找到,算法结束; 否则,重新对当前排列从后向前扫描,找到第一个大于i的元素k,交换i和k,然后对从j开始到结束的子序列反转,则此时得到的新排列就为下一个字典序排列。 这种方式实现得到的所有排列是按字典序有序的,这也是C++ STL算法next_permutation的思想。 */ /* 字典序算法的思想来历是:严格按照从低到高的顺序输出排列。 字典序算法如下: 设P是1~n的一个全排列:p=p1p2......pn=p1p2......pj-1pjpj+1......pk-1pkpk+1......pn 1)从排列的右端开始,找出第一个比右边数字小的数字的序号j(j从左端开始计算),即 j=max{i|pi2)在pj的右边的数字中,找出所有比pj大的数中最小的数字pk,即 k=max{i|pi>pj}(右边的数从右至左是递增的,因此k是所有大于pj的数字中序号最大者) 3)对换pj,pk 4)再将pj+1......pk-1pkpk+1pn倒转得到排列p''=p1p2.....pj-1pjpn.....pk+1pkpk-1.....pj+1,这就是排列p的下一个下一个排列。 例如839647521是数字1~9的一个排列。从它生成下一个排列的步骤如下: 自右至左找出排列中第一个比右边数字小的数字4 839647521 在该数字后的数字中找出比4大的数中最小的一个5 839647521 将5与4交换 839657421 将7421倒转 839651247 所以839647521的下一个排列是839651247。 */ //全排列算法:这里使用字典序算法 int elemNewPerm[MATRIX_WIDTH*MATRIX_WIDTH] = {0}; //用来存放每个排列的元素 memcpy(elemNewPerm, elems, sizeof(int)*numOfElem); //初始化为初始序列 int pos_j = 0; int pos_k = 0; int p_k = 0; while (true) { //----------------------------------------- //测试当前排列填入矩阵是否合法 int idxToBeFilled = 0; for (int i=0; i<MATRIX_WIDTH; i++) { for (int j=0; j<MATRIX_WIDTH; j++) { if (matrix[i][j] > 1 && matrix[i][j] < MATRIX_WIDTH) { tempMatrix[i][j] = matrix[i][j]; //拷贝原来元素 } else { tempMatrix[i][j] = elemNewPerm[idxToBeFilled++];//填入待填入的元素 } } } if (VerifyMatrix(tempMatrix) == true) //测试是否符合要求 { DisplayMatrix(tempMatrix); //符合条件,打印出来 cout << "The above matrix is an OK answer!" << endl; } //------------------------------------------- //------------------------------------------- //全排列字典序列算法 //从右侧寻找第一个比右侧小的元素j for (pos_j=numOfElem-2; pos_j>=0; pos_j--) { if (elemNewPerm[pos_j] < elemNewPerm[pos_j+1]) break; //找到j } if (pos_j < 0) break; //没有找到j //寻找j右侧比pj大的最小数 p_k = elems[numOfElem-1]; //先令p_k为最大值 pos_k = 0; for (int x=pos_j; x<numOfElem; x++) { if (elemNewPerm[x] > elemNewPerm[pos_j] && elemNewPerm[x] <= p_k) //找比pj大的最小值 { p_k = elemNewPerm[x]; pos_k = x; //找到k } } if (pos_k == 0) break; //没有找到k //调换pj和pk SwapValue(elemNewPerm[pos_j], elemNewPerm[pos_k]); //翻转j之后的序列,不包括j for (int y=1; y<=(numOfElem-1-pos_j)/2; y++) { SwapValue(elemNewPerm[pos_j+y], elemNewPerm[numOfElem-y]); } } //TODO: 算法2:使用栈进行回溯 return true; } int main() { int MatrixToBeFilled[MATRIX_WIDTH][MATRIX_WIDTH] = {{3, 0, 0, 0}, {0, 2, 0, 0}, {0, 4, 1, 0}, {0, 0, 3, 0}}; FillMatrix(MatrixToBeFilled); //解题 return 0; }

 

运行结果如下:

-------------matrix-------------- 3 1 2 4 4 2 1 3 1 3 4 2 2 4 3 1 --------------------------------- The above matrix is an OK answer! -------------matrix-------------- 3 1 2 4 4 2 1 3 2 3 4 1 1 4 3 2 --------------------------------- The above matrix is an OK answer! -------------matrix-------------- 3 1 4 2 4 2 1 3 1 3 2 4 2 4 3 1 --------------------------------- The above matrix is an OK answer! -------------matrix-------------- 3 4 1 2 1 2 4 3 4 3 2 1 2 1 3 4 --------------------------------- The above matrix is an OK answer! -------------matrix-------------- 3 4 2 1 1 2 4 3 2 3 1 4 4 1 3 2 --------------------------------- The above matrix is an OK answer! -------------matrix-------------- 3 4 2 1 1 2 4 3 4 3 1 2 2 1 3 4 --------------------------------- The above matrix is an OK answer!

 

把宏改成9的话,应该可以解决九宫格数独问题,懒得试,主要是怕不行,没时间去改……

算法比较慢,有待改进,可惜大量时间要花在老板的项目和自己的GRE/TOEFL上,所以没时间细扣,唉,有悖程序员精益求精、锲而不舍的精神啊,BS一下懒惰的自己……

 

你可能感兴趣的:(算法,Date,测试,Matrix,loops,permutation)