zoukankan      html  css  js  c++  java
  • 解数独(Leetcode-37 / HDU-1426)/回溯/状态压缩

    解数独(Leetcode-37 / HDU-1426)/回溯/状态压缩

    问题描述

    编写一个程序,通过填充空格来解决数独问题。

    • 一个数独的解法需遵循如下规则:
      • 数字 1-9 在每一行只能出现一次。
      • 数字 1-9 在每一列只能出现一次。
      • 数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。
      • 空白格用 '.' 表示。

    img

    一个数独。

    img

    答案被标成红色。

    提示:

    给定的数独序列只包含数字 1-9 和字符 '.' 。
    你可以假设给定的数独只有唯一解。
    给定数独永远是 9x9 形式的。


    DFS+回溯

    对于每个格子,只要没有填写数字就进行深搜,判断1-9中哪个可以填入

    填入后继续深搜然后回溯

    这样可以遍历到所有可能的结果

    void dfs(int step) {	//step为当前所对应选的步数
        if(step==cnt) {
            // 如果cnt个点都已经找完了
            for(int i = 0;i < 9;i++) {
                for(int j = 0;j < 9;j++) {
                    cout << mp[i][j] << " ";
                }
                cout << endl;
            }
            return;
        }
        for(int i = 1;i <= 9;i++) {
            if(check(step,i)) {	
                // 判重 + 剪枝 (判断i是否可以填入)
                mp[node[step].x][node[step].y] = i;
                dfs(step+1);
                // 回溯
                mp[node[step].x][node[step].y] = 0;	
            }
        }
        return;
    }
    

    判重 + 剪枝

    判断行列以及3x3格子中有无重复

    对3x3方格内有无重复进行判断

    如果当前的坐标为 ( i , j ) , 令x=i/3x3,y=j/3x3

    则点(x , y)为该点所对应的3x3格子的最左上角的点, 遍历即可

    // 对行列重复元素进行判断
    for(int i = 0;i < 9;i++) {
        // 判断行和列中是否有重复
        if(mp[node[step].x][i]==k||mp[i][node[step].y]==k) 
            return false;
    }
    // 对该元素所在方格进行判断
    int n = node[step].x/3*3;   //
    int m = node[step].y/3*3;   //
    for(int i = n;i < n+3;i++) {
        for(int j = m;j < m+3;j++) {
            if(mp[i][j]==k) return false;
        }
    }
    

    完整代码

    #include <iostream>
    using namespace std;
    // 存图
    int mp[10][10];
    char temp;
    int cnt;int flag = 0;
    struct {
        // ?点的坐标
        int x,y;
    } node[100];
    bool check(int step,int k) {
        // 判断数字k能否用在node[k]中
        for(int i = 0;i < 9;i++) {
            // 判断行和列中是否有重复
            if(mp[node[step].x][i]==k||mp[i][node[step].y]==k) 
                return false;
        }
        // 判断3x3方块中是否存在
        int n = node[step].x/3*3;   //行
        int m = node[step].y/3*3;   //列
        for(int i = n;i < n+3;i++) {
            for(int j = m;j < m+3;j++) {
                if(mp[i][j]==k) return false;
            }
        }
        return true;
    }
    void dfs(int step) {
        if(step==cnt) {
            for(int i = 0;i < 9;i++) {
                for(int j = 0;j < 9;j++) {
                    cout << mp[i][j] << " ";
                }   //注意这里(格式-暂定)
                cout << endl;
            }
            return;
        }
        for(int i = 1;i <= 9;i++) {
            if(check(step,i)) {
                mp[node[step].x][node[step].y] = i;
                dfs(step+1);
                mp[node[step].x][node[step].y] = 0;
            }
        }
        return;
    }
    int main()
    {
        freopen("in.txt","r",stdin);
        while(cin >> temp) {
            cnt=0;
            if(temp=='?') {
                node[cnt].x = 0;
                node[cnt++].y = 0;
                mp[0][0] = 0;
            } else {
                mp[0][0] = temp-'0';
            }
            for(int i = 0;i < 9;i++) {
                for(int j = 0;j < 9;j++) {
                    if(i==0&&j==0)  continue;
                    cin >> temp;
                    if(temp=='?') {
                        node[cnt].x = i;
                        node[cnt++].y = j;
                        mp[i][j] = 0;
                    } else {
                        mp[i][j] = temp-'0';
                    }
                }
            }
            if(flag)    cout << endl;
            dfs(0);
        }
        return 0;
    }
    
    

    hdu 1426 样例

    输入

    7 1 2 ? 6 ? 3 5 8
    ? 6 5 2 ? 7 1 ? 4
    ? ? 8 5 1 3 6 7 2
    9 2 4 ? 5 6 ? 3 7
    5 ? 6 ? ? ? 2 4 1
    1 ? 3 7 2 ? 9 ? 5
    ? ? 1 9 7 5 4 8 6
    6 ? 7 8 3 ? 5 1 9
    8 5 9 ? 4 ? ? 2 3
    

    输出

    7 1 2 4 6 9 3 5 8
    3 6 5 2 8 7 1 9 4
    4 9 8 5 1 3 6 7 2
    9 2 4 1 5 6 8 3 7
    5 7 6 3 9 8 2 4 1
    1 8 3 7 2 4 9 6 5
    2 3 1 9 7 5 4 8 6
    6 4 7 8 3 2 5 1 9
    8 5 9 6 4 1 7 2 3
    

    状态压缩

    用位集表示状态

    用位集(bitset)表示某一个点(x,y)的状态, 即点(x,y)可以填那些数

    row表示每一行状态, col表示每一列状态, cell表示3x3格子中的状态

    例如: row[2] = 110101011

    每一位对应的数字 123456789

    表示 第三行(注意下标)已经填了1,2,4,6,8,9 则它可能填的状态为~row[2] (当然这里仅对行进行了讨论)

    此外, 如果 col[5] = 111101111 代表第六列已经填了1,2,3,4,6,7,8,9

    则 row[2] | col[5] = 111101111 代表第三行和第六列相交的那个点只能填入5 (要取反~)

    下面的注释写的很详细了!

    using namespace std;
    class Solution {
    public:
        bitset<9> getPossibleStatus(int x, int y) {
            // 位运算 对 点(x,y) 所在的行和列和格子进行压缩  返回所得可能填的值
            // 注意取反
            return ~(rows[x] | cols[y] | cells[x / 3][y / 3]);  
        }
    
        vector<int> getNext(vector<vector<char>>& board) {
            //  每次都使用都选择能填的数字最少的格子开始填
            vector<int> ret;
            int minCnt = 10;
            for (int i = 0; i < board.size(); i++) {
                for (int j = 0; j < board[i].size(); j++) {
                    // 不是要填的直接continue
                    if (board[i][j] != '.') continue;
                    // 当前点能填的状态的压缩
                    auto cur = getPossibleStatus(i, j);
                    // 要找到状态最少的哪个
                    if (cur.count() >= minCnt) continue;
                    ret = { i, j };
                    minCnt = cur.count();
                }
            }
            // 返回的是一个点
            return ret;
        }
    
        void fillNum(int x, int y, int n, bool fillFlag) {
            // 根据fillFlag 将n填入点(x,y)
            // fillFlag为true为填入 ,, fillFlag为false为消除(回溯)
            rows[x][n] = (fillFlag) ? 1 : 0;
            cols[y][n] = (fillFlag) ? 1 : 0;
            cells[x/3][y/3][n] = (fillFlag) ? 1: 0;
        }
        
        bool dfs(vector<vector<char>>& board, int cnt) {
            // cnt为0时,所有的 . 都已经填完
            if (cnt == 0) return true;
            // 寻找最优状态准备填入
            auto next = getNext(board);
            // 解出所有可能状态
            auto bits = getPossibleStatus(next[0], next[1]);
            for (int n = 0; n < bits.size(); n++) {
                // 对所有可能状态进行尝试
                // 为0说明该位已被填入   不做考虑
                // bitset::test()是C++ STL中的一个内置函数,用于测试是否设置了给定索引处的位。
                if (!bits.test(n)) continue;
                // 测试 将 n 填入填入点(next[0], next[1])
                fillNum(next[0], next[1], n, true);
                board[next[0]][next[1]] = n + '1';  // n 取值为0-8  
                // dfs
                if (dfs(board, cnt - 1)) return true;
                // 回溯
                board[next[0]][next[1]] = '.';
                fillNum(next[0], next[1], n, false);
            }
            return false;
        }
    
        void solveSudoku(vector<vector<char>>& board) {
            // 行对应的状态
            rows = vector<bitset<9>>(9, bitset<9>());
            // 列对应的状态
            cols = vector<bitset<9>>(9, bitset<9>());
            // 3x3 方格对应的状态
            cells = vector<vector<bitset<9>>>(3, vector<bitset<9>>(3, bitset<9>()));
            // cnt为要填的总数
            int cnt = 0;
            for (int i = 0; i < board.size(); i++) {
                for (int j = 0; j < board[i].size(); j++) {
                    cnt += (board[i][j] == '.');
                    if (board[i][j] == '.') continue;
                    // 为何 减去 1?   如果例如board[i][j]为 '1' 那么减去 '1' 即为 整型0 对应着第一位(一共九位),即不用移动,表示1
                    int n = board[i][j] - '1';
                    // 索引零对应的是存储的最后一位,cout输出和按照索引输出会得到互为倒序的结果~
                    rows[i] |= (1 << n);
                    cols[j] |= (1 << n);
                    cells[i / 3][j / 3] |= (1 << n);c
                }
            }
            dfs(board, cnt);
        }
    private:
        vector<bitset<9>> rows;
        vector<bitset<9>> cols;
        vector<vector<bitset<9>>> cells;
    };
    
  • 相关阅读:
    bzoj3670 [Noi2014]动物园
    bzoj2882 工艺
    bzoj3097 Hash Killer I
    bzoj3729 Gty的游戏
    【BZOJ4555】[TJOI&HEOI2016]求和 斯特林数+NTT
    【bzoj4869】[Shoi2017]相逢是问候 线段树+扩展欧拉定理
    【BZOJ1853】[Scoi2010]幸运数字 容斥原理+搜索
    【BZOJ2839】集合计数 容斥原理+组合数
    【BZOJ3622】已经没什么好害怕的了 容斥原理+dp
    【BZOJ3589】动态树 树链剖分+线段树
  • 原文地址:https://www.cnblogs.com/xun-/p/13693361.html
Copyright © 2011-2022 走看看