数独 (dfs)

原题链接

数独是一种传统益智游戏,你需要把一个 9 t i m e s 9 9 \\times 9 9times9 的数独补充完整,使得数独中每行、每列、每个 3 t i m e s 3 3 \\times 3 3times3 的九宫格内数字 1 s i m 9 1 \\sim 9 1sim9 均恰好出现一次。

请编写一个程序填写数独。

输入格式

输入包含多组测试用例。

每个测试用例占一行,包含 81 81 81 个字符,代表数独的 81 81 81 个格内数据(顺序总体由上到下,同行由左到右)。

每个字符都是一个数字( 1 − 9 1-9 19)或一个 .(表示尚未填充)。

您可以假设输入中的每个谜题都只有一个解决方案。

文件结尾处为包含单词 end 的单行,表示输入结束。

输出格式

每个测试用例,输出一行数据,代表填充完全后的数独。

输入样例:
4.....8.5.3..........7......2.....6.....8.4......1.......6.3.7.5..2.....1.4......
......52..8.4......3...9...5.1...6..2..7........3.....6...1..........7.4.......3.
end
输出样例:
417369825632158947958724316825437169791586432346912758289643571573291684164875293
416837529982465371735129468571298643293746185864351297647913852359682714128574936

算法

(dfs)

剪枝与优化:

  • 位运算优化:在这一个问题里,位运算可以快速的帮助我们去判断一个行,列,宫里是否存在一个和它相同的数。

  • 可行性剪枝:我们先看一下这一个格子填在这里,是不是在所在的行,所在的列,所在的宫里都没有出现过。

  • 最优性剪枝:我们可以随意选择一个空格子。假设我们枚举到了两个格子,有一个格子有 99 种填法,有一个格子有两种填法,那么我们应该先去选择填法较少的那一个格子


另外这里 dfs 用 bool 型的好处就是可以找到一个解后一路 return 掉

C++ 代码
#include 
#include 
#include 
#include 
using namespace std;
typedef long long ll;
const int N = 9,M = 1 << N;
int ones[M],map[M];//分别代表有多少个 1 和打表数组 
int row[N],col[N],cell[3][3];
char str[N * N];

#define lowbit(x) (x & -x) 
void init()// 初始化(将所有位置都初始化可以填数的状态)
{
    for (int i = 0;i < N;i ++ ) row[i] = col[i] = (1 << N) - 1;
    for (int i = 0;i < 3;i ++ ) 
        for (int j = 0;j < 3;j ++ ) 
            cell[i][j] = (1 << N) - 1;// 每个3 * 3的小方格也用二进制来优化(刚开始也都为1)
}
void draw(int x,int y,int t,bool is_set) // 在(x, y)的位置上(is_set)<是/否>填t的操作 
{
    if (is_set) str[x * N + y] = t + '1';
    else str[x * N + y] = '.';
    
    int v = 1 << t;// 找到该数对应二进制之后的位置的数
    if (!is_set) v = -v;//不填的话加回来,以保持原样
    row[x] -= v;
    col[y] -= v;
    cell[x / 3][y / 3] -= v;
}
int get(int x,int y)
{
    return row[x] & col[y] & cell[x / 3][y / 3];
}
bool dfs(int u)
{
    if (!u) return true;
    int x,y,minv = 10;// x, y记录枚举方案最少的位置的x, y,记录当前最少枚举方案

    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) {
                    x = i,y = j;
                    minv = ones[state];
                }
            }
        }
    }
    int state = get(x,y);// 找到最少枚举方案对应的位置的能填的数的状态
    for (int i = state;i;i -= lowbit(i)) {
        int t = map[lowbit(i)];
        draw(x,y,t,true);// 找到该位置上能填的数
        if (dfs(u - 1)) return true; // 如果找到一组可行解就一路return true下去
        draw(x,y,t,false);
    }
    return false;
}
int main()
{
    for (int i = 0;i < N;i ++ ) map[1 << i] = i;//预处理每个以二为底的数有多少
    for (int i = 0;i < 1 << N;i ++ ) {
        for (int j = 0;j < N;j ++ ) {
            ones[i] += i >> j & 1;//每个数的二进制表示有多少个1
        }
    }

    while (cin >> str && str[0] != 'e') {
        init();

        int cnt = 0;// 记录有几个空格需要填数
        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);
        puts(str);
    }
    return 0;
}

你可能感兴趣的:(dfs,深度优先,算法)