數獨(通俗易懂)

通俗易懂的方法就是dfs簡單爆搜,本文介紹的是最簡單粗暴的方法,直接暴力搜索,沒有進行任何優化。

數獨又稱爲9宮格(如下圖所示)
每一行的數字都不能重複
每一列的數字都不能重複
每一個小9宮格(方陣)數字都不能重複
可以看做上面三個分別是一個包含1-9的集合

在這裏插入圖片描述
因此我們可以設置三個二維數組來記錄每一行、每一列、每一個方陣1-9的數字,從而確定每個點可以填哪些數。一維分別表示第幾行、第幾列、第幾個方陣,然後二維用來記錄哪些數字已經存在,1表示存在,0表示不存在。
假定我們輸入的數據如下:

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

那麼,我們設置一個二維數組a[10][10]來進行圖的存儲,然後再對每個輸入點進行判斷,當前輸入的點對應的值如果不爲0,那麼就對這個點進行記錄。row[10][10]記錄行、col[10][10]記錄列、g[10][10]記錄方陣。以輸入的第二行第三列的點(值爲3)爲例(我們數組下標從1開始,爲了方便)
a[2][3]>0,那麼進行點的標記

row[2][a[i][j]]=1、col[3]a[i][j]]=1、g[((i-1)/3)*3+(j-1)/3+1)][a[i][j]]=1。

上面的三個標記也就是數獨的精華了,把這裏理解了,數獨這個題目也就很容易理解了。
這裏最難的地方就是方陣的邊界問題,一不小心就可能錯了。因爲我們要將1-9劃分成3個部分,但是1~9除以3的話邊界不好處理3/3等於1,但是我們想讓他分在第一塊,也就是它應該等於0,6/2的話應該讓他等於1,應該和4/3、5/3都等於1,因爲他們在同一個方陣,那麼我們就得把1-9映射到0-8然後再進行劃分
(0~2)/3對應的是0
(3~5)/3對應的是1
(6~8)/3對應的是2
這樣就完美的避免了這個問題,我們設置的下標是從1開始的,但是這樣得到的結果是從0開始,因此我們給結果加上1,就從0-8變成了1-9。也就是9個方陣的編號。
到這裏我們就可以處理每個點可以選擇哪些數據,然後dfs遍歷找到合適的情況。

那麼還有一個問題就是從哪個點開始找,然後怎麼找!!!
題目都說了暴力搜索嘛,那麼我就從位置(1,1)開始搜索,一列一列的搜索,搜索完一行,那麼從下一行第一個數據開始。題目肯定保證數獨有解,那麼當搜到位置(9,9)時,那麼也就到了終點,就說明完成了數獨的填充,因此,直接打印填充好的圖就可以了。(理解的同學可以自己先寫寫代碼,不理解的童鞋可以藉助下面的代碼理解理解)

代碼在AcWing和落谷均已通過測試
AcWing上的題目鏈接:簡單數獨

#include<iostream>
#include<cstdio>
using namespace std;
const int N=10;
//a用來出存圖、col[i]記錄第i列已經存在的數
//row[i]記錄第i行已經存在的數、g[i]記錄第i個塊已經存在的數
int a[N][N],col[N][N],row[N][N],g[N][N];
//inline 是內聯函數,非遞歸函數使用纔有作用
//作用就是程序不把他當做函數,節省了程序調用函數的時間
//使用inline的目的就是爲了節省時間,快!!!
inline void pr()
{
    for(int i=1;i<N;i++)
    {
        for(int j=1;j<N;j++)
            printf("%d",a[i][j]);
        puts("");//這麼寫相當於空格
    }
    exit(0);//這裏還是稍微優化了一下,找到就直接結束程序,沒必要再繼續找
}
void dfs(int x,int y)
{
    //如果當前節點有值就不需要填充,直接跳到下一個節點
    if(a[x][y]!=0)
        if(x==N-1&&y==N-1) pr();//全部填充完畢
        else if(y==N-1) dfs(x+1,1);//x行已經填充完畢
        else dfs(x,y+1);//x行一列一列填充
    else
        for(int i=1;i<N;i++)
        {
            //判斷當前節點已經存在值i,不存在執行填充
            if(!row[x][i]&&!col[y][i]&&!g[((x-1)/3)*3+(y-1)/3+1][i])
            {
                //填充操作,對點的狀態進行標註
                a[x][y]=i,row[x][i]=1,col[y][i]=1,g[((x-1)/3)*3+(y-1)/3+1][i]=1;
                if(x==N-1&&y==N-1) pr();//點填充完恰好是圖填充完
                else if(y==N-1) dfs(x+1,1);//填充完對下一個點進行操作
                else dfs(x,y+1);
                //回溯,狀態還原
                a[x][y]=0,row[x][i]=0,col[y][i]=0,g[((x-1)/3)*3+(y-1)/3+1][i]=0;
            }
        }
}
int main()
{
    char temp;
    for(int i=1;i<N;i++)
        for(int j=1;j<N;j++)
        {
            cin>>temp;
            //對輸入的char數據進行存放、1-9對應爲1-9,'.'對應爲0
            a[i][j]=temp>='0'&&temp<='9'?int(temp-'0'):0;
            //標註不爲0的點情況
            if(a[i][j]) g[((i-1)/3)*3+(j-1)/3+1][a[i][j]]=1,row[i][a[i][j]]=1,col[j][a[i][j]]=1;
        }
    //從1,1開始順序搜索,一行一行從左到右進行搜索
    dfs(1,1);
    return 0;
}

當然,這麼暴力的話,對於難一點的數據,肯定是會TLE的,那麼我們就需要對代碼進行優化,分爲以下幾個方面:
1、選擇已填充個數多(行、列和方陣都滿足)的位置作爲填充的起點
2、適當剪枝,對沒有必要繼續執行的分支,不再執行
3、使用位運算提高運算速度
在這裏插入圖片描述
上圖是簡單數獨這個題目oj上運行時間,優化之後可以說的是快的一批呀!!!優化之後的代碼可以提交這個題目:進階版數獨,數據比較難(優化之後的代碼在進階版數獨都跑了600+ms),有興趣的可以試一下,這個的題目我已通過,之後我會發布詳細題解,先附上AC代碼,有興趣的童鞋可以先看看。

#include<iostream>
#include<algorithm>
using namespace std;
const int N=9;
char str[100];
int row[N],col[N],chunk[3][3],tab[1<<N],map[1<<N];
inline int lowbit(int x)
{
    return x&-x;
}
inline 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++)
            chunk[i][j]=(1<<N)-1;
}
inline int get(int x,int y)
{
    return row[x]&col[y]&chunk[x/3][y/3];
}
bool dfs(int cnt)
{
    if(!cnt) return true;
    int x,y,mmin=10,t;
    for(int i=0;i<N;i++)
        for(int j=0;j<N;j++)
            if(str[i*9+j]=='.')
            {
                t=tab[get(i,j)];
                if(t<mmin) mmin=t,x=i,y=j;
            }
    for(int i=get(x,y);i;i-=lowbit(i))
    {
        t=map[lowbit(i)];
        row[x]-=1<<t;
        col[y]-=1<<t;
        chunk[x/3][y/3]-=1<<t;
        str[x*9+y]='1'+t;
        if(dfs(cnt-1)) return true;
        row[x]+=1<<t;
        col[y]+=1<<t;
        chunk[x/3][y/3]+=1<<t;
        str[x*9+y]='.';
    }
    return false;
}
int main()
{
    for(int i=0;i<N;i++) map[1<<i]=i;
    for(int i=0;i<1<<N;i++)
    {
        int s=0;
        for(int j=i;j;j-=lowbit(j)) s++;
        tab[i]=s;
    }
    for(int i=0;i<N*N;i++) cin>>str[i];
    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';
                row[i]-=1<<t;
                col[j]-=1<<t;
                chunk[i/3][j/3]-=1<<t;
            }
            else cnt++;
    dfs(cnt);
    for(int i=0;i<N;i++)
    {
        for(int j=0;j<N;j++)
            printf("%c",str[i*N+j]);
        puts("");
    }
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章