Sudoku 數獨 Dancing Links模板

來源:互聯網
上載者:User

推薦論文: momodi的《Dancing Links 在搜尋中的應用》、Knuth的DLX論文、陳丹琦的《Dancing Links的應用》

 

Dancing Links是用來最佳化一類精確覆蓋問題中的DFS過程。

精確覆蓋問題是指在一個01矩陣中,選出一些行使每一列有且僅有1個1.

解法是Knuth提出的X演算法:

1.矩陣被全部刪除,搜尋成功退出。

2.選擇包含元素最少的一列c(可以隨便選一列)刪除,枚舉這列含1的行r作為解的一部分,刪除r行所有含1的列。

3.遞迴調用,成功返回,失敗則回溯。

 

一般搜尋中用bool數組標記行和列是否被刪除,通過列找所有含1的行需要r次,通過行找所有的列需要c次,

然而在搜尋過程中,矩陣的行和列不斷被刪除,不斷減少,後面含1的行列很少,成為疏鬆陣列,

這時再使用r或c次的尋找就是浪費時間了。

Dancing Links就是使用雙向迴圈十字鏈表來儲存矩陣,搜尋過程中鏈表大小會不斷減小,遍曆一次就不需要r或c次了。

雙向鏈表的刪除操作:

L[R[x]] = L[x]; R[L[x]] = R[x];

雙向鏈表的恢複操作也很簡單:

L[R[x]] = x; R[L[x]] = x;

Knuth將支援這個操作的鏈表命名為Dancing Links 。

 

Sudoku 數獨向精確覆蓋問題的轉換

數專屬4個約束條件:

1.在i行只能放一個數字k

2.在j列只能放一個數字k

3.在block(i,j)塊只能放一個數字k

4.i行j列只能放一個數字

 

所以建一個01矩陣:

行數n*n*n,n*n個格子,每個格子有n中可能,每種可能對應一行。

列數4*n*n,代表n*n個格子的4個約束條件。

如果數獨i行j列已經有值k,則在(i*n+j)*n+k行插入4個1,列數分別是:

i*n+k-1

n*n+j*n+k-1

2*n*n+block(i,j)*n+k-1

3*n*n+i*n+j

否則插入n行,k=1 to n。

如果n=16,那麼有4096行,1024列,矩陣遍曆需要4096*1024=4194304次,

但是1的個數只有4096*4=16384個,Dancing Links 遍曆只需要16384次。

這樣對比下就可以看出Dancing Links 節約了很多時間。

調用DLX演算法就可以求出數獨的一個解或者判斷無解。

 

 n皇后問題也可以轉換為精確覆蓋問題。

還有一類重複覆蓋問題:

在一個01矩陣中,選出一些行使每一列至少有1個1.

需要配合A*演算法解決。

 

 

數獨模板:

//POJ3076 736 KB 172 ms G++ 2689 B #include<cstdio>#define N 4099#define M 1025int m=4,n=m*m,H=4*n*n,cnt,size[M],ans[16][16];struct Node{    int r,c;    Node *U,*D,*L,*R;}node[16385],row[N],col[M],head,*p;void init(int r,int c){    cnt=0;    head.L=head.R=head.U=head.D=&head;    for(int i=0;i<c;i++){        col[i].r=r;        col[i].c=i;        col[i].L=&head;        col[i].R=head.R;        col[i].U=col[i].D=col[i].L->R=col[i].R->L=&col[i];        size[i]=0;    }    for(int i=r-1;i>=0;i--){        row[i].r=i;        row[i].c=c;        row[i].U=&head;        row[i].D=head.D;        row[i].L=row[i].R=row[i].U->D=row[i].D->U=&row[i];    }}void insert(int r,int c){    p=&node[cnt++];    p->r=r;    p->c=c;    p->R=&row[r];    p->L=row[r].L;    p->L->R=p->R->L=p;    p->U=&col[c];    p->D=col[c].D;    p->U->D=p->D->U=p;    ++size[c];}void delLR(Node *p){    p->L->R=p->R;    p->R->L=p->L;}void delUD(Node *p){    p->U->D=p->D;    p->D->U=p->U;}void resumeLR(Node *p){p->L->R=p->R->L=p;}void resumeUD(Node *p){p->U->D=p->D->U=p;}void cover(int c){    if(c==H)        return;    delLR(&col[c]);    Node *R,*C;    for(C=col[c].D;C!=&col[c];C=C->D)for(R=C->L;R!=C;R=R->L){--size[R->c];delUD(R);}}void resume(int c){    if(c==H)        return;    Node *R,*C;    for(C=col[c].U;C!=&col[c];C=C->U)for(R=C->R;R!=C;R=R->R){++size[R->c];resumeUD(R);}    resumeLR(&col[c]);}int dfs(int k){    if(head.L==&head)return 1;    int INF=-1u>>1,r,c=-1;Node *p,*rc;    for(p=head.R;p!=&head;p=p->R)        if(size[p->c]<INF)            INF=size[c=p->c];if(!size[c])return 0;    cover(c);    for(p=col[c].D;p!=&col[c];p=p->D){        for(rc=p->L;rc!=p;rc=rc->L)            cover(rc->c);r=p->r-1;ans[r/(n*n)][r/n%n]=r%n;        if(dfs(k+1))            return 1;        for(rc=p->R;rc!=p;rc=rc->R)            resume(rc->c);    }    resume(c);    return 0;}void insert(int i,int j,int k){int r=(i*n+j)*n+k;insert(r,i*n+k-1);insert(r,n*n+j*n+k-1);insert(r,2*n*n+(i/m*m+j/m)*n+k-1);insert(r,3*n*n+i*n+j);}char s[16][20];void Sudoku(){int i,j,k;init(n*n*n+1,H);for(i=0;i<n;i++)for(j=0;j<n;j++)if(s[i][j]!='-')insert(i,j,k=s[i][j]-'A'+1);else{for(k=1;k<=n;k++)insert(i,j,k);}dfs(0);for(i=0;i<n;i++){for(j=0;j<n;j++)putchar(ans[i][j]+'A');puts("");}puts("");}int main(){int i;    while(~scanf("%s",s[0])){for(i=1;i<n;i++)scanf("%s",s[i]);Sudoku();}}

精確覆蓋:

//01矩陣的完美覆蓋 HUST1017#include <iostream>#include <cstdio>#include <vector>using namespace std;/***最大行***/#define MAXROW 1005/***最大列***/#define MAXCOL 1005int ans[MAXROW+5];struct DancingLinksNode {    int r, c; /***結點所在的行列位置***/        DancingLinksNode *U, *D, *L, *R;/***結點的上下左右結點指標***/};DancingLinksNode node[MAXROW * MAXCOL];/****備用結點****/    DancingLinksNode row[MAXROW];/****行頭****/DancingLinksNode col[MAXCOL];/****列頭****/DancingLinksNode head;/****表頭****/int cnt;/****使用了多少結點****/int size[MAXCOL];/****列含有多少個域****/int m, n;/****表的行與列變數****/void init(int r, int c)/****初始化,r, c分別表示表的大小***/ {    cnt = 0;/****將可以使用的結點設為第一個****/       head.r = r; /****head結點的r,c分別表示表的大小,以備查****/    head.c = c;        head.L = head.R = head.U = head.D = &head;/****初始化head結點****/       for(int i = 0; i < c; ++i) /***初始化列頭***/ {        col[i].r = r;        col[i].c = i;        col[i].L = &head;        col[i].R = head.R;        col[i].L->R = col[i].R->L = &col[i];        col[i].U = col[i].D = &col[i];        size[i] = 0;    }    for(int i = r - 1; i > -1; --i)/***初始化行頭,在刪除的時候,如果碰到row[i].c  == c的情形應當被跳過***/{        row[i].r = i;        row[i].c = c;        row[i].U = &head;        row[i].D = head.D;        row[i].U->D = row[i].D->U = &row[i];        row[i].L = row[i].R = &row[i];    }}inline void addNode(int r, int c)/****增加一個結點,在原表中的位置為r行,c列***/ {    DancingLinksNode *ptr = &node[cnt++];/****找一個未曾使用的結點****/    ptr->r = r;/****設定結點的行列號****/    ptr->c = c;    ptr->R = &row[r];/****將結點加入雙向鏈表中****/    ptr->L = row[r].L;    ptr->L->R = ptr->R->L = ptr;    ptr->U = &col[c];    ptr->D = col[c].D;    ptr->U->D = ptr->D->U = ptr;        ++size[c];/****將size域加1****/}inline void delLR(DancingLinksNode * ptr)/****刪除ptr所指向的結點的左右方向****/ {    ptr->L->R = ptr->R;    ptr->R->L = ptr->L;}inline void delUD(DancingLinksNode * ptr)/****刪除ptr所指向的結點的上下方向****/ {    ptr->U->D = ptr->D;    ptr->D->U = ptr->U;}inline void resumeLR(DancingLinksNode * ptr)/****重設ptr所指向的結點的左右方向****/ {    ptr->L->R = ptr->R->L = ptr;}inline void resumeUD(DancingLinksNode * ptr)/****重設ptr所指向的結點的上下方向****/ {    ptr->U->D = ptr->D->U = ptr;}inline void cover(int c)/****覆蓋第c例***/ {    if(c == n)/**** c == n 表示頭****/         return;        delLR(&col[c]);/****刪除表頭****/    DancingLinksNode *R, *C;    for(C = col[c].D; C != (&col[c]); C = C->D) {        if(C->c == n)            continue;        for(R = C->L; R != C; R = R->L){            if(R->c == n)                continue;            --size[R->c];            delUD(R);        }        delLR(C);    }}inline void resume(int c)/****重設第c列****/ {    if(c == n)        return;    DancingLinksNode *R, *C;    for(C = col[c].U; C != (&col[c]); C = C->U) {        if(C->c == n)            continue;        resumeLR(C);        for(R = C->R; R != C; R = R->R) {            if(R->c == n)                continue;            ++size[R->c];            resumeUD(R);        }    }    resumeLR(&col[c]);/****把列頭接進表頭中****/}bool search(int k)/****搜尋核心演算法,k表示搜尋層數****/ {       if(head.L == (&head)) /***搜尋成功,返回true***/     {printf("%d\n",k);for(int i=0;i<k;i++)printf("%d\n",ans[i]);return true;}    /***c表示下一個列對象位置,找一個分支數目最小的進行覆蓋***/    int INF = (1<<30), c = -1;        for(DancingLinksNode *ptr=head.L;ptr!=(&head);ptr=ptr->L)         if(size[ptr->c] < INF) {            INF = size[ptr->c];            c = ptr->c;        }    cover(c); /***覆蓋第c列***/    DancingLinksNode * ptr;    for(ptr = col[c].D; ptr != (&col[c]); ptr = ptr->D) {        DancingLinksNode *rc;        ptr->R->L = ptr;        for(rc = ptr->L; rc != ptr; rc = rc->L)             cover(rc->c);        ptr->R->L = ptr->L;ans[k]=ptr->r+1;        if(search(k + 1))             return true;        ptr->L->R = ptr;        for(rc = ptr->R; rc != ptr; rc = rc->R)             resume(rc->c);        ptr->L->R = ptr->R;    }    resume(c);/***取消覆蓋第c列***/    return false;}int main() {    while(scanf("%d%d", &m, &n) != EOF) {        init(m, n);        for(int i = 0; i < m; ++i) {int x,j;scanf("%d",&x);while(x--){scanf("%d",&j);j--;addNode(i, j);//i行j列為1}        }        if(!search(0)) puts("NO");    }}/*模型的建立自然是要把衝突的條件都擺出來,這裡有4個。1.同行不能有相同的數2.同列不能有相同的數3.同塊不能有相同的數除了這3個很顯然的約束,還有一個比較重要的,就是4.每個位置只能有一個數所以,對於一個9*9的數獨,如此設定行:(行標號,列標號),(行標號,數),(列標號,數),(塊標號,數)每塊都是9*9=81,一共是324個位置。其中比據我要加入一個i行j列的數字k那麼要加入一個含4個1的行,即(i,j),(i,k),(j,k)(block(i,j),k)block(i,j)返回(i,j)的塊標號如果有初始值的話把所有初始值都放一起,放在第0行,然後在第1層迴圈中做下特判,只進行一次覆蓋。。如果能保證第一次必然迴圈到此行就這麼做。否則,在遞迴開始前就先把這些列刪除。提出前一種方案只是因為寫起來方便。*/

 

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.