Dancing Link讲解
學(xué)了一下精確覆蓋問題,這個(gè)博客寫的真的很好,贊一下:
http://www.cnblogs.com/grenet/p/3145800.html
http://www.cnblogs.com/grenet/p/3163550.html
我就直接轉(zhuǎn)載了,當(dāng)做保存:
第一篇:
精確覆蓋問題的定義:給定一個(gè)由0-1組成的矩陣,是否能找到一個(gè)行的集合,使得集合中每一列都恰好包含一個(gè)1
例如:如下的矩陣
就包含了這樣一個(gè)集合(第1、4、5行)
?
如何利用給定的矩陣求出相應(yīng)的行的集合呢?我們采用回溯法
?
矩陣1:
?
先假定選擇第1行,如下所示:
如上圖中所示,紅色的那行是選中的一行,這一行中有3個(gè)1,分別是第3、5、6列。
由于這3列已經(jīng)包含了1,故,把這三列往下標(biāo)示,圖中的藍(lán)色部分。藍(lán)色部分包含3個(gè)1,分別在2行中,把這2行用紫色標(biāo)示出來
根據(jù)定義,同一列的1只能有1個(gè),故紫色的兩行,和紅色的一行的1相沖突。
那么在接下來的求解中,紅色的部分、藍(lán)色的部分、紫色的部分都不能用了,把這些部分都刪除,得到一個(gè)新的矩陣
矩陣2:
行分別對(duì)應(yīng)矩陣1中的第2、4、5行
列分別對(duì)應(yīng)矩陣1中的第1、2、4、7列
?
于是問題就轉(zhuǎn)換為一個(gè)規(guī)模小點(diǎn)的精確覆蓋問題
?
在新的矩陣中再選擇第1行,如下圖所示
還是按照之前的步驟,進(jìn)行標(biāo)示。紅色、藍(lán)色和紫色的部分又全都刪除,導(dǎo)致新的空矩陣產(chǎn)生,而紅色的一行中有0(有0就說明這一列沒有1覆蓋)。說明,第1行選擇是錯(cuò)誤的
?
那么回到之前,選擇第2行,如下圖所示
按照之前的步驟,進(jìn)行標(biāo)示。把紅色、藍(lán)色、紫色部分刪除后,得到新的矩陣
矩陣3:
行對(duì)應(yīng)矩陣2中的第3行,矩陣1中的第5行
列對(duì)應(yīng)矩陣2中的第2、4列,矩陣1中的第2、7列
?
由于剩下的矩陣只有1行,且都是1,選擇這一行,問題就解決
于是該問題的解就是矩陣1中第1行、矩陣2中的第2行、矩陣3中的第1行。也就是矩陣1中的第1、4、5行
?
在求解這個(gè)問題的過程中,我們第1步選擇第1行是正確的,但是不是每個(gè)題目第1步選擇都是正確的,如果選擇第1行無法求解出結(jié)果出來,那么就要推倒之前的選擇,從選擇第2行開始,以此類推
?
從上面的求解過程來看,實(shí)際上求解過程可以如下表示
1、從矩陣中選擇一行
2、根據(jù)定義,標(biāo)示矩陣中其他行的元素
3、刪除相關(guān)行和列的元素,得到新矩陣
4、如果新矩陣是空矩陣,并且之前的一行都是1,那么求解結(jié)束,跳轉(zhuǎn)到6;新矩陣不是空矩陣,繼續(xù)求解,跳轉(zhuǎn)到1;新矩陣是空矩陣,之前的一行中有0,跳轉(zhuǎn)到5
5、說明之前的選擇有誤,回溯到之前的一個(gè)矩陣,跳轉(zhuǎn)到1;如果沒有矩陣可以回溯,說明該問題無解,跳轉(zhuǎn)到7
6、求解結(jié)束,把結(jié)果輸出
7、求解結(jié)束,輸出無解消息
?
從如上的求解流程來看,在求解的過程中有大量的緩存矩陣和回溯矩陣的過程。而如何緩存矩陣以及相關(guān)的數(shù)據(jù)(保證后面的回溯能正確恢復(fù)數(shù)據(jù)),也是一個(gè)比較頭疼的問題(并不是無法解決)。以及在輸出結(jié)果的時(shí)候,如何輸出正確的結(jié)果(把每一步的選擇轉(zhuǎn)換為初始矩陣相應(yīng)的行)。
?
于是算法大師Donald E.Knuth(《計(jì)算機(jī)程序設(shè)計(jì)藝術(shù)》的作者)出面解決了這個(gè)方面的難題。他提出了DLX(Dancing Links X)算法。實(shí)際上,他把上面求解的過程稱為X算法,而他提出的舞蹈鏈(Dancing Links)實(shí)際上并不是一種算法,而是一種數(shù)據(jù)結(jié)構(gòu)。一種非常巧妙的數(shù)據(jù)結(jié)構(gòu),他的數(shù)據(jù)結(jié)構(gòu)在緩存和回溯的過程中效率驚人,不需要額外的空間,以及近乎線性的時(shí)間。而在整個(gè)求解過程中,指針在數(shù)據(jù)之間跳躍著,就像精巧設(shè)計(jì)的舞蹈一樣,故Donald E.Knuth把它稱為Dancing Links(中文譯名舞蹈鏈)。
?
Dancing Links的核心是基于雙向鏈的方便操作(移除、恢復(fù)加入)
我們用例子來說明
假設(shè)雙向鏈的三個(gè)連續(xù)的元素,A1、A2、A3,每個(gè)元素有兩個(gè)分量Left和Right,分別指向左邊和右邊的元素。由定義可知
A1.Right=A2,A2.Right=A3
A2.Left=A1,A3.Left=A2
在這個(gè)雙向鏈中,可以由任一個(gè)元素得到其他兩個(gè)元素,A1.Right.Right=A3,A3.Left.Left=A1等等
?
現(xiàn)在把A2這個(gè)元素從雙向鏈中移除(不是刪除)出去,那么執(zhí)行下面的操作就可以了
A1.Right=A3,A3.Left=A1
那么就直接連接起A1和A3。A2從雙向鏈中移除出去了。但僅僅是從雙向鏈中移除了,A2這個(gè)實(shí)體還在,并沒有刪除。只是在雙向鏈中遍歷的話,遍歷不到A2了。
那么A2這個(gè)實(shí)體中的兩個(gè)分量Left和Right指向誰?由于實(shí)體還在,而且沒有修改A2分量的操作,那么A2的兩個(gè)分量指向沒有發(fā)生變化,也就是在移除前的指向。即A2.Left=A1和A2.Right=A3
?
如果此時(shí)發(fā)現(xiàn),需要把A2這個(gè)元素重新加入到雙向鏈中的原來的位置,也就是A1和A3的中間。由于A2的兩個(gè)分量沒有發(fā)生變化,仍然指向A1和A3。那么只要修改A1的Right分量和A3的Left就行了。也就是下面的操作
A1.Right=A2,A3.Left=A2
?
仔細(xì)想想,上面兩個(gè)操作(移除和恢復(fù)加入)對(duì)應(yīng)了什么?是不是對(duì)應(yīng)了之前的算法過程中的關(guān)鍵的兩步?
移除操作對(duì)應(yīng)著緩存數(shù)據(jù)、恢復(fù)加入操作對(duì)應(yīng)著回溯數(shù)據(jù)。而美妙的是,這兩個(gè)操作不再占用新的空間,時(shí)間上也是極快速的
?
在很多實(shí)際運(yùn)用中,把雙向鏈的首尾相連,構(gòu)成循環(huán)雙向鏈
?
Dancing Links用的數(shù)據(jù)結(jié)構(gòu)是交叉十字循環(huán)雙向鏈
而Dancing Links中的每個(gè)元素不僅是橫向循環(huán)雙向鏈中的一份子,又是縱向循環(huán)雙向鏈的一份子。
因?yàn)榫_覆蓋問題的矩陣往往是稀疏矩陣(矩陣中,0的個(gè)數(shù)多于1),Dancing Links僅僅記錄矩陣中值是1的元素。
?
Dancing Links中的每個(gè)元素有6個(gè)分量
分別:Left指向左邊的元素、Right指向右邊的元素、Up指向上邊的元素、Down指向下邊的元素、Col指向列標(biāo)元素、Row指示當(dāng)前元素所在的行
?
Dancing Links還要準(zhǔn)備一些輔助元素(為什么需要這些輔助元素?沒有太多的道理,大師認(rèn)為這能解決問題,實(shí)際上是解決了問題)
Ans():Ans數(shù)組,在求解的過程中保留當(dāng)前的答案,以供最后輸出答案用。
Head元素:求解的輔助元素,在求解的過程中,當(dāng)判斷出Head.Right=Head(也可以是Head.Left=Head)時(shí),求解結(jié)束,輸出答案。Head元素只有兩個(gè)分量有用。其余的分量對(duì)求解沒啥用
C元素:輔助元素,稱列標(biāo)元素,每列有一個(gè)列標(biāo)元素。本文開始的題目的列標(biāo)元素分別是C1、C2、C3、C4、C5、C6、C7。每一列的元素的Col分量都指向所在列的列標(biāo)元素。列標(biāo)元素的Col分量指向自己(也可以是沒有)。在初始化的狀態(tài)下,Head.Right=C1、C1.Right=C2、……、C7.Right=Head、Head.Left=C7等等。列標(biāo)元素的分量Row=0,表示是處在第0行。
?
下圖就是根據(jù)題目構(gòu)建好的交叉十字循環(huán)雙向鏈(構(gòu)建的過程后面的詳述)
就上圖解釋一下
每個(gè)綠色方塊是一個(gè)元素,其中Head和C1、C2、……、C7是輔助元素。橙色框中的元素是原矩陣中1的元素,給他們標(biāo)上號(hào)(從1到16)
左側(cè)的紅色,標(biāo)示的是行號(hào),輔助元素所在的行是0行,其余元素所在的行從1到6
每兩個(gè)元素之間有一個(gè)雙向箭頭連線,表示雙向鏈中相鄰兩個(gè)元素的關(guān)系(水平的是左右關(guān)系、垂直的是上下關(guān)系)
單向的箭頭并不是表示單向關(guān)系,而因?yàn)槭茄h(huán)雙向鏈,左側(cè)的單向箭頭和右側(cè)的單向箭頭(上邊的和下邊的)組成了一個(gè)雙向箭頭,例如元素14左側(cè)的單向箭頭和元素16右側(cè)的單項(xiàng)箭頭組成一個(gè)雙向箭頭,表示14.Left=16、16.Right=14;同理,元素14下邊的單項(xiàng)箭頭和元素C4上邊的單向箭頭組成一個(gè)雙向箭頭,表示14.Down=C4、C4.Up=14
?
接下來,利用圖來解釋Dancing Links是如何求解精確覆蓋問題
1、首先判斷Head.Right=Head?若是,求解結(jié)束,輸出解;若不是,求解還沒結(jié)束,到步驟2(也可以判斷Head.Left=Head?)
2、獲取Head.Right元素,即元素C1,并標(biāo)示元素C1(標(biāo)示元素C1,指的是標(biāo)示C1、和C1所在列的所有元素、以及該元素所在行的元素,并從雙向鏈中移除這些元素)。如下圖中的紫色部分。
如上圖可知,行2和行4中的一個(gè)必是答案的一部分(其他行中沒有元素能覆蓋列C1),先假設(shè)選擇的是行2
?
3、選擇行2(在答案棧中壓入2),標(biāo)示該行中的其他元素(元素5和元素6)所在的列首元素,即標(biāo)示元素C4和標(biāo)示元素C7,下圖中的橙色部分。
注意的是,即使元素5在步驟2中就從雙向鏈中移除,但是元素5的Col分量還是指向元素C4的,這里體現(xiàn)了雙向鏈的強(qiáng)大作用。
?
把上圖中的紫色部分和橙色部分移除的話,剩下的綠色部分就如下圖所示
一下子空了好多,是不是轉(zhuǎn)換為一個(gè)少了很多元素的精確覆蓋問題?,利用遞歸的思想,很快就能寫出求解的過程來。我們繼續(xù)完成求解過程
?
4、獲取Head.Right元素,即元素C2,并標(biāo)示元素C2。如下圖中的紫色部分。
如圖,列C2只有元素7覆蓋,故答案只能選擇行3
?
5、選擇行3(在答案棧中壓入3),標(biāo)示該行中的其他元素(元素8和元素9)所在的列首元素,即標(biāo)示元素C3和標(biāo)示元素C6,下圖中的橙色部分。
把上圖中的紫色部分和橙色部分移除的話,剩下的綠色部分就如下圖所示
?
6、獲取Head.Right元素,即元素C5,元素C5中的垂直雙向鏈中沒有其他元素,也就是沒有元素覆蓋列C5。說明當(dāng)前求解失敗。要回溯到之前的分叉選擇步驟(步驟2)。那要回標(biāo)列首元素(把列首元素、所在列的元素,以及對(duì)應(yīng)行其余的元素。并恢復(fù)這些元素到雙向鏈中),回標(biāo)列首元素的順序是標(biāo)示元素的順序的反過來。從前文可知,順序是回標(biāo)列首C6、回標(biāo)列首C3、回標(biāo)列首C2、回標(biāo)列首C7、回標(biāo)列首C4。表面上看起來比較復(fù)雜,實(shí)際上利用遞歸,是一件很簡單的事。并把答案?;謴?fù)到步驟2(清空的狀態(tài))的時(shí)候。又回到下圖所示
?
7、由于之前選擇行2導(dǎo)致無解,因此這次選擇行4(再無解就整個(gè)問題就無解了)。選擇行4(在答案棧中壓入4),標(biāo)示該行中的其他元素(元素11)所在的列首元素,即標(biāo)示元素C4,下圖中的橙色部分。
把上圖中的紫色部分和橙色部分移除的話,剩下的綠色部分就如下圖所示
?
8、獲取Head.Right元素,即元素C2,并標(biāo)示元素C2。如下圖中的紫色部分。
如圖,行3和行5都可以選擇
?
9、選擇行3(在答案棧中壓入3),標(biāo)示該行中的其他元素(元素8和元素9)所在的列首元素,即標(biāo)示元素C3和標(biāo)示元素C6,下圖中的橙色部分。
把上圖中的紫色部分和橙色部分移除的話,剩下的綠色部分就如下圖所示
?
14、因?yàn)镠ead.Right=Head。故,整個(gè)過程求解結(jié)束。輸出答案,答案棧中的答案分別是4、5、1。表示該問題的解是第4、5、1行覆蓋所有的列。如下圖所示(藍(lán)色的部分)
?
從以上的14步來看,可以把Dancing Links的求解過程表述如下
?
1、Dancing函數(shù)的入口
2、判斷Head.Right=Head?,若是,輸出答案,返回True,退出函數(shù)。
3、獲得Head.Right的元素C
4、標(biāo)示元素C
5、獲得元素C所在列的一個(gè)元素
6、標(biāo)示該元素同行的其余元素所在的列首元素
7、獲得一個(gè)簡化的問題,遞歸調(diào)用Daning函數(shù),若返回的True,則返回True,退出函數(shù)。
8、若返回的是False,則回標(biāo)該元素同行的其余元素所在的列首元素,回標(biāo)的順序和之前標(biāo)示的順序相反
9、獲得元素C所在列的下一個(gè)元素,若有,跳轉(zhuǎn)到步驟6
10、若沒有,回標(biāo)元素C,返回False,退出函數(shù)。
?
?
?
之前的文章的表述,為了表述簡單,采用面向?qū)ο蟮乃悸?#xff0c;說每個(gè)元素有6個(gè)分量,分別是Left、Right、Up、Down、Col、Row分量。
但在實(shí)際的編碼中,用數(shù)組也能實(shí)現(xiàn)相同的作用。例如:用Left()表示所有元素的Left分量,Left(1)表示元素1的Left分量
在前文中,元素分為Head元素、列首元素(C1、C2等)、普通元素。在編碼中,三種元素統(tǒng)一成一種元素。如上題,0表示Head元素,1表示元素C1、2表示元素C2、……、7表示元素C7,從8開始表示普通元素。這是統(tǒng)一后,編碼的簡便性。利用數(shù)組的下標(biāo)來表示元素,宛若指針一般。
第二篇:
本文介紹該算法的實(shí)際運(yùn)用,利用舞蹈鏈(Dancing Links)算法求解數(shù)獨(dú)
?
在前文中可知,舞蹈鏈(Dancing Links)算法在求解精確覆蓋問題時(shí)效率驚人。
那利用舞蹈鏈(Dancing Links)算法求解數(shù)獨(dú)問題,實(shí)際上就是下面一個(gè)流程
1、把數(shù)獨(dú)問題轉(zhuǎn)換為精確覆蓋問題
2、設(shè)計(jì)出數(shù)據(jù)矩陣
3、用舞蹈鏈(Dancing Links)算法求解該精確覆蓋問題
4、把該精確覆蓋問題的解轉(zhuǎn)換為數(shù)獨(dú)的解
?
首先看看數(shù)獨(dú)問題(9*9的方格)的規(guī)則
1、每個(gè)格子只能填一個(gè)數(shù)字
2、每行每個(gè)數(shù)字只能填一遍
3、每列每個(gè)數(shù)字只能填一遍
4、每宮每個(gè)數(shù)字只能填一遍(宮的概念,參看“算法實(shí)踐——數(shù)獨(dú)的基本解法”)
?
那現(xiàn)在就是利用這個(gè)規(guī)則把數(shù)獨(dú)問題轉(zhuǎn)換為精確覆蓋問題
可是,直觀上面的規(guī)則,發(fā)現(xiàn)比較難以轉(zhuǎn)換為精確覆蓋問題。因此,把上面的表述換個(gè)說法
1、每個(gè)格子只能填一個(gè)數(shù)字
2、每行1-9的這9個(gè)數(shù)字都得填一遍(也就意味著每個(gè)數(shù)字只能填一遍)
3、每列1-9的這9個(gè)數(shù)字都得填一遍
4、每宮1-9的這9個(gè)數(shù)字都得填一遍
?
這樣理解的話,數(shù)獨(dú)問題轉(zhuǎn)換為精確覆蓋問題就相對(duì)簡單多了。關(guān)鍵就是如何構(gòu)造精確覆蓋問題中的矩陣
?
我們把矩陣的每個(gè)列都定義成一個(gè)約束條件。
?
第1列定義成:(1,1)填了一個(gè)數(shù)字
第2列定義成:(1,2)填了一個(gè)數(shù)字
……
第9列定義成:(1,9)填了一個(gè)數(shù)字
第10列定義成:(2,1)填了一個(gè)數(shù)字
……
第18列定義成:(2,9)填了一個(gè)數(shù)字
……
第81列定義成:(9,9)填了一個(gè)數(shù)字
至此,用第1-81列完成了約束條件1:每個(gè)格子只能填一個(gè)數(shù)字
第N列(1≤N≤81)定義成:(X,Y)填了一個(gè)數(shù)字。
N、X、Y之間的關(guān)系是:X=INT((N-1)/9)+1;Y=((N-1) Mod 9)+1;N=(X-1)×9+Y
?
?
第82列定義成:在第1行填了數(shù)字1
第83列定義成:在第1行填了數(shù)字2
……
第90列定義成:在第1行填了數(shù)字9
第91列定義成:在第2行填了數(shù)字1
……
第99列定義成:在第2行填了數(shù)字9
……
第162列定義成:在第9行填了數(shù)字9
至此,用第82-162列(共81列)完成了約束條件2:每行1-9的這9個(gè)數(shù)字都得填一遍
第N列(82≤N≤162)定義成:在第X行填了數(shù)字Y。
N、X、Y之間的關(guān)系是:X=INT((N-81-1)/9)+1;Y=((N-81-1) Mod 9)+1;N=(X-1)×9+Y+81
?
?
第163列定義成:在第1列填了數(shù)字1
第164列定義成:在第1列填了數(shù)字2
……
第171列定義成:在第1列填了數(shù)字9
第172列定義成:在第2列填了數(shù)字1
……
第180列定義成:在第2列填了數(shù)字9
……
第243列定義成:在第9列填了數(shù)字9
至此,用第163-243列(共81列)完成了約束條件3:每列1-9的這9個(gè)數(shù)字都得填一遍
第N列(163≤N≤243)定義成:在第X列填了數(shù)字Y。
N、X、Y之間的關(guān)系是:X=INT((N-162-1)/9)+1;Y=((N-162-1) Mod 9)+1;N=(X-1)×9+Y+162
?
?
第244列定義成:在第1宮填了數(shù)字1
第245列定義成:在第1宮填了數(shù)字2
……
第252列定義成:在第1宮填了數(shù)字9
第253列定義成:在第2宮填了數(shù)字1
……
第261列定義成:在第2宮填了數(shù)字9
……
第324列定義成:在第9宮填了數(shù)字9
至此,用第244-324列(共81列)完成了約束條件4:每宮1-9的這9個(gè)數(shù)字都得填一遍
第N列(244≤N≤324)定義成:在第X宮填了數(shù)字Y。
N、X、Y之間的關(guān)系是:X=INT((N-243-1)/9)+1;Y=((N-243-1) Mod 9)+1;N=(X-1)×9+Y+243
?
至此,用了324列完成了數(shù)獨(dú)的四個(gè)約束條件,矩陣的列定義完成
那接下來,就是把數(shù)獨(dú)轉(zhuǎn)換為矩陣
數(shù)獨(dú)問題中,每個(gè)格子分兩種情況。有數(shù)字的格子、沒數(shù)字的格子。
?
有數(shù)字的格子
以例子來說明,在(4,2)中填的是7
把(4,2)中填的是7,解釋成4個(gè)約束條件
1、在(4,2)中填了一個(gè)數(shù)字。
2、在第4行填了數(shù)字7
3、在第2列填了數(shù)字7
4、在第4宮填了數(shù)字7(坐標(biāo)(X,Y)到宮N的公式為:N=INT((X-1)/3)×3+INT((Y-1)/3)+1)
?
那么這4個(gè)條件,分別轉(zhuǎn)換成矩陣對(duì)應(yīng)的列為
1、在(4,2)中填了一個(gè)數(shù)字。對(duì)應(yīng)的列N=(4-1)×9+2=29
2、在第4行填了數(shù)字7。對(duì)應(yīng)的列N=(4-1)×9+7+81=115
3、在第2列填了數(shù)字7。對(duì)應(yīng)的列N=(2-1)×9+7+162=178
4、在第4宮填了數(shù)字7。對(duì)應(yīng)的列N=(4-1)×9+7+243=277
?
于是,(4,2)中填的是7,轉(zhuǎn)成矩陣的一行就是,第29、115、178、277列是1,其余列是0。把這1行插入到矩陣中去。
?
沒數(shù)字的格子
還是舉例說明,在(5,8)中沒有數(shù)字
把(5,8)中沒有數(shù)字轉(zhuǎn)換成
(5,8)中填的是1,轉(zhuǎn)成矩陣的一行就是,第44、118、226、289列是1,其余列是0。
(5,8)中填的是2,轉(zhuǎn)成矩陣的一行就是,第44、119、227、290列是1,其余列是0。
(5,8)中填的是3,轉(zhuǎn)成矩陣的一行就是,第44、120、228、291列是1,其余列是0。
(5,8)中填的是4,轉(zhuǎn)成矩陣的一行就是,第44、121、229、292列是1,其余列是0。
(5,8)中填的是5,轉(zhuǎn)成矩陣的一行就是,第44、122、230、293列是1,其余列是0。
(5,8)中填的是6,轉(zhuǎn)成矩陣的一行就是,第44、123、231、294列是1,其余列是0。
(5,8)中填的是7,轉(zhuǎn)成矩陣的一行就是,第44、124、232、295列是1,其余列是0。
(5,8)中填的是8,轉(zhuǎn)成矩陣的一行就是,第44、125、233、296列是1,其余列是0。
(5,8)中填的是9,轉(zhuǎn)成矩陣的一行就是,第44、126、234、297列是1,其余列是0。
把這9行插入到矩陣中。由于這9行的第44列都是1(不會(huì)有其他行的44列會(huì)是1),也就是說這9行中必只有1行(有且只有1行)選中(精確覆蓋問題的定義,每列只能有1個(gè)1),是最后解的一部分。這就保證了最后解在(5,8)中只有1個(gè)數(shù)字。
?
這樣,從數(shù)獨(dú)的格子依次轉(zhuǎn)換成行(1行或者9行)插入到矩陣中。完成了數(shù)獨(dú)問題到精確覆蓋問題的轉(zhuǎn)換。
總結(jié)
以上是生活随笔為你收集整理的Dancing Link讲解的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: hust1342(流量有上下界的最小流)
- 下一篇: poj3074(数独)