题目描述

图片说明

输入格式

The input test file will contain multiple cases. Each test case consists of a single line containing 81 characters, which represent the 81 squares of the Sudoku grid, given one row at a time. Each character is either a digit (from 1 to 9) or a period (used to indicate an unfilled square). You may assume that each puzzle in the input will have exactly one solution. The end-of-file is denoted by a single line containing the word “end”.
注意: 本题输入数据量较大,cin, getline可能会超时,建议使用scanf。

输出格式

For each test case, print a line representing the completed Sudoku puzzle.

输入样例

.2738..1..1...6735.......293.5692.8...........6.1745.364.......9518...7..8..6534.
......52..8.4......3...9...5.1...6..2..7........3.....6...1..........7.4.......3.
end

输出样例

527389416819426735436751829375692184194538267268174593643217958951843672782965341
416837529982465371735129468571298643293746185864351297647913852359682714128574936

题目思路

题目整体思路很容易想到:对于每一个空位置,尝试每一个可填数,填入后与同行、同列和同块的所有数字进行比对,看是否有重复;若有,更改当前填入数据,直到该位置无可能性,返回上一个填写的位置,更改数据。

难点1 怎样实现与同行同列同块数字的比较?

首先,我们可以通过一维数组存储输出输入的字符数组。这会为后面的遍历和查找带来方便。对该数组进行扩充,变成二维数组left,第一维存放该位置的数字,第二维存放该位置可放数字的可能性(如果数字确定可能性就为1)。
该位置可放数字的可能性通过函数assign对数组的更新来确定

bool assign(int left[][10],int idx,int lefted)
{
    //假设idx位置的值是lefted 
    left[idx][0]=1;
    for(int i=1;i<10;i++)
    {
        left[idx][i]=0;
    }
    left[idx][lefted]=1;

    int row=idx/9,col=idx%9;
    //在相应行判断有无重复元素 
    for(int i=0;i<9;i++)
    {
        int now=row*9+i;
        //若当前判断位置不是自身且该位置lefted是唯一可能性,说明该假设错误 
        if(now!=idx&&!remove(left,now,lefted))
        {
            return false;
        }    
    }
    //在相应列判断有无重复元素 
    for(int i=0;i<9;i++)
    {
        int now=i*9+col;
        if(now!=idx&&!remove(left,now,lefted))
        {
            return false;
        }
    }
    //在相应块判断有无重复元素 
    for(int i=0;i<3;i++)
    {
        for(int j=0;j<3;j++)
        {
            int now=((row/3)*3+i)*9+(col/3)*3+j;
            if(now!=idx&&!remove(left,now,lefted))
            {
                return false;
            }
        }
    }
    return true;
}

难点2 怎样修改某位置的可能数字?

通过remove函数实现,注意,修改时要对可能性为1和2的位置进行特判

//从value数组中移除 value[idx][lefted]的可能性 
bool remove(int left[][10],int idx,int lefted)
{ 
    if(left[idx][lefted]==1)//若shudu[idx]有取lefted的可能 
    {
        left[idx][lefted]=0;
        left[idx][0]--;
        //该位置没有其他可能性了
        if(left[idx][0]==0)
        {
            return false;
        }
        //该位置只有一种可能性了
        else if(left[idx][0]==1)
        { 
            for(int i=1;i<10;i++)
            {
                if(left[idx][i]!=0)
                {
                    if(!assign(left,idx,i))
                        return false;
                    else
                        return true;
                }
            }    
        }
        else
            return true; 
    }
    else 
        return true; 
}

难点3 怎样修改递归函数使得时间复杂度减低?

在选择试探的空位时,我们可以采用每次优先选择可能数字最少的进行试探,这样可以在极大程度上减少我们的搜索量。(快速失败法)

//选择shudu[]的空缺位置中可能性最少的位置进行匹配 
bool search_match(int left[][10])
{    
    int minx=0,minl=9;
    int flag=1;
    for(int i=0;i<81;i++)
    {
        if(left[i][0]!=1)
        {
            flag=0;
            if(left[i][0]<minl)
            {
                minl=left[i][0];
                minx=i;
            }
        }
    }
    //当所有位置的可能性都为1时说明成功,结束递归 
    if(flag==1)
    {
        complete_shudu(left);
        return true;
    }

    for(int i=1;i<10;i++)
    {

        //遍历该位置的所有可能性 
        if(left[minx][i]!=0)
        {
            //设临时数组,将left[][]拷贝过来  
            int left_temp[81][10];
            for(int j=0;j<81;j++)
            {
                for(int k=0;k<10;k++)
                {
                    left_temp[j][k]=left[j][k];
                }
            }
            //若i可以成功插入,且i插入后的数独可以完整填出来 
            if(assign(left_temp,minx,i))
            {
                if(search_match(left_temp))
                    return true;                
            }
            //若i不可以插入,则在left数组中删掉该可能性 
            left[minx][0]--;
            left[minx][i]=0;

        }    
    }
    return false;
}

完整代码

#include<stdio.h>
#include<string.h>

char shudu[100];//输入的数独数组
int left[82][10]={0};//对于数独第i号格子shudu[i][0]表示其可选择的数,shudu[i][j]表示是否可选数字j 
bool assign(int value[][10],int idx,int lefted);

//对left数组进行初始化
void init(int left[82][10],char s[100])
{
    for(int i=0;i<81;i++)
    {
        if(s[i]=='.')
        {
            left[i][0]=9;
            for(int j=1;j<10;j++)
            {
                left[i][j]=1;
            }
        }
        else
        {
            left[i][0]=1;
            for(int j=1;j<10;j++)
            {
                left[i][j]=0;
            }
            left[i][(int)(s[i]-'0')]=1;
        }
    }
}
//打印数独数组
void print(char s[100])
{
    for(int i=0;i<81;i++)
    {
        printf("%c",s[i]);
    }
    printf("\n");
}
//将整理好的数字填充到shudu数组中
void complete_shudu(int left[][10])
{
    for(int i=0;i<81;i++)
    {
        for(int j=1;j<10;j++)
        {
            if(left[i][j]!=0)
            {
                shudu[i]=j+'0';
                break;
            }
        }
    }
}
//从value数组中移除 value[idx][lefted]的可能性 
bool remove(int left[][10],int idx,int lefted)
{ 
    if(left[idx][lefted]==1)//若shudu[idx]有取lefted的可能 
    {
        left[idx][lefted]=0;
        left[idx][0]--;
        if(left[idx][0]==0)
        {
            return false;
        }
        else if(left[idx][0]==1)
        { 
            for(int i=1;i<10;i++)
            {
                if(left[idx][i]!=0)
                {
                    if(!assign(left,idx,i))
                        return false;
                    else
                        return true;
                }
            }    
        }
        else
            return true; 
    }
    else 
        return true; 
}
//更新shudu[idx]的值 ,并更新left表 
bool assign(int left[][10],int idx,int lefted)
{
    //假设idx位置的值是lefted 
    left[idx][0]=1;
    for(int i=1;i<10;i++)
    {
        left[idx][i]=0;
    }
    left[idx][lefted]=1;

    int row=idx/9,col=idx%9;
    //在相应行判断有无重复元素 
    for(int i=0;i<9;i++)
    {
        int now=row*9+i;
        //若当前判断位置不是自身且该位置lefted是唯一可能性,说明该假设错误 
        if(now!=idx&&!remove(left,now,lefted))
        {
            return false;
        }    
    }
    //在相应列判断有无重复元素 
    for(int i=0;i<9;i++)
    {
        int now=i*9+col;
        if(now!=idx&&!remove(left,now,lefted))
        {
            return false;
        }
    }
    //在相应块判断有无重复元素 
    for(int i=0;i<3;i++)
    {
        for(int j=0;j<3;j++)
        {
            int now=((row/3)*3+i)*9+(col/3)*3+j;
            if(now!=idx&&!remove(left,now,lefted))
            {
                return false;
            }
        }
    }
    return true;
}
//选择shudu[]的空缺位置中可能性最少的位置进行匹配 
bool search_match(int left[][10])
{    
    int minx=0,minl=9;
    int flag=1;
    for(int i=0;i<81;i++)
    {
        if(left[i][0]!=1)
        {
            flag=0;
            if(left[i][0]<minl)
            {
                minl=left[i][0];
                minx=i;
            }
        }
    }
    //当所有位置的可能性都为1时说明成功,结束递归 
    if(flag==1)
    {
        complete_shudu(left);
        return true;
    }

    for(int i=1;i<10;i++)
    {

        //遍历该位置的所有可能性 
        if(left[minx][i]!=0)
        {
            //设临时数组,将left[][]拷贝过来  
            int left_temp[81][10];
            for(int j=0;j<81;j++)
            {
                for(int k=0;k<10;k++)
                {
                    left_temp[j][k]=left[j][k];
                }
            }
            //若i可以成功插入,且i插入后的数独可以完整填出来 
            if(assign(left_temp,minx,i))
            {
                if(search_match(left_temp))
                    return true;                
            }
            //若i不可以插入,则在left数组中删掉该可能性 
            left[minx][0]--;
            left[minx][i]=0;

        }    
    }
    return false;
}

int main()
{
    scanf("%s",shudu);
    while(strcmp(shudu,"end")!=0)
    {
        //每次读入一个数独,都要将left数组重置 
        memset(left,0,sizeof(left));
        init(left,shudu);
        //根据已确定的数字更新未确定块的可选情况
        for(int i=0;i<81;i++)
        {
            if(shudu[i]!='.')
            {
                assign(left,i,(int)(shudu[i]-'0'));
            } 
        } 

        search_match(left);

        print(shudu);
        memset(shudu,NULL,sizeof(shudu));
        scanf("%s",shudu);
    }
    return 0;
}

总结

数独的解法中,最经典的是舞蹈链的解法,可以很好的降低时间复杂度,但是目前自己还看不懂这种做法。
链接先放在这里,以后再慢慢看:https://www.cnblogs.com/grenet/p/3163550.html
另,特别感谢我的小伙伴耐心讲解,这里附上他的博客地址:https://www.cztcode.com/2020/poj3074-summary/