数独求解 Sudoku Solver

142 篇文章 20 订阅
51 篇文章 0 订阅

数独游戏是个经典的游戏。现在我要通过程序的方法才自动求解一个数独。这个问题在《Puzzles for Programmers and Pros》一书中有讲解。

思路:

最直接的方法是,可以用backtracking进行递归尝试。一旦探测到错误就回溯,最终将在递归的最深处发现正解。

注意backtracking是可以用引用类型参数进行递归深入的,但是递归回溯出来的时候一定要将改变的值回复回原样,这样下一轮的尝试才不会受到影响。


纯粹的递归尝试就相当于穷举法,速度非常慢。想想人脑做数独的时候是怎么进行的?肯定不是上面的方法。而是先找出最容易写出答案的。对于一个位置,如果发现其同行、同列、同方格中已经把9个数的8个都使用过了,那么必然的,这个位置具有唯一的答案。依照这个方法进行重复,可以先把数独填上若干。

当找不到具有唯一确定答案的位置的时候,意味着就必须要尝试性的推测了。这时候,我就找推测范围最小的位置进行尝试。因为范围越小,成功概率越大。这样可以尽量减少错误的递归次数。


代码:

#include <iostream>
#include <vector>
#include <string>
using namespace std;

vector<char> checker(vector<vector<char> > &board, int row, int col)
{
    vector<char> posibile;
    int bitmap[9];
    memset(bitmap, 0, sizeof(bitmap));
    int i;
    for(i=0;i<9;i++)
        if(board[row][i] != '.')
            bitmap[board[row][i] -'1'] = 1;
    
    for(i=0;i<9;i++)
        if(board[i][col] != '.')
            bitmap[board[i][col] - '1'] = 1;
    
    int row_base = (row/3)*3;
    int col_base = (col/3)*3;
    for(int i = 0;i<3;i++)
        for(int j=0;j<3;j++)
        {
            if(board[row_base + i][col_base + j] != '.')
                bitmap[board[row_base + i][col_base + j] - '1'] = 1;
        }

    for(i=0;i<9;i++)
        if(bitmap[i] == 0)
        {
            posibile.push_back(char('1' + i));
        }
    return posibile;
}

bool fun(vector<vector<char> > &board)
{
    size_t min = 10;
    vector<char> minoption;
    int mini, minj;
    // 寻找一个推测范围最小的位置
    for(int i=0;i<9;i++)
        for(int j=0;j<9;j++)
        {
            if(board[i][j] != '.')
                continue;
            
            vector<char> option = checker(board, i, j);
            if(option.size() < min)
            {
                min = option.size();
                minoption = option;
                mini = i;
                minj = j;
            }
        }
    
    if(min == 10)
        return true; //min没有改变,可见所有都填满
    
    // 对推测范围最小的位置进行递归尝试
    for(int k=0;k<minoption.size();k++)
    {
        board[mini][minj] = minoption[k];
        if(fun(board))
            return true;
        board[mini][minj] = '.';
    }
    return false; //这儿很关键!!因为并非option里一定有正确答案,因为可能是之前的数放错了

}

void solveSudoku(vector<vector<char> > &board)
{
    if(board.size() != 9)
        return;
    bool flag = true;

    while(flag)
    {
        flag = false;
        for(int i=0;i<9;i++)
        {
            for(int j=0;j<9;j++)
            {
                if(board[i][j] == '.')
                {
                    vector<char> tmp = checker(board, i, j);
                    if(tmp.size() == 1)
                    {
                        board[i][j] = tmp[0];
                        flag = true;
                    }
                }
            }
        }
    }
    
    fun(board);
}


void show(vector<vector<char> > &board)
{
    for(int i=0;i<9;i++)
    {
        for(int j=0;j<9;j++)
            cout<<board[i][j]<<" ";
        cout<<endl;
    }
    cout<<endl;
}


int main(int argc, const char * argv[])
{
    vector<vector<char> > board;
    vector<char> t;
    string s[9] = {"..9748...","7........",".2.1.9...","..7...24.",".64.1.59.",".98...3..","...8.3.2.","........6","...2759.."};
    
    for(int i=0;i<9;i++)
    {
        for(int j=0;j<9;j++)
            t.push_back(s[i][j]);
        board.push_back(t);
        t.clear();
    }
    show(board);
    cout<<"start..."<<endl;
    solveSudoku(board);
    cout<<"over"<<endl;
    show(board);
    return 0;
}




  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值