课前叨叨
数独是个很古老的数学问题,很早之前就有了,最早出现于 17 世纪[1],关于解数独有一些方法,有很多,比较出名的一个算法叫舞蹈连。最开始我们先不讨论很复杂的算法。从最开始的最简单的地方来完成解数独。
精确覆盖[2]
数独的规则就是在每横每竖每个小的方格,都有不同的数字,数字的范围由数独的阶数决定。在检测每个数字是否重复或者空缺的时候出现的问题就是精确覆盖问题,但是精确覆盖的问题,不是完全这么描述的,维基百科中有详细解释。
在此我写了这样一个算法,不是舞蹈链的方式,是针对数独实现的方式。
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#define N 3 // suduku box length
#define M (N * N) // sudoku row length
bool exact_cover(char str[M]) {
uint16_t a = (1 << M) - 1;
for (size_t i = 0; i < M; i++) {
if (str[i] != '.') {
a ^= 1 << (str[i] - 49);
}
}
return a == 0;
}
bool exact_cover_8(char str[M]) {
uint8_t a = UINT8_MAX;
bool has_9 = false;
for (size_t i = 0; i < M; i++) {
if (str[i] != '.') {
if (str[i] - 49 != 8) {
a ^= 1 << (str[i] - 49);
} else {
has_9 = true;
}
}
}
return a == 0 && has_9;
}
int main(int argc, char const *argv[]) {
char str[] = "123456789";
printf("%s=%s\n", str, exact_cover(str) ? "true" : "false");
printf("%s=%s\n", str, exact_cover_8(str) ? "true" : "false");
return 0;
}
算法的思想是这样的,我们需要精确覆盖的数字就是 1-9,所以需要 9 位二进制位来表示每一位上的数字是否被覆盖,最开始这个 9 位二进制数全部初始化为 1,如果数独中 2 被覆盖了,那么这个二进制数的第二位被重置成 0,当最终的数被所有数异或得到的结果是否为 0,就是我们需要的得到的结果。
C 中有两种 int 类型符合我们的需要 int16 和 int8,但是 int8 少一位,int16 又多了,所以我们可以用 int16 实现一个很简单的精确覆盖的算法,int8 的话需要配合一个 bool 值来实现我们的算法。
课后叨叨
既然这个精确覆盖的算法做完了,那么我们就可以做 LeetCode 第 27 题[3]解数独的这一题了,通过回溯算法来实现,每次我们填进数独一个数字都需要验证一下数独是不是一个有效的数独,后边就分几期逐步的来实现这一步。之前我写过一个解数独的命令行版并且是可视化的版本[4]。