为了提高自己的算法能力和代码实践能力,拓宽视野,为转专业做准备,今天特别阅读了《深入浅出程序设计竞赛(基础篇)》(洛谷学术组)中的深度优先搜索板块。在浅看了例14-1后,我便着手从头开始写求解四节数独的程序,提高对深度优先搜索的认识。
书中的原题是这样的:
P187 例14-1 四阶数独
数独是一种著名的益智游戏。这里讨论的是一种简化过的数独--四阶数独。给出一个 4 x 4的格子,每个格子只能填写1到4的整数要求每行每列和四等分更小的正方形部分都刚好由1到4组成。给出空白的方格,请问:一共有多少种合法的填写方法?
书中原解:
#include<bits/stdc++.h>
using namespace std;
#define size 5
int a[size*size],n=16,ans=0;
int b1[size][5],b2[size][5],b3[size][5];
void dfs(int x){
if(x>n){ans++;
// for(int i=1;i<=n;i++){
// printf("%d ",a[i]);
// if(i%4==0) puts(" ");
// }
// puts(" ");
return;
}
int row = (x-1)/4+1;
int col = (x-1)%4+1;
int block = (row-1)/2 *2 +(col-1)/2 +1;
for(int i=1;i<=4;i++){
if(b1[row][i]==0 && b2[col][i]==0 && b3[block][i]==0){
a[x]=i;
b1[row][i]=1;b2[col][i]=1;b3[block][i]=1;
dfs(x+1);
b1[row][i]=0;b2[col][i]=0;b3[block][i]=0;
}
}
}
int main(){
dfs(1);
cout << ans ;
return 0;
}
个人研究的题目是这样的:
研究题目 求解四阶数独
给出一个4 x 4的格子,里面有部分格子已经填充了1到4的整数,请问:按照数独的规则,一共有多少种填法?
在实现代码的过程中遇到了一些困难。首先是如何存储每一步搜索之后数独题面的变化。一开始我以为可以只用boolmap定位数独中每一个格子里的数字。这个名字是我自己起的,在这里的作用我做出解释:
boolmap 布尔矩阵
其相当于一个三维的布尔数组,对
,boolmap[i][j][k]表示的是第j行(i=0)/列(i=1)/块(i=2)中存在整数
的命题的真假。
当时我天真地认为只要确定行和列(实现集合的交),以及要定位的数字就能确定某行某列是否存在该数字。结果实现的是集合的并而不是交的功能,这导致我重新编写了代码,定义了如下的结构体
dataSet 数据集
其内含了boolmap和实时更新的数独题面presentStatus,后者完成识别和定位数字的功能。
接下来只需要专注于深度优先搜索的函数的编写和一些辅助性函数的重构即可。代码中有一些不必要的函数如testSpace,getNumber,generateCompletedSudoku等,这是第一次编写犯错的遗留物,而我不想费劲去修改他们了。
#include<bits/stdc++.h>
using namespace std;
vector<bool> emptyVal = {false, false, false, false};
// 辅助函数
struct dataSet {
vector<vector<vector<bool>>> boolmap;
vector<vector<int>> presentStatus;
dataSet(vector<vector<vector<bool>>> iptBmap, vector<vector<int>> iptStatus) {
this->boolmap = iptBmap;
this->presentStatus = iptStatus;
}
};
int blockJudge(int y, int x) {
int block;
if (y <= 1 && x <= 1) block = 0;
if (y <= 1 && (1 < x && x <= 3)) block = 1;
if ((1 < y && y <= 3) && x <= 1) block = 2;
if ((1 < y && y <= 3) && (1 < x && x <= 3)) block = 3;
return block;
}
void changeStatus(dataSet& set, int y, int x, int i, bool s) {
int block = blockJudge(y, x);
set.boolmap[0][y][i] = s;
set.boolmap[1][x][i] = s;
set.boolmap[2][block][i] = s;
}
bool testStatus(dataSet set, int y, int x, int i, bool s) {
vector<vector<vector<bool>>> b = set.boolmap;
int block = blockJudge(y, x);
return (b[0][y][i] == s && b[1][x][i] == s && b[2][block][i] == s);
}
bool testSpace(dataSet& set, int y, int x) {
return (set.presentStatus[y][x] == 0);
}
dataSet generateJudgemap(vector<vector<int>>& problem) {
vector<vector<vector<bool>>> b(3, {emptyVal, emptyVal, emptyVal, emptyVal});
vector<vector<int>> solution(4, {0, 0, 0, 0});
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
if (problem[i][j] != 0) {
int index = problem[i][j] - 1;
int block = blockJudge(i, j);
b[0][i][index] = true;
b[1][j][index] = true;
b[2][block][index] = true;
solution[i][j] = problem[i][j];
}
}
}
return dataSet(b, solution);
}
int getNumber(dataSet& set, int y, int x) {
return set.presentStatus[y][x];
}
vector<vector<int>> generateCompleledSudoku(dataSet& set) {
return set.presentStatus;
}
// 深度优先搜索
void dfs(int n, dataSet iptset, int& ans) {
dataSet set = iptset;
if (n > 16) {
vector<vector<int>> answer = generateCompleledSudoku(set);
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
printf("%2d", answer[i][j]);
}
cout << endl;
}
ans++;
cout << "----------" << endl;
return;
}
int y = (n - 1) / 4; // 搜索所在的行数
int x = (n - 1) % 4; // 搜索所在的列数
if (!testSpace(set, y, x)) {
dfs(n + 1, set, ans);
return;
}
for (int i = 0; i < 4; i++) {
if (testStatus(set, y, x, i, false)) {
set.presentStatus[y][x] = i + 1;
changeStatus(set, y, x, i, true);
dfs(n + 1, set, ans);
set = iptset;
}
}
}
int main() {
vector<vector<int>> problem(4,vector<int>(4,0));
cout << "请输入四阶数独题面:" << endl;
for(int i=0;i<4;i++){
for(int j=0;j<4;j++){
cin >> problem[i][j];
}
}
dataSet set = generateJudgemap(problem);
dataSet ori = set;
int ans = 0;
cout << "四阶数独的解为:" << endl;
dfs(1, set, ans);
cout << "解的个数为:" << ans << endl;
cout << "输入任意字符退出...";
char exit;
cin >> exit;
return 0;
}
有意义的变量名和函数名尽管长了一点,但确实能帮助开发人员快速梳理程序结构,也利于修复漏洞,后续维护。至少能帮助我在第一次尝试编写该程序犯错后快速修正错误的代码。
感谢你能看到这里。