C++ 完美破解九宫格(数独)游戏

本文介绍了一段使用C++实现的数独求解程序,通过回溯法结合剪枝策略,仅输出唯一解。同时提供了一种方法用于打印所有可能的解,适用于检查数独的有效性。

看到优快云上有位大神用C#写了一个破解数独的程序(点击打开链接),不过我对C#也不懂,比较喜欢C++,就用标准C++也写了一个,希望各位喜欢。三纯程序,纯控制台程序,纯各人爱好,纯算法程序,无win API。基本思路与之前那个类似,采用brute force加剪枝,找到第一个符合条件的情况就立即退出不再查找。一般一个合格的数独只有唯一解,如果你的数独多解的话,那也就不叫数独了。


代码如下:

#include <iostream>
#include <deque>

using namespace std;

const int MAX_SIZE = 9;		// 九宫格

struct Pos {
	Pos(int x, int y) : row(x), col(y) {};
	int row;
	int col;
};

typedef deque <Pos> qpos;
qpos Q;		// 记录要放置数字的位置

// 九宫格数组,0表示玩家要放置数字的位置
int sudoku[MAX_SIZE][MAX_SIZE] = {
	{0, 0, 3, 0, 5, 0, 0, 0, 9},
	{0, 0, 0, 1, 0, 0, 0, 2, 5},
	{0, 8, 0, 0, 3, 7, 0, 0, 0},
	{0, 0, 0, 0, 0, 8, 0, 9, 7},
	{2, 0, 0, 0, 6, 0, 0, 0, 4},
	{9, 4, 0, 0, 0, 0, 8, 0, 1},
	{0, 0, 0, 6, 9, 0, 4, 0, 0},
	{8, 0, 0, 0, 0, 5, 0, 0, 0},
	{6, 0, 0, 0, 1, 0, 9, 0, 0},
};

void printSudoku()
{
	cout << "-------------------------" << endl;
	for (int i = 0; i < MAX_SIZE; i++) {
		for (int j = 0; j <MAX_SIZE; j++) {
			if (j % 3 == 0) {
				cout << "| ";
			}
			cout << sudoku[i][j] << " ";
		}
		cout << "| ";

		cout << endl;
		if ( (i+1) % 3 == 0 ) {
			cout << "-------------------------" << endl;
		}
	}
}

bool check(Pos p, int n)
{
	int cur_row = p.row;
	int cur_col = p.col;
	// 验证行列是否合格
	for (int i = 0; i < MAX_SIZE; i++) {
		if (n == sudoku[i][cur_col] || n == sudoku[cur_row][i]) {
			return false;
		}
	}

	// 验证九宫格内是无复生数字
	int grid_row = ( cur_row / 3 ) * 3;
	int grid_col = ( cur_col / 3 ) * 3;
	for (int i = 0; i < 3; i++) {
		if (n == sudoku[grid_row][i + grid_col] || 
			n == sudoku[grid_row + i][grid_col]) {
			return false;
		}
	}

	return true;
}

bool place(qpos & Q)
{
	// 递归结束条件为没有要断续放置数字的位置
	if (Q.empty()) {
		printSudoku();
		return true;
	}
	Pos cur(Q.front().row, Q.front().col);	// 当前需要放置的位置信息
	Q.pop_front();
	for (int i = 1; i <= 9; i++) {		// 从1到9轮流尝试
		if ( check(cur, i) ) {
			sudoku[cur.row][cur.col] = i;	// 放置数字i到当前位置
			if ( !place(Q) ) {		// 放置下一位置
				// 下一位置放置失败,则在当前位置尝试放置下一个i
				sudoku[cur.row][cur.col] = 0;	// 将当前位置值重置
			} else {	// 下一位置放置成功
				return true;
			}
		}
	}		
	Q.push_front(cur);	//当前位置不论怎么放置数字,下一位置都无法放置成功,
				// 重新插入该位置信息,返回上一级放置位置
	return false;
}

int main()
{	
	// Q中保存需要放置数字的位置
	for (int i = 0; i < MAX_SIZE; i++) {
		for (int j = 0; j < MAX_SIZE; j++) {
			if (0 == sudoku[i][j]) {
				Q.push_back(Pos(i, j));
			}
		}
	}

	place(Q);

	return 0;
}


我也来个运行截图:


上面的程序只会打印出一种符合的数独,如果你想将所以可能的情况都打印出来怎么办,一般情况下,合格的数独都只有一种解,那万一有一个不合格的数独,你又想知道它的全部解,那么,你可以用下面的方法:

/**
 * 打印所有符合条件的情况
 */
void place2(qpos & Q)
{
	// 递归结束条件为没有要断续放置数字的位置
	if (Q.empty()) {
		printSudoku();
		return;
	}
	Pos cur(Q.front().row, Q.front().col);	// 当前需要放置的位置信息
	Q.pop_front();
	for (int i = 1; i <= 9; i++) {		// 从1到9轮流尝试
		if ( check(cur, i) ) {
			sudoku[cur.row][cur.col] = i;	// 放置数字i到当前位置
			place2(Q);
			sudoku[cur.row][cur.col] = 0;	// 将当前位置值重置
		}
	}		
	Q.push_front(cur);	// 重新插入该位置信息,返回上一级放置位置
}

一个简单的解数独的小程序 //***求数独的解,参数mod为0或1,time0为搜索开始时的时间,mod=0时仅检查Data1中数独是否有解,有解则抛出1,mod=1时求出所有解并输出*** { int i,j,im=-1,jm,min=10; int mark[10]; for(i=0;i<9;i++) { for(j=0;j<9;j++) { if(Data1[i][j]) //如果该位置有数据则跳过 { continue; } int c=Uncertainty(i,j,mark); //如果该位置为空则先求不确定度 if(c==0) //如果不确定度为0则表示该数独无解 { return; } if(c<min) //得到不确定度最小的位置(第im行 第jm列 不确定度为min) { im=i; jm=j; min=c; } } } if(im==-1) //所有位置都已经确定,数独已经解出,按要求输出解 { if(mod==1) //显示所有解 { if(IsSolved()==true) { if(Solutions++<MAXANSNUM) { cout<<"第 "<<Solutions<<" 个 解:"<<endl; Display(1); } if((time(NULL)-time0)>TIMEOUT) { throw(Solutions); } } return; } else //只给出一个解 { throw(1); //跳出所有递归调用,返回1 } } Uncertainty(im,jm,mark); //从不确定度最小的位置开始解 for(i=1;i<=9;i++) { if(mark[i]==0) { Data1[im][jm]=i; //对不确定度最小的位置尝试可能的赋值 Search(mod,time0); //递归调用 } } Data1[im][jm]=0; } void Csudoku::Set(int n) //***随机生成数独,参数n表示数独中待填元素个数*** { srand((unsigned)time(NULL)); int i,j,k; do { for(i=0;i<9;i++) //随机给每行的某一个位置赋值 { for(j=0;j<9;j++) { Data1[i][j]=0; } j=rand()%9; Data1[i][j]=i+1; } } while(!Solve(0)); //按照随机赋的值给出一个解 for(k=0;k<n;) //从中随机去掉n个数据 { i=rand()%81; j=i%9; i=i/9; if(Data1[i][j]>0) { Data1[i][j]=0; k++; } } for(i=0;i<9;i++) //将生成的数独存入Data0数组 { for(j=0;j<9;j++) { Data0[i][j]=Data1[i][j]; } } }
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值