日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

DLX (Dancing Links/舞蹈链)算法——求解精确覆盖问题

發布時間:2024/10/12 编程问答 29 豆豆
生活随笔 收集整理的這篇文章主要介紹了 DLX (Dancing Links/舞蹈链)算法——求解精确覆盖问题 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

精確覆蓋問題的定義:給定一個由0-1組成的矩陣,是否能找到一個行的集合,使得集合中每一列都恰好包含一個1

例如:如下的矩陣

就包含了這樣一個集合(第1、4、5行)

?

如何利用給定的矩陣求出相應的行的集合呢?我們采用回溯法

?

矩陣1:

?

先假定選擇第1行,如下所示:

如上圖中所示,紅色的那行是選中的一行,這一行中有3個1,分別是第3、5、6列。

由于這3列已經包含了1,故,把這三列往下標示,圖中的藍色部分。藍色部分包含3個1,分別在2行中,把這2行用紫色標示出來

根據定義,同一列的1只能有1個,故紫色的兩行,和紅色的一行的1相沖突。

那么在接下來的求解中,紅色的部分、藍色的部分、紫色的部分都不能用了,把這些部分都刪除,得到一個新的矩陣

矩陣2:

行分別對應矩陣1中的第2、4、5行

列分別對應矩陣1中的第1、2、4、7列

?

于是問題就轉換為一個規模小點的精確覆蓋問題

?

在新的矩陣中再選擇第1行,如下圖所示

還是按照之前的步驟,進行標示。紅色、藍色和紫色的部分又全都刪除,導致新的空矩陣產生,而紅色的一行中有0(有0就說明這一列沒有1覆蓋)。說明,第1行選擇是錯誤的

?

那么回到之前,選擇第2行,如下圖所示

按照之前的步驟,進行標示。把紅色、藍色、紫色部分刪除后,得到新的矩陣

矩陣3:

行對應矩陣2中的第3行,矩陣1中的第5行

列對應矩陣2中的第2、4列,矩陣1中的第2、7列

?

由于剩下的矩陣只有1行,且都是1,選擇這一行,問題就解決

于是該問題的解就是矩陣1中第1行、矩陣2中的第2行、矩陣3中的第1行。也就是矩陣1中的第1、4、5行

?

在求解這個問題的過程中,我們第1步選擇第1行是正確的,但是不是每個題目第1步選擇都是正確的,如果選擇第1行無法求解出結果出來,那么就要推倒之前的選擇,從選擇第2行開始,以此類推

?

從上面的求解過程來看,實際上求解過程可以如下表示

1、從矩陣中選擇一行

2、根據定義,標示矩陣中其他行的元素

3、刪除相關行和列的元素,得到新矩陣

4、如果新矩陣是空矩陣,并且之前的一行都是1,那么求解結束,跳轉到6;新矩陣不是空矩陣,繼續求解,跳轉到1;新矩陣是空矩陣,之前的一行中有0,跳轉到5

5、說明之前的選擇有誤,回溯到之前的一個矩陣,跳轉到1;如果沒有矩陣可以回溯,說明該問題無解,跳轉到7

6、求解結束,把結果輸出

7、求解結束,輸出無解消息

?

從如上的求解流程來看,在求解的過程中有大量的緩存矩陣和回溯矩陣的過程。而如何緩存矩陣以及相關的數據(保證后面的回溯能正確恢復數據),也是一個比較頭疼的問題(并不是無法解決)。以及在輸出結果的時候,如何輸出正確的結果(把每一步的選擇轉換為初始矩陣相應的行)。

?

于是算法大師Donald E.Knuth(《計算機程序設計藝術》的作者)出面解決了這個方面的難題。他提出了DLX(Dancing Links X)算法。實際上,他把上面求解的過程稱為X算法,而他提出的舞蹈鏈(Dancing Links)實際上并不是一種算法,而是一種數據結構。一種非常巧妙的數據結構,他的數據結構在緩存和回溯的過程中效率驚人,不需要額外的空間,以及近乎線性的時間。而在整個求解過程中,指針在數據之間跳躍著,就像精巧設計的舞蹈一樣,故Donald E.Knuth把它稱為Dancing Links(中文譯名舞蹈鏈)。

?

Dancing Links的核心是基于雙向鏈的方便操作(移除、恢復加入)

我們用例子來說明

假設雙向鏈的三個連續的元素,A1、A2、A3,每個元素有兩個分量Left和Right,分別指向左邊和右邊的元素。由定義可知

A1.Right=A2,A2.Right=A3

A2.Left=A1,A3.Left=A2

在這個雙向鏈中,可以由任一個元素得到其他兩個元素,A1.Right.Right=A3,A3.Left.Left=A1等等

?

現在把A2這個元素從雙向鏈中移除(不是刪除)出去,那么執行下面的操作就可以了

A1.Right=A3,A3.Left=A1

那么就直接連接起A1和A3。A2從雙向鏈中移除出去了。但僅僅是從雙向鏈中移除了,A2這個實體還在,并沒有刪除。只是在雙向鏈中遍歷的話,遍歷不到A2了。

那么A2這個實體中的兩個分量Left和Right指向誰?由于實體還在,而且沒有修改A2分量的操作,那么A2的兩個分量指向沒有發生變化,也就是在移除前的指向。即A2.Left=A1和A2.Right=A3

?

如果此時發現,需要把A2這個元素重新加入到雙向鏈中的原來的位置,也就是A1和A3的中間。由于A2的兩個分量沒有發生變化,仍然指向A1和A3。那么只要修改A1的Right分量和A3的Left就行了。也就是下面的操作

A1.Right=A2,A3.Left=A2

?

仔細想想,上面兩個操作(移除和恢復加入)對應了什么?是不是對應了之前的算法過程中的關鍵的兩步?

移除操作對應著緩存數據、恢復加入操作對應著回溯數據。而美妙的是,這兩個操作不再占用新的空間,時間上也是極快速的

?

在很多實際運用中,把雙向鏈的首尾相連,構成循環雙向鏈

?

Dancing Links用的數據結構是交叉十字循環雙向鏈

而Dancing Links中的每個元素不僅是橫向循環雙向鏈中的一份子,又是縱向循環雙向鏈的一份子。

因為精確覆蓋問題的矩陣往往是稀疏矩陣(矩陣中,0的個數多于1),Dancing Links僅僅記錄矩陣中值是1的元素。

?

Dancing Links中的每個元素有6個分量

分別:Left指向左邊的元素、Right指向右邊的元素、Up指向上邊的元素、Down指向下邊的元素、Col指向列標元素、Row指示當前元素所在的行

?

Dancing Links還要準備一些輔助元素(為什么需要這些輔助元素?沒有太多的道理,大師認為這能解決問題,實際上是解決了問題)

Ans():Ans數組,在求解的過程中保留當前的答案,以供最后輸出答案用。

Head元素:求解的輔助元素,在求解的過程中,當判斷出Head.Right=Head(也可以是Head.Left=Head)時,求解結束,輸出答案。Head元素只有兩個分量有用。其余的分量對求解沒啥用

C元素:輔助元素,稱列標元素,每列有一個列標元素。本文開始的題目的列標元素分別是C1、C2、C3、C4、C5、C6、C7。每一列的元素的Col分量都指向所在列的列標元素。列標元素的Col分量指向自己(也可以是沒有)。在初始化的狀態下,Head.Right=C1、C1.Right=C2、……、C7.Right=Head、Head.Left=C7等等。列標元素的分量Row=0,表示是處在第0行。

?

下圖就是根據題目構建好的交叉十字循環雙向鏈(構建的過程后面的詳述)

就上圖解釋一下

每個綠色方塊是一個元素,其中Head和C1、C2、……、C7是輔助元素。橙色框中的元素是原矩陣中1的元素,給他們標上號(從1到16)

左側的紅色,標示的是行號,輔助元素所在的行是0行,其余元素所在的行從1到6

每兩個元素之間有一個雙向箭頭連線,表示雙向鏈中相鄰兩個元素的關系(水平的是左右關系、垂直的是上下關系)

單向的箭頭并不是表示單向關系,而因為是循環雙向鏈,左側的單向箭頭和右側的單向箭頭(上邊的和下邊的)組成了一個雙向箭頭,例如元素14左側的單向箭頭和元素16右側的單項箭頭組成一個雙向箭頭,表示14.Left=16、16.Right=14;同理,元素14下邊的單項箭頭和元素C4上邊的單向箭頭組成一個雙向箭頭,表示14.Down=C4、C4.Up=14

?

接下來,利用圖來解釋Dancing Links是如何求解精確覆蓋問題

1、首先判斷Head.Right=Head?若是,求解結束,輸出解;若不是,求解還沒結束,到步驟2(也可以判斷Head.Left=Head?)

2、獲取Head.Right元素,即元素C1,并標示元素C1標示元素C1,指的是標示C1、和C1所在列的所有元素、以及該元素所在行的元素,并從雙向鏈中移除這些元素)。如下圖中的紫色部分。

如上圖可知,行2和行4中的一個必是答案的一部分(其他行中沒有元素能覆蓋列C1),先假設選擇的是行2

?

3、選擇行2(在答案棧中壓入2),標示該行中的其他元素(元素5和元素6)所在的列首元素,即標示元素C4標示元素C7,下圖中的橙色部分。

注意的是,即使元素5在步驟2中就從雙向鏈中移除,但是元素5的Col分量還是指向元素C4的,這里體現了雙向鏈的強大作用。

?

把上圖中的紫色部分和橙色部分移除的話,剩下的綠色部分就如下圖所示

一下子空了好多,是不是轉換為一個少了很多元素的精確覆蓋問題?,利用遞歸的思想,很快就能寫出求解的過程來。我們繼續完成求解過程

?

4、獲取Head.Right元素,即元素C2,并標示元素C2。如下圖中的紫色部分。

如圖,列C2只有元素7覆蓋,故答案只能選擇行3

?

5、選擇行3(在答案棧中壓入3),標示該行中的其他元素(元素8和元素9)所在的列首元素,即標示元素C3標示元素C6,下圖中的橙色部分。

把上圖中的紫色部分和橙色部分移除的話,剩下的綠色部分就如下圖所示

?

6、獲取Head.Right元素,即元素C5,元素C5中的垂直雙向鏈中沒有其他元素,也就是沒有元素覆蓋列C5。說明當前求解失敗。要回溯到之前的分叉選擇步驟(步驟2)。那要回標列首元素(把列首元素、所在列的元素,以及對應行其余的元素。并恢復這些元素到雙向鏈中),回標列首元素的順序是標示元素的順序的反過來。從前文可知,順序是回標列首C6回標列首C3回標列首C2回標列首C7回標列首C4。表面上看起來比較復雜,實際上利用遞歸,是一件很簡單的事。并把答案棧恢復到步驟2(清空的狀態)的時候。又回到下圖所示

?

7、由于之前選擇行2導致無解,因此這次選擇行4(再無解就整個問題就無解了)。選擇行4(在答案棧中壓入4),標示該行中的其他元素(元素11)所在的列首元素,即標示元素C4,下圖中的橙色部分。

把上圖中的紫色部分和橙色部分移除的話,剩下的綠色部分就如下圖所示

?

8、獲取Head.Right元素,即元素C2,并標示元素C2。如下圖中的紫色部分。

如圖,行3和行5都可以選擇

?

9、選擇行3(在答案棧中壓入3),標示該行中的其他元素(元素8和元素9)所在的列首元素,即標示元素C3標示元素C6,下圖中的橙色部分。

把上圖中的紫色部分和橙色部分移除的話,剩下的綠色部分就如下圖所示

?

10、獲取Head.Right元素,即元素C5,元素C5中的垂直雙向鏈中沒有其他元素,也就是沒有元素覆蓋列C5。說明當前求解失敗。要回溯到之前的分叉選擇步驟(步驟8)。從前文可知,回標列首C6回標列首C3。并把答案棧恢復到步驟8(答案棧中只有4)的時候。又回到下圖所示

?

11、由于之前選擇行3導致無解,因此這次選擇行5(在答案棧中壓入5),標示該行中的其他元素(元素13)所在的列首元素,即標示元素C7,下圖中的橙色部分。

把上圖中的紫色部分和橙色部分移除的話,剩下的綠色部分就如下圖所示

?

12、獲取Head.Right元素,即元素C3,并標示元素C3。如下圖中的紫色部分。

?

13、如上圖,列C3只有元素1覆蓋,故答案只能選擇行3(在答案棧壓入1)。標示該行中的其他元素(元素2和元素3)所在的列首元素,即標示元素C5標示元素C6,下圖中的橙色部分。

把上圖中的紫色部分和橙色部分移除的話,剩下的綠色部分就如下圖所示

?

14、因為Head.Right=Head。故,整個過程求解結束。輸出答案,答案棧中的答案分別是4、5、1。表示該問題的解是第4、5、1行覆蓋所有的列。如下圖所示(藍色的部分)

?

從以上的14步來看,可以把Dancing Links的求解過程表述如下

?

1、Dancing函數的入口

2、判斷Head.Right=Head?,若是,輸出答案,返回True,退出函數。

3、獲得Head.Right的元素C

4、標示元素C

5、獲得元素C所在列的一個元素

6、標示該元素同行的其余元素所在的列首元素

7、獲得一個簡化的問題,遞歸調用Daning函數,若返回的True,則返回True,退出函數。

8、若返回的是False,則回標該元素同行的其余元素所在的列首元素,回標的順序和之前標示的順序相反

9、獲得元素C所在列的下一個元素,若有,跳轉到步驟6

10、若沒有,回標元素C,返回False,退出函數。

?

?

?

之前的文章的表述,為了表述簡單,采用面向對象的思路,說每個元素有6個分量,分別是Left、Right、Up、Down、Col、Row分量。

但在實際的編碼中,用數組也能實現相同的作用。例如:用Left()表示所有元素的Left分量,Left(1)表示元素1的Left分量

在前文中,元素分為Head元素、列首元素(C1、C2等)、普通元素。在編碼中,三種元素統一成一種元素。如上題,0表示Head元素,1表示元素C1、2表示元素C2、……、7表示元素C7,從8開始表示普通元素。這是統一后,編碼的簡便性。利用數組的下標來表示元素,宛若指針一般。

?

?

下面是代碼的講解

?

1、該類的一些變量


??? Private Left() As?Integer, Right() As?Integer, Up() As?Integer, Down() As?Integer
??? Private Row() As?Integer, Col() As?Integer

??? Private _Head As?Integer

??? Private _Rows As?Integer, _Cols As?Integer, _NodeCount As?Integer
??? Private Ans() As?Integer

?

前兩行表示每個元素的六個分量,用數組表示;_Head表示元素Head,在類中初始化時令其等于0;_Rows表示矩陣的行數,_Cols表示矩陣的列數,_NodeCount表示元素的個數;Ans()用于存放答案

?

?

2、求解的主函數,Dance函數,是個遞歸函數,參數K表示當前的調用層數。


??? Public?Function Dance() As?Integer()
??????? Return IIf(Dance(0) = True, Ans, Nothing)
??? End?Function?

??? Private?Function Dance(ByVal K As?Integer) As?Boolean

??????? Dim C1 As?Integer = Right(_Head)
??????? If (C1 = _Head) Then
??????????? ReDim?Preserve Ans(K - 1)
??????????? Return?True
??????? End?If
??????? RemoveCol(C1)

??????? Dim I As?Integer, J As?Integer

??????? I = Down(C1)
??????? Do?While I <> C1
??????????? Ans(K) = Row(I)

??????????? J = Right(I)
??????????? Do?While J <> I
??????????????? RemoveCol(Col(J))
??????????????? J = Right(J)
??????????? Loop

??????????? If Dance(K + 1) Then?Return?True

??????????? J = Left(I)
??????????? Do?While J <> I
??????????????? ResumeCol(Col(J))
??????????????? J = Left(J)
??????????? Loop

??????????? I = Down(I)
??????? Loop?

??????? ResumeCol(C1)
??????? Return?False
??? End?Function

?

其中第一個函數Dance是對外開放的函數,它通過調用Dance(0)來求解問題,根據返回值來決定返回答案(當為True的時候)還是返回空(當為False的時候)

第二個函數是求解的主函數。首先通過Right(_Head)獲得_Head元素的右元素。判斷是否等于自身,若是,求解結束,因為答案保存在Ans(0)到Ans(K-1)中,所以先把答案數組中多余的部分去除(利用Redim語句)。

RemoveCol函數是用來標示列首元素的,ResumeCol函數用來回標列首元素的,其中通過Col(J)獲得J元素的列首元素。在函數中有個很聰明的設計,在標示列首元素時,順序是從I元素的右側元素開始;而在回標列首元素時,順序是從I元素的左側元素開始,正好順序和標示列首元素的順序相反。

在調用Dance(K+1)前,把當前選中的行保存到Ans(K)中,當Dance(K+1)返回True時,說明遞歸調用獲得正確的解,那直接返回True;返回False時,說明當前選擇的行不正確,回標列首元素,獲得下一個元素。

當元素C1中所在的列的其余元素所選定的行沒有求解正確的遞歸函數時(包括C1列沒有其余的元素),說明當前的求解失敗,回標列首元素C1,返回False

?

?

3、求解的輔助函數,RemoveCol函數,標示列首函數


??? Public?Sub RemoveCol(ByVal Col As?Integer)

??????? Left(Right(Col)) = Left(Col)
??????? Right(Left(Col)) = Right(Col)

??????? Dim I As?Integer, J As?Integer

??????? I = Down(Col)
??????? Do?While I <> Col
??????????? J = Right(I)
??????????? Do?While J <> I
??????????????? Up(Down(J)) = Up(J)
??????????????? Down(Up(J)) = Down(J)
??????????????? J = Right(J)
??????????? Loop

??????????? I = Down(I)
??????? Loop

??? End?Sub

?

首先,利用Left(Right(Col)) = Left(Col) 和Right(Left(Col)) = Right(Col) 把列首元素Col從水平雙向鏈中移除出去。再依次把Col所在的列的其余元素的所在行的其余元素從垂直雙向鏈中移除出去,利用的是Up(Down(J)) = Up(J) 和Down(Up(J)) = Down(J)。找尋Col所在列的其余元素的順序是從下邊(Down分量)開始,移除所在行其余元素的順序是從右邊(Right分量)開始 。可以參考之前的圖中的紫色部分。

?

4、求解的輔助函數,ResumeCol函數,回標列首函數


??? Public?Sub ResumeCol(ByVal Col As?Integer)

??????? Left(Right(Col)) = Col
??????? Right(Left(Col)) = Col

??????? Dim I As?Integer, J As?Integer

??????? I = Up(Col)

??????? Do?While (I <> Col)
??????????? J = Right(I)
??????????? Do?While J <> I
??????????????? Up(Down(J)) = J
??????????????? Down(Up(J)) = J
??????????????? J = Right(J)
??????????? Loop
??????????? I = Up(I)
??????? Loop

??? End?Sub

?

首先,利用Left(Right(Col)) = Col 和Right(Left(Col)) = Col 把列首元素Col恢復到水平雙向鏈中。再依次把Col所在的列的其余元素的所在行的其余元素恢復到垂直雙向鏈中,利用的是Up(Down(J)) = J 和Down(Up(J)) = J。找尋Col所在列的其余元素的順序是從上邊(Up分量)開始(和之前的RemoveCol函數相反),恢復所在行其余元素的順序是從右邊(Right分量)開始 。

?

5、類的初始化函數


??? Public?Sub?New(ByVal Cols As?Integer)
??????? ReDim Left(Cols), Right(Cols), Up(Cols), Down(Cols), Row(Cols), Col(Cols), Ans(Cols)
??????? Dim I As?Integer

??????? Up(0) = 0
??????? Down(0) = 0
??????? Right(0) = 1
??????? Left(0) = Cols

??????? For I = 1 To Cols
??????????? Up(I) = I
??????????? Down(I) = I
??????????? Left(I) = I - 1
??????????? Right(I) = I + 1
??????????? Col(I) = I
??????????? Row(I) = 0
??????? Next

??????? Right(Cols) = 0

??????? _Rows = 0
??????? _Cols = Cols
??????? _NodeCount = Cols
??????? _Head = 0
??? End?Sub

?

初始化函數有一個參數Cols,表示這個矩陣的列數。

初始化的時候,由于沒有傳入矩陣元素的信息。因此,在該函數中先把輔助元素完成

0表示Head元素,1-Cols表示Cols個列的列首元素

第一句,重定義六個分量的數組,表示Head元素和列首元素的六個分量。

Right(0) = 1表示Head元素的Right分量指向列首元素1(第1列的列首元素);Left(0) = Cols表示Head元素的Left分量指向列首元素Cols(第Cols列的列首元素)

后面的一段循環,給每個列首元素指定六個分量。Up和Down分量指向自己,Left分量指向左邊的列首元素(I-1),Right分量指向右邊的列首元素(I+1),Col分量指向自己,Row分量為0,參看前面的圖。最后Right(Cols)=0,Cols列的列首元素的Right分量指向Head元素

其后是一些變量的賦值。把_Head賦值為0,表示0為Head元素,是為了后面的代碼的直觀性

?

?

6、添加矩陣元素的函數


??? Public?Sub AppendLine(ByVal?ParamArray Value() As?Integer)
??????? _Rows += 1
??????? If Value.Length = 0 Then?Exit Sub

??????? Dim I As?Integer, K As?Integer = 0

??????? For I = 0 To Value.Length - 1
??????????? If Value(I) = 1 Then
??????????????? _NodeCount += 1
??????????????? ReDim?Preserve Left(_NodeCount)
??????????????? ReDim?Preserve Right(_NodeCount)
??????????????? ReDim?Preserve Up(_NodeCount)
??????????????? ReDim?Preserve Down(_NodeCount)
??????????????? ReDim?Preserve Row(_NodeCount)
??????????????? ReDim?Preserve Col(_NodeCount)
??????????????? ReDim?Preserve Ans(_NodeCount)
??????????????? If K = 0 Then
??????????????????? Left(_NodeCount) = _NodeCount
??????????????????? Right(_NodeCount) = _NodeCount
??????????????????? K = 1
??????????????? Else
??????????????????? Left(_NodeCount) = _NodeCount - 1
??????????????????? Right(_NodeCount) = Right(_NodeCount - 1)
??????????????????? Left(Right(_NodeCount - 1)) = _NodeCount
??????????????????? Right(_NodeCount - 1) = _NodeCount
??????????????? End?If

??????????????? Down(_NodeCount) = I + 1
??????????????? Up(_NodeCount) = Up(I + 1)
??????????????? Down(Up(I + 1)) = _NodeCount
??????????????? Up(I + 1) = _NodeCount?

??????????????? Row(_NodeCount) = _Rows
??????????????? Col(_NodeCount) = I + 1
??????????? End?If
??????? Next

??? End?Sub

?

把矩陣的一行元素(包括0和1)添加到類中

在前文中介紹了Dancing Links中只存儲1的元素(稀疏矩陣),因此,在添加的時候,先判斷值是否是1。

那實際上問題是如何把元素添加到雙向鏈中,在添加的過程中,自左向右添加。

?

先考量如何把元素添加到水平雙向鏈中

當添加這一行的第一個元素時,由于還沒有雙向鏈,首先構造一個只有一個元素的雙向鏈。Left(_NodeCount) = _NodeCount和Right(_NodeCount) = _NodeCount。這個元素的Left和Right分量都指向自己。

從第二個元素開始。問題就轉換為把元素添加到水平雙向鏈的末尾,實際上需要知道之前的水平雙向鏈的最左邊的元素和最右邊的元素,可以肯定的是最右邊的元素是_NodeCount-1,最左邊的元素是什么?之前并沒有緩存啊。由于是循環雙向鏈,Right(_NodeCount-1)就是這雙向鏈的最左邊的元素。Left(_NodeCount) = _NodeCount - 1,把當前元素的Left分量指向最右邊的元素即_NodeCount-1;Right(_NodeCount) = Right(_NodeCount - 1) ,把當前元素的Right分量指向最左邊的元素即Right(_NodeCount-1);Left(Right(_NodeCount - 1)) = _NodeCount,把最左邊的元素即Right(_NodeCount-1)的Left分量指向當前元素;Right(_NodeCount - 1) = _NodeCount,把最右邊的元素即_NodeCount-1的Right分量指向當前元素

?

再考量如何把元素添加到垂直雙向鏈

同樣,問題就轉換為把元素添加到垂直雙向鏈的末尾,實際上需要知道之前的垂直雙向鏈的最上邊的元素和最下邊的元素。和水平雙向鏈的不同,我們沒法知道最下邊的元素,但是我們可以利用列首元素知道最上邊的元素(列首元素就是該雙向鏈中最上邊的元素)。因此,最上邊的元素是I+1(因為I是從0開始的,故相應的列就是I+1,相應的列首元素就是I+1),那么最下邊的元素就是Up(I+1)。Down(_NodeCount) = I + 1,把當前元素的Down分量指向最上邊的元素即I+1;Up(_NodeCount) = Up(I + 1) ,把當前元素的Up分量指向最下邊的元素即Up(I+1);Down(Up(I + 1)) = _NodeCount,把最下邊元素即Up(I+1)的Down分量指向當前元素;Up(I + 1) = _NodeCount,把最上邊元素即I+1的Up分量指向當前元素

?

至此,完成了把當前元素添加到兩個雙向鏈的過程

最后,給當前元素的Row分量和Col分量賦值

?

在文首的題目中,添加第一行的數據,如下調用

AppendLine(0,0,1,0,1,1,0)

?

?

如果一行中有大量的0,那么用下面的函數比較方便


??? Public?Sub AppendLineByIndex(ByVal?ParamArray Index() As?Integer)
??????? _Rows += 1
??????? If Index.Length = 0 Then?Exit Sub

??????? Dim I As?Integer, K As?Integer = 0?

??????? ReDim?Preserve Left(_NodeCount + Index.Length)
??????? ReDim?Preserve Right(_NodeCount + Index.Length)
??????? ReDim?Preserve Up(_NodeCount + Index.Length)
??????? ReDim?Preserve Down(_NodeCount + Index.Length)
??????? ReDim?Preserve Row(_NodeCount + Index.Length)
??????? ReDim?Preserve Col(_NodeCount + Index.Length)
??????? ReDim?Preserve Ans(_NodeCount + Index.Length)

??????? For I = 0 To Index.Length - 1

??????????? _NodeCount += 1

??????????? If I = 0 Then
??????????????? Left(_NodeCount) = _NodeCount
??????????????? Right(_NodeCount) = _NodeCount
??????????? Else
??????????????? Left(_NodeCount) = _NodeCount - 1
??????????????? Right(_NodeCount) = Right(_NodeCount - 1)
??????????????? Left(Right(_NodeCount - 1)) = _NodeCount
??????????????? Right(_NodeCount - 1) = _NodeCount
??????????? End?If

??????????? Down(_NodeCount) = Index(I)
??????????? Up(_NodeCount) = Up(Index(I))
??????????? Down(Up(Index(I))) = _NodeCount
??????????? Up(Index(I)) = _NodeCount

??????????? Row(_NodeCount) = _Rows
??????????? Col(_NodeCount) = Index(I)
??????? Next?

??? End?Sub

該函數的參數是這一行中值為1的元素的所在列的下標,具體就不再解釋了。和AppendLine函數類似。

在文首的題目中,添加第一行的數據,如下調用

AppendLineByIndex(3,5,6)

和AppendLine(0,0,1,0,1,1,0)效果相同。

?

下面的代碼是調用該類求解文首題目的代碼

?

Dim tS As New clsDancingLinks(7)


tS.AppendLineByIndex(3, 5, 6)
tS.AppendLineByIndex(1, 4, 7)
tS.AppendLineByIndex(2, 3, 6)
tS.AppendLineByIndex(1, 4)
tS.AppendLineByIndex(2, 7)
tS.AppendLineByIndex(4, 5, 7)

?

Dim Ans() As Integer = tS.Dance

?

Ans()數組中的值是4,5,1

?

?

至此,求解精確覆蓋問題的Dancing Links算法就介紹完了。利用十字循環雙向鏈這個特殊的數據結構,不可思議的完成了緩存矩陣和回溯矩陣的過程,十分優雅,十分高效。故Donald E.Knuth把它稱為Dancing Links(舞蹈鏈)。我更喜歡跳躍的舞者這個名字

?

有很多問題都能轉換為精確覆蓋問題,再利用Dancing Links算法求解就方便多了。

?

?

最后,把該類的完整代碼貼在下方



Public?Class?clsDancingLinks
??? Private Left() As?Integer, Right() As?Integer, Up() As?Integer, Down() As?Integer
??? Private Row() As?Integer, Col() As?Integer

??? Private _Head As?Integer

??? Private _Rows As?Integer, _Cols As?Integer, _NodeCount As?Integer
??? Private Ans() As?Integer

??? Public?Sub?New(ByVal Cols As?Integer)
??????? ReDim Left(Cols), Right(Cols), Up(Cols), Down(Cols), Row(Cols), Col(Cols), Ans(Cols)
??????? Dim I As?Integer

??????? Up(0) = 0
??????? Down(0) = 0
??????? Right(0) = 1
??????? Left(0) = Cols

??????? For I = 1 To Cols
??????????? Up(I) = I
??????????? Down(I) = I
??????????? Left(I) = I - 1
??????????? Right(I) = I + 1
??????????? Col(I) = I
??????????? Row(I) = 0
??????? Next

??????? Right(Cols) = 0

??????? _Rows = 0
??????? _Cols = Cols
??????? _NodeCount = Cols
??????? _Head = 0
??? End?Sub

??? Public?Sub AppendLine(ByVal?ParamArray Value() As?Integer)
??????? _Rows += 1
??????? If Value.Length = 0 Then?Exit Sub

??????? Dim I As?Integer, K As?Integer = 0

??????? For I = 0 To Value.Length - 1
??????????? If Value(I) = 1 Then
??????????????? _NodeCount += 1
??????????????? ReDim?Preserve Left(_NodeCount)
??????????????? ReDim?Preserve Right(_NodeCount)
??????????????? ReDim?Preserve Up(_NodeCount)
??????????????? ReDim?Preserve Down(_NodeCount)
??????????????? ReDim?Preserve Row(_NodeCount)
??????????????? ReDim?Preserve Col(_NodeCount)
??????????????? ReDim?Preserve Ans(_NodeCount)
??????????????? If K = 0 Then
??????????????????? Left(_NodeCount) = _NodeCount
??????????????????? Right(_NodeCount) = _NodeCount
??????????????????? K = 1
??????????????? Else
??????????????????? Left(_NodeCount) = _NodeCount - 1
??????????????????? Right(_NodeCount) = Right(_NodeCount - 1)
??????????????????? Left(Right(_NodeCount - 1)) = _NodeCount
??????????????????? Right(_NodeCount - 1) = _NodeCount
??????????????? End?If

??????????????? Down(_NodeCount) = I + 1
??????????????? Up(_NodeCount) = Up(I + 1)
??????????????? Down(Up(I + 1)) = _NodeCount
??????????????? Up(I + 1) = _NodeCount?

??????????????? Row(_NodeCount) = _Rows
??????????????? Col(_NodeCount) = I + 1
??????????? End?If
??????? Next

??? End?Sub

??? Public?Sub AppendLineByIndex(ByVal?ParamArray Index() As?Integer)
??????? _Rows += 1
??????? If Index.Length = 0 Then?Exit Sub

??????? Dim I As?Integer, K As?Integer = 0?

??????? ReDim?Preserve Left(_NodeCount + Index.Length)
??????? ReDim?Preserve Right(_NodeCount + Index.Length)
??????? ReDim?Preserve Up(_NodeCount + Index.Length)
??????? ReDim?Preserve Down(_NodeCount + Index.Length)
??????? ReDim?Preserve Row(_NodeCount + Index.Length)
??????? ReDim?Preserve Col(_NodeCount + Index.Length)
??????? ReDim?Preserve Ans(_NodeCount + Index.Length)

??????? For I = 0 To Index.Length - 1

??????????? _NodeCount += 1

??????????? If I = 0 Then
??????????????? Left(_NodeCount) = _NodeCount
??????????????? Right(_NodeCount) = _NodeCount
??????????? Else
??????????????? Left(_NodeCount) = _NodeCount - 1
??????????????? Right(_NodeCount) = Right(_NodeCount - 1)
??????????????? Left(Right(_NodeCount - 1)) = _NodeCount
??????????????? Right(_NodeCount - 1) = _NodeCount
??????????? End?If

??????????? Down(_NodeCount) = Index(I)
??????????? Up(_NodeCount) = Up(Index(I))
??????????? Down(Up(Index(I))) = _NodeCount
??????????? Up(Index(I)) = _NodeCount

??????????? Row(_NodeCount) = _Rows
??????????? Col(_NodeCount) = Index(I)
??????? Next


??? End?Sub

??? Public?Function Dance() As?Integer()
??????? Return IIf(Dance(0) = True, Ans, Nothing)
??? End?Function


??? Private?Function Dance(ByVal K As?Integer) As?Boolean

??????? Dim C1 As?Integer = Right(_Head)
??????? If (C1 = _Head) Then
??????????? ReDim?Preserve Ans(K - 1)
??????????? Return?True
??????? End?If
??????? RemoveCol(C1)

??????? Dim I As?Integer, J As?Integer

??????? I = Down(C1)
??????? Do?While I <> C1
??????????? Ans(K) = Row(I)

??????????? J = Right(I)
??????????? Do?While J <> I
??????????????? RemoveCol(Col(J))
??????????????? J = Right(J)
??????????? Loop

??????????? If Dance(K + 1) Then?Return?True

??????????? J = Left(I)
??????????? Do?While J <> I
??????????????? ResumeCol(Col(J))
??????????????? J = Left(J)
??????????? Loop

??????????? I = Down(I)
??????? Loop?

??????? ResumeCol(C1)
??????? Return?False
??? End?Function

??? Public?Sub RemoveCol(ByVal Col As?Integer)

??????? Left(Right(Col)) = Left(Col)
??????? Right(Left(Col)) = Right(Col)

??????? Dim I As?Integer, J As?Integer

??????? I = Down(Col)
??????? Do?While I <> Col
??????????? J = Right(I)
??????????? Do?While J <> I
??????????????? Up(Down(J)) = Up(J)
??????????????? Down(Up(J)) = Down(J)
??????????????? J = Right(J)
??????????? Loop

??????????? I = Down(I)
??????? Loop

??? End?Sub

??? Public?Sub ResumeCol(ByVal Col As?Integer)

??????? Left(Right(Col)) = Col
??????? Right(Left(Col)) = Col

??????? Dim I As?Integer, J As?Integer

??????? I = Up(Col)

??????? Do?While (I <> Col)
??????????? J = Right(I)
??????????? Do?While J <> I
??????????????? Up(Down(J)) = J
??????????????? Down(Up(J)) = J
??????????????? J = Right(J)
??????????? Loop
??????????? I = Up(I)
??????? Loop

??? End?Sub
End?Class

轉載于:https://www.cnblogs.com/MisdomTianYa/p/6581710.html

總結

以上是生活随笔為你收集整理的DLX (Dancing Links/舞蹈链)算法——求解精确覆盖问题的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。