9*9数独求解算法(C++)

1.使用递归法实现回溯策略,依次对候选数(1-9)逐个进行合法性判定,完成数独

C++代码如下:

// 检查在 (row, col) 位置填入 num 是否合法
bool isSafe(int arr[9][9], int row, int col, int num) {
    // 检查行
    for(int i = 0; i < 9; i++) {
        if(arr[row][i] == num) return false;
    }
    // 检查列
    for(int i = 0; i < 9; i++) {
        if(arr[i][col] == num) return false;
    }
    // 检查九宫格
    int startRow = row - row % 3;
    int startCol = col - col % 3;
    for(int i = 0; i < 3; i++) {
        for(int j = 0; j < 3; j++) {
            if(arr[i + startRow][j + startCol] == num) return false;
        }
    }
    return true;
}

// 回溯法解决数独问题
bool solveSudoku(int arr[9][9]) {
    for(int row = 0; row < 9; row++) {
        for(int col = 0; col < 9; col++) {
            if(arr[row][col] == 0) {
                for(int num = 1; num <= 9; num++) {
                    if(isSafe(arr, row, col, num)) {
                        arr[row][col] = num;
                        if(solveSudoku(arr)) {
                            return true;
                        }
                        arr[row][col] = 0; // 回溯
                    }
                }
                return false; 
            }
        }
    }
    return true; 
}

2.尝试过程优化,先用行列九宫格约束法把能确定的格子先填上,再对未填的格子执行回溯算法,

C++代码:

//列遍历
//参数: 1.当前所在行标, 2.记录已有的数的数组
void SudokuCols(const int arr[9][9], int row, int reArr[10]) {
	for(size_t i = 0; i < 9; i++) {
		reArr[arr[row][i]] = 0;
	}
}
//行遍历
//参数: 1.当前所在列标, 2.记录已有的数的数组
void SudokuRows(const int arr[9][9], int col, int reArr[10]) {
	for(size_t i = 0; i < 9; i++) {
		reArr[arr[i][col]] = 0;
	}
}
//获取标识可直接计算的位置的数组
//参数:1.数独数组,2.要计算的二维数组, 3.存储已有的数的数组, 4.行标  5.列标
void getPointArr(const int arr[9][9], int PointArr[9][9], const int reArr[10], int row, int col) {
	int couts = 0;
	for(size_t i = 1; i < 10; i++) {
		reArr[i] == 1 && couts++;
	}
	if(couts == 1 && arr[row][col] == 0) {
		PointArr[row][col] = 1;
	}
}
//获取九宫格有几个数
//参数:1.数独数组 2.行坐标 3.列坐标 4.记录已有的数的数组
void get9BoxGrid(const int arr[9][9], int row, int col, int reArr[10]) {
	int startRow = row - row % 3;
	int startCol = col - col % 3;
	for(int i = 0; i < 3; i++) {
		for(int j = 0; j < 3; j++) {
			reArr[arr[i + startRow][j + startCol]] = 0;
		}
	}
}
// 检查在 (row, col) 位置填入 num 是否合法
bool isSafe(int arr[9][9], int row, int col, int num) {
	// 检查行
	for(int i = 0; i < 9; i++) {
		if(arr[row][i] == num) return false;
	}
	// 检查列
	for(int i = 0; i < 9; i++) {
		if(arr[i][col] == num) return false;
	}
	// 检查九宫格
	int startRow = row - row % 3;
	int startCol = col - col % 3;
	for(int i = 0; i < 3; i++) {
		for(int j = 0; j < 3; j++) {
			if(arr[i + startRow][j + startCol] == num) return false;
		}
	}
	return true;
}
//解部分数独函数  
void getPortionSudoku(int arr[9][9]) {
	int reArr[10] = { 1 };

	int PointArr[9][9] = { 0 };

	bool stop = false;

	while(!stop) {
		stop = true;
		memset(PointArr, 0, sizeof(PointArr));
		for(int i = 0; i < 9; ++i) {
			for(int j = 0; j < 9; ++j) {
				fill(reArr, reArr + 10, 1);
				//获取行列已有的数
				SudokuCols(arr, i, reArr);
				SudokuRows(arr, j, reArr);
				//获取能计算确定的数的位置数组
				getPointArr(arr, PointArr, reArr, i, j);
				//按行列判定法计算填入数字
				if(PointArr[i][j]) {
					for(size_t num = 1; num < 10; num++) {
						if(reArr[num]) {
							arr[i][j] = num;
							stop = false;
							break;
						}
					}
				} else {//按九宫格判定法计算填入数字
					if(arr[i][j] == 0) {
						int nineBoxGrid[9] = { 0 };
						get9BoxGrid(arr, i, j, reArr);
						getPointArr(arr, PointArr, reArr, i, j);
						if(PointArr[i][j]) {
							for(size_t num = 1; num < 10; num++) {
								if(reArr[num]) {
									arr[i][j] = num;
									stop = false;
									break;
								}
							}
						}
					}
				}
			}
		}
	}
}
// 回溯法解决数独问题
bool solveSudoku(int arr[9][9]) {
	for(int row = 0; row < 9; row++) {
		for(int col = 0; col < 9; col++) {
			if(arr[row][col] == 0) {
				for(int num = 1; num <= 9; num++) {
					if(isSafe(arr, row, col, num)) {
						arr[row][col] = num;
						if(solveSudoku(arr)) {
							return true;
						}
						arr[row][col] = 0; // 回溯
					}
				}
				return false;
			}
		}
	}
	return true;
}

提供main函数:

int main() {
	int arr[9][9] = {
		{ 0, 2, 0, 4, 0, 9, 1, 0, 0 },
		{ 0, 0, 6, 0, 5, 0, 0, 8, 9 },
		{ 0, 7, 0, 0, 8, 3, 0, 2, 4 },
		{ 7, 1, 0, 5, 0, 0, 0, 0, 0 },
		{ 0, 0, 0, 0, 9, 0, 2, 0, 0 },
		{ 0, 0, 0, 0, 4, 0, 0, 0, 7 },
		{ 0, 6, 0, 0, 0, 0, 0, 0, 0 },
		{ 0, 0, 7, 3, 0, 0, 8, 0, 1 },
		{ 3, 4, 0, 0, 0, 5, 0, 6, 0 }
	};

	int rows = sizeof(arr) / sizeof(arr[0]);
	int cols = sizeof(arr[0]) / sizeof(arr[0][0]);

	for(int i = 0; i < rows; ++i) {
		for(int j = 0; j < cols; ++j) {
			cout << arr[i][j] << " ";
		}
		cout << endl;
	}
	cout <<"计算后:"<< endl;

	getPortionSudoku(arr);

	if(solveSudoku(arr)) {
		for(int i = 0; i < 9; i++) {
			for(int j = 0; j < 9; j++) {
				cout << arr[i][j] << " ";
			}
			cout << endl;
		}
	} else {
		cout << "No solution exists" << endl;
	}

	return 0;
}

一个简单的解数独的小程序 //***数独的解,参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]; } } }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_xian_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值