发布时间:2024-01-23 12:00
自己学校的作业,删了觉得太可惜了, 不如发出来一起学习
数独的定义
数独(shù dú)是源自18世纪瑞士的一种数学游戏。是一种运用纸、笔进行演算的逻辑游戏。玩家需要根据9×9盘面上的已知数字,推理出所有剩余空格的数字,并满足每一行、每一列、每一个粗线宫(3*3)内的数字均含1-9,不重复 [1] 。
数独盘面是个九宫,每一宫又分为九个小格。在这八十一格中给出一定的已知数字和解题条件,利用逻辑和推理,在其他的空格上填入1-9的数字。使1-9每个数字在每一行、每一列和每一宫中都只出现一次,所以又称“九宫格”。
图形展示:
首先, 这是一道搜索类的题目, 采用 dfs 搜索会比较方便的写出代码
但是如果只有 dfs 来搜索所有的状态, 进而来判断答案是肯定不行的, 因为每一位都可以填写 1~9 ,即9个状态, 因而最坏的情况则是 9 * 9 * 9 * … * 9 即 O ( 9 n ) O(9^{n}) O(9n)的复杂度, 为指数级别
那么要优化这个情况, 则必须要进行剪枝, dfs + 剪枝优化 也称为回溯法. 虽然算法的最坏的时间复杂度依然不变, 但是通过优化, 可以使得算法很难达到得最坏的情况, 可以近似的看成一个可接受的复杂度.
那么本题用到的剪枝优化则是
位运算
由于每放置一位均需要 check()
一次, 如果 check()
为真则可以放置当前的数字在当前的位置上
因此需要对 check()
做一个优化:
设置一个 line[9]
, col[9]
和ceil[3][3]
数组若第 i
行可以放置数字 j
, 则 line[i] >> j & 1 == 1
(即line[]
的第i
个元素的第j
位为1
), col[], ceil[][]
同理. ceil[i][j]
表示第i行,第j列
个单元格可以放置的数字
初始化 : 将line,col, ceil
的每一个元素的前9
位均设置为1
运算: 若第i
行, j
列存在了数字 k
, 则line[i] -= 1 << k
line[i] -= 1 << k; // 第i行的第k位变为0
col[j] -= 1 << k; // 第j列的第k位变为0
ceil[i / 3][j / 3] -= 1 << k; // i, j所属的单元格的第k位变为0
排除等效冗余
任意一个状态下,我们只需要找一个位置填数即可,而不是找所有的位置和可填的数字
因此我们在dfs
的时候若查找到了, 则返回true
, 否则返回false
bool Compute::dfs(int cnt)
在递归时则是:
if (dfs(cnt - 1)) return true;
优化搜索循序
很明显,我们肯定是从当前能填合法数字最少的位置开始填数字, 即第i
行,j
列的合法状态的二进制中1
的数量最少的一个
当前的合法的状态为第i
行的合法状态和第j
列的合法状态和i,j
所属单元格的合法状态的并集:
line[i] & col[j] & ceil[i / 3][j / 3];
然后选取其中的1
所在的位置, 并获取它代表的数字
1
所在的位置有一个运算叫做lowbit
运算, 用它就可以在 O ( 1 ) O(1) O(1)的时间以内获取当前最小的1所在的位,而不是用 O ( n ) O(n) O(n)的复杂度来遍历这个数字的每一位然后计算1
的个数, 并取得个数最小的点, 然后遍历该点
compute.h
#pragma once
#include
static class Compute {
public:
Compute(const char*);
Compute(std::string);
operator const char* ();
bool has_answer = false;
protected:
int get(int, int);
void draw(int, int, int, bool);
void init();
int lowbit(int);
bool dfs(int);
private:
const static int N = 9, M = 1 << N;
int ones[M];
int ceil[3][3], map[M];
int line[N], col[N];
char str[N * N + 1];
int cnt = 0;
};
compute.cpp
#include "compute.h"
Compute::Compute(std::string temp) : Compute((const char *)temp.c_str()) {
;
}
Compute::Compute(const char* temp) {
for (int i = 0; i <= N * N; i++) str[i] = temp[i];
for (int i = 0; i < N; i++) map[1 << i] = i;
for (int i = 0; i < M; i++) {
for (int j = 0; j < N; j++) {
ones[i] += i >> j & 1;
}
}
init();
for (int i = 0, k = 0; i < N; i++)
for (int j = 0; j < N; j++, k++) {
if (str[k] != '.') {
int t = str[k] - '1';
draw(i, j, t, true);
}
else {
cnt++;
}
}
dfs(cnt);
}
Compute::operator const char* () {
return str;
}
int Compute::get(int x, int y) {
return line[x] & col[y] & ceil[x / 3][y / 3];
}
void Compute::draw(int x, int y, int t, bool is_set) {
if (is_set) str[x * N + y] = t + '1';
else str[x * N + y] = '.';
int v = 1 << t;
if (!is_set) v = -v;
line[x] -= v;
col[y] -= v;
ceil[x / 3][y / 3] -= v;
}
void Compute::init() {
cnt = 0;
for (int i = 0; i < N; i++) {
line[i] = (1 << N) - 1;
col[i] = (1 << N) - 1;
}
for (int i = 0; i < 3; i++)
for (int j = 0; j < 3; j++) {
ceil[i][j] = (1 << N) - 1;
}
}
int Compute::lowbit(int x) {
return x & -x;
}
bool Compute::dfs(int cnt) {
if (cnt == 0) {
has_answer = true;
return true;
}
int minv = 10;
int a = -1, b = -1;
for (int i = 0; i < N; i++)
for (int j = 0; j < N; j++) {
if (str[i * N + j] == '.') {
int state = get(i, j);
if (ones[state] < minv) {
minv = ones[state];
a = i, b = j;
}
}
}
if (a == -1 || b == -1) return false;
int state = get(a, b);
if (state < 0 || state >((1 << N) - 1)) return false;
for (int i = state; i; i -= lowbit(i)) {
int t = map[lowbit(i)];
draw(a, b, t, true);
if (dfs(cnt - 1)) return true;
draw(a, b, t, false);
}
return false;
}
数独.cpp
#include
#include
#include "Compute.h"
#include
using namespace std;
void show(const char* temp) {
printf("=======================================\n");
for (int i = 0; i < 9; i++) {
for (int j = 0; j < 9; j++) {
char a = temp[i * 9 + j];
if (a == '.') a = ' ';
if ((j + 1) % 3 == 0)
printf(" %2c |", a);
else
printf(" %2c ", a);
}
printf("\n");
if ((i + 1) % 3 == 0)
printf("=======================================\n");
}
}
int main() {
while (1) {
char str[] = "4.....8.5.3..........7......2.....6.....8.4......1.......6.3.7.5..2.....1.4......";
cout << "请输入棋盘(若为空, 则输入 . )\n示例:";
cout << str << endl;
cin >> str;
show(str);
time_t start = clock();
Compute ans = str;
time_t ed = clock();
if (ans.has_answer) {
cout << "存在一组解为:" << endl;
show(ans);
}
else {
cout << "不存在一组解" << endl;
}
cout << "运算时间为: " << ed - start << " ms" << endl;
}
return 0;
}
可以看到,运算时间还是很可观的