您的位置:首页 > 编程语言

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

2009-05-06 00:10 926 查看
五一期间无聊时想起去年考研复试有一道上机题目当时没作出来,于是一时兴起想重新拾起看看是当时太紧张,还是自己能力不足。然后发现这道题目还真稍微有些难度,相当于一道数独问题(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一下懒惰的自己……
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐