简单易懂的Dancing links讲解(4)
DancingLinks的應(yīng)用
??????? 把dancingLink應(yīng)用于實際問題時,只有一個難點,就是如何把具體的問題轉(zhuǎn)換為可以精確覆蓋的01矩陣模型,一旦完成了這個步后,直接套用模板就可以解決問題了。
應(yīng)用之一:傷腦筋十二塊???????????????
傷腦筋十二塊是dancing links精確覆蓋的典型應(yīng)用,理解起來最容易??????
圖2,12片5格骨牌的拼圖
題目描述:?????????
給你12個如上圖的5格骨牌,如何讓程序拼出如上的“口”字圖形。??????????
上圖是題目的一個答案,你知道程序如何得到這個答案的嗎?沒錯,就是用dancinglink的精確覆蓋?????????????
我們想象一個有72列的矩陣,其中12列是12個骨牌,剩下60列是60個非中心部分的格子,構(gòu)造出?????????
所有可能的行來代表在一塊骨牌在棋盤上得放置方案;每行有一些‘’1“,用來標(biāo)識被覆蓋的格子,5個1標(biāo)識一個骨牌放置的位置(恰有1568個這樣的行)?????????????
我們將最前面的12列命名為F,I,L,P,N,T,U,V,W,X,Y,Z,并且我們可以用兩個數(shù)字i,j給矩陣中對應(yīng)棋盤上的第i行第j列格子的那一列命名。通過給出那些出現(xiàn)了‘’1"的列的名字,可以很方便地表示每一行。? ??????
例如,圖2就是與下面12行的對應(yīng)的精確覆蓋。????????????
1568個行指的是12個骨牌可放置方案的總和,比如長條骨牌I共有64種放置方案,1568中就包含了這64種??????????????????????????????
這1568行中,每行都有6個1,分布在72個列中?????????????????????????????????
這個矩陣的構(gòu)造思路是:??????????????????????????????????
首先找約束關(guān)系,這里只有兩個約束關(guān)系,??????????????????????????????
(1)12個骨牌,每種只有1個,?????????????????????????????????
(2)60個空格中,一個位置只能放一種骨牌(否則就要重疊著放了)????????????????????????????????
因為第一個約束關(guān)系,有了12個列來區(qū)分骨牌種類,因為第二個約束關(guān)系,有了60個選5個來表示骨牌放置????
應(yīng)用之二:數(shù)獨問題 sudoku???????????
解數(shù)獨,生成數(shù)獨,都可以使用精確覆蓋,要把數(shù)獨問題構(gòu)造成01矩陣還是有一定的難度???????????
首先找約束關(guān)系,這里只有四個約束關(guān)系,?????????
(1)81個格子中每個格子只能放一個數(shù)字??
(2)每一行的數(shù)字不能重復(fù)??????
(3)每一列的數(shù)字不能重復(fù)??????
(4)每一九宮內(nèi)的數(shù)字不能重復(fù)
四個約束關(guān)系中,每個約束對應(yīng)一個列域,對于第二個約束關(guān)系,數(shù)獨中共有9行,每行可以填9個不同的數(shù)字???????
因此第二個列域,共有9* 9,81個列,依此類推,數(shù)獨問題共有列324個。????????????
由于81個格子,每個格子都最多有9種選擇,所以行最多有81*9=729行?????????????
這樣01矩陣的每行都有4個1,第一個1分布在1到81列,第二個1分布在82到162列,第三個1分布在163到243列,????????
最后一個1分布在其余列區(qū)域。?
?
思考:為什么不能這樣構(gòu)造01矩陣,用5個1,第一個1表示格子序號,有81個列,第二個1表示數(shù)字,從1到9有9個列,第三個1表示行號,有9行,第四個1表示列號也有9個,第五個1表示九宮格序號,也有9個,這樣共有117列。????????????
????????
為了便于理解,舉個例子?????????????
??? 9,2,0,0,0,0,0,0,0,????????
??? 5,0,0,8,7,0,0,0,0,????????
??? 0,3,8,0,9,1,0,0,0,????????
??? 0,5,2,9,3,0,1,6,0,????????
??? 0,9,0,0,0,0,0,3,0,????????
??? 0,7,3,0,6,4,9,8,0,????????
??? 0,0,0,4,1,0,2,5,0,????????
??? 0,0,0,0,5,3,0,0,1,????????
??? 0,0,0,0,0,0,0,7,3?????????
如上數(shù)獨有空格40個,已知格子41個,把這個數(shù)獨構(gòu)造成01矩陣,矩陣的行有?????????????
40*9+41? 共401行????
對于第一個數(shù)字9,在1到81列的第一列,在82到162列的第9個,即90列,在163列到243列的第9個,在244到324列的第9個各占一個1???????????
對于第三個數(shù)字0,由于有9個選擇,所以在構(gòu)造01矩陣時,要向矩陣插入9個行,來表示各種可能?????????????
對于第四個數(shù)字8,它在二行四列,把這個數(shù)字寫入dancing link的網(wǎng)狀數(shù)據(jù)結(jié)構(gòu)時,需要新增四個節(jié)點,這四個節(jié)點都在同一行,它們的列序號分別為,
13, 81 + 9 + 8 - 1,81 + 81 +?3 * 9 + 8 - 1,81 +?81 +?81 + 9 ?+ 8 - 1, ?序號是從0開始的,所有要減去一。
現(xiàn)在假設(shè),2行6列的空格是數(shù)字8,那么這個數(shù)字也會對應(yīng)四個節(jié)點,列序號分別為
15,81 + 9 + 8 - 1, 81 +?81 + 5 * 9 + 8 - 1, 81 + 81 + 81 + 9 + 8 - 1,
可以看到, ? 這兩個8的 行域,九宮格域都是相同的,表示這兩個數(shù)字的行和九宮格都相沖了,四個列域,只要有一個相沖,兩條記錄就不能共存。這兩個8顯然不能共存。
數(shù)獨還有一個變種,對角線數(shù)獨,兩條對角線的數(shù)字也不能重復(fù),這時構(gòu)造01矩陣模型時,就需要額外增加兩個列域,左對角線域,右對角線域。增加的兩個列域都只有9列,
對于1行1列的數(shù)字,會在01矩陣模型中對應(yīng)5個節(jié)點,
對于2行3列的數(shù)字,由于不位于兩條對角線上,會在01矩陣模型中只對應(yīng)4個節(jié)點,?
對于5行5列的數(shù)字,恰好在兩條對角線的交匯處,會在01矩陣模型中對應(yīng)6個節(jié)點
對于數(shù)獨的生成?????????????
總體思路是一行一行的生成,第一行可以用一個隨機的1到9的排列,接下來的8行,每行都要用dancinglink求解可行的排序?????????????
(1)先對1到9這9個數(shù)進行隨機排列,把這個排列作為數(shù)獨終盤布局的第一行?????????????
(2)自己寫函數(shù)篩選出下一行,每個格子可以填寫的數(shù)字集合,篩選時不用考慮行沖突????????
比如對于排列5,9,7,4,2,6,8,3,1?????????????
篩選結(jié)果如下:? 123468,123468,123468,135789,135789,135789,245679,245679,245679??
表示對于下一行的1,2,3列,可以選擇的數(shù)字集合有1,2,3,4,6,8.???????????
下一行的4,5,6列,可以選擇的數(shù)字集合有1,3,5,7,8,9???????
下一行的7,8,9列,可以選擇的數(shù)字集合有2,4,5,6,7,9???????
這時,構(gòu)造01矩陣,就只有2個約束關(guān)系???????????
1 ? ? ? 對于下一行的9個格子,每個格子只能放一個數(shù)字????
2?????? 對于下一行的9個格子中的數(shù)字,每個數(shù)字都不能重復(fù)?
????????
因為第3個和4個約束,已經(jīng)在篩選時考慮進去,這里不需再多此一舉??????????
這時的01矩陣,列有9+ 9=18個,行有6* 9 = 54行(6+6+6+6+6+6+6+6+6)。
?
應(yīng)用之三:N皇后?????????
N皇后問題也可以轉(zhuǎn)換為dancinglinks的精確覆蓋問題???????????
這里只講如何把n皇后問題轉(zhuǎn)換為01矩陣,首先有四個約束關(guān)系?????????????
(1)所有皇后不能在同一行???????
(2)所有皇后不能在同一列???????
(3)所有皇后不能在同一左斜線?????????????
(4)所有皇后不能在同一右斜線 ? ? ? ? ? ??
為了便于理解,舉個例子?????????????
n=8時,有8行,8列,15個左斜線,15個右斜線(2*n-1)???????????
這樣構(gòu)造的矩陣有46個列,8*8=64個行???????
矩陣的每行都有4個1,分別分布在行域,列域,左斜線域,右斜線域???????????
在編程求解這個問題時,需要做一點變通,因為左斜線域,右斜線域的列不可能被全部覆蓋?????????
因此只需行域和列域被完全覆蓋就算找到問題的一個解了。
附:
dancing LInks 求解數(shù)獨的C++代碼
#include<iostream> #include<string.h>using namespace std;struct Node {Node *up;Node *down;Node *left;Node *right;Node *colRoot; //列首int row; //所在行int sum; //此列節(jié)點總數(shù) };#define R 729 #define C 324class Dlx { public:Node *nodes,*row,*col,*head;//可用節(jié)點,行首,列首,總頭節(jié)點int rowNum,colNum,nodeCount;//行數(shù),列數(shù),總節(jié)點數(shù)int *result,resultCount;//結(jié)果,結(jié)果行數(shù)Dlx(){nodes=new Node[R*C];//直接用數(shù)組竟然運行不起,棧溢出了,還得放在堆里row=new Node[R];col=new Node[C+1];result=new int[R];}~Dlx(){delete []nodes;delete []row;delete []col;delete []result;}void init(int r,int c);//初始化void cover(Node *t);//覆蓋一列void uncover(Node *t);//取消覆蓋bool solove(int k=0);//搜索出結(jié)果void addNode(int r,int c);//添加一個節(jié)點 };void Dlx::init(int r,int c) {int i;rowNum=r;colNum=c;//將各列連起來,col[colNum]為總頭節(jié)點for(i=0;i<=colNum;i++){col[i].up=col[i].down=col+i;col[i].left=col + (i+colNum)%(1+colNum);col[i].right=col + (i+1)%(1+colNum);col[i].sum=0;}head=col+colNum;//將各行節(jié)點數(shù)清零for(i=0;i<rowNum;i++){row[i].up=row[i].down=row[i].left=row[i].right=row[i].colRoot=row+i;}nodeCount=0;//總節(jié)點數(shù)清零 }void Dlx::addNode(int r,int c) {nodes[nodeCount].up=col[c].up;nodes[nodeCount].down=col+c;nodes[nodeCount].left=row[r].left;nodes[nodeCount].right=row+r;nodes[nodeCount].row=r;nodes[nodeCount].colRoot=col+c;col[c].up=col[c].up->down=row[r].left=row[r].left->right=nodes+nodeCount++; col[c].sum++; }void Dlx::cover(Node *t) {Node *p,*q;t->left->right=t->right;t->right->left=t->left;for(p=t->down;p!=t;p=p->down){for(q=p->right;q!=p;q=q->right){q->up->down=q->down;q->down->up=q->up;q->colRoot->sum--;}} }void Dlx::uncover(Node *t) {Node *p,*q;for(p=t->up;p!=t;p=p->up){for(q=p->left;q!=p;q=q->left){q->up->down=q->down->up=q;q->colRoot->sum++;}}t->left->right=t->right->left=t; }bool Dlx::solove(int k) {//是否還有未覆蓋的列if(head->right==head){//記錄完成覆蓋所用行數(shù)resultCount=k;return true;}Node *pMin,*p,*q;//找到節(jié)點數(shù)最少的一列,并覆蓋for(pMin=head->right,p=pMin->right;p!=head;p=p->right){if(pMin->sum>p->sum)pMin=p;}cover(pMin);for(p=pMin->down;p!=pMin;p=p->down){result[k]=p->row;//選定此列上的一個節(jié)點,將此節(jié)點所在行上所有節(jié)點的對應(yīng)列進行覆蓋for(q=p->right;q!=p;q=q->right)cover(q->colRoot);if(solove(k+1))return true;//如果不能成功,則取消覆蓋for(q=p->left;q!=p;q=q->left)uncover(q->colRoot);}uncover(pMin);return false; } int getRowIndex(int rowNum) {int num = rowNum%9;int rowIndex = rowNum / 81;return 81 + rowIndex*9 + num; } int getColIndex(int rowNum) {int num = rowNum%9;int index = rowNum/9; //位置int colIndex = index%9;return 162 + colIndex*9+num; } int getSquareIndex(int rowNum) {int num = rowNum%9;int index = rowNum/9; //位置int rowIndex = index / 9;int colIndex = index%9;int squareIndex = int(rowIndex/3)*3 + colIndex/3;return 243 + squareIndex*9+num; } int main3() {int i,j;int node4=0;char str[82];Dlx dlx;//cin>>n;dlx.init(729,324);//for(i=0;i<9;i++)//{// cin>> (str+i*9);//}//......52..8.4......3...9...5.1...6..2..7........3.....6...1..........7.4.......3.const char *input = ".2738..1..1...6735.......293.5692.8...........6.1745.364.......9518...7..8..6534.";strcpy(str,input);for(i=0;i<729;i++){//cout << "row=>" << i << "\tcol=> 位置" << i/9 <<"\t行"<<81+i/9/9*9+i%9<<"\t列"<<162+i/9%9*9+i%9<<"\t塊"<< 243+(i/9/9/3*3+i/9%9/3)*9+i%9;//cout << "row=>" << i << "\tcol=> 位置" << i/9 <<"\t行"<<getRowIndex(i)<<"\t列"<<getColIndex(i)<<"\t塊"<<getSquareIndex(i);if(str[i/9]=='.' || str[i/9]-'1'==i%9){node4++;int rowIndex = i;int colIndex = i/9;dlx.addNode(rowIndex,colIndex);//位置沖突dlx.addNode(rowIndex,getRowIndex(i));//行沖突dlx.addNode(rowIndex,getColIndex(i));//列沖突dlx.addNode(rowIndex,getSquareIndex(i));//塊沖突// cout << "\t<=";}//cout << endl;}if(dlx.solove()){//結(jié)果存到字符串中for(i=0;i<81;i++){j=dlx.result[i];str[j/9]='1'+j%9;}//輸出字符串for(i=0;i<9;i++){for(j=0;j<9;j++)cout<<str[i*9+j];cout<<endl;}}return 0; }附,生成數(shù)獨終盤布局的Flex代碼
<?xml version="1.0" encoding="utf-8"?> <s:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:mx="library://ns.adobe.com/flex/mx"><fx:Declarations><!-- Place non-visual elements (e.g., services, value objects) here --></fx:Declarations><fx:Script><![CDATA[import model.DancingNode;private var sample:String="1279,2367,1369,24789,4578,259,13458,1368,456";private var rowArr:Array=[];private var colArr:Array=[];private var head:DancingNode;private var nodes:Array=[];private var cnt:Array=[];private var answer:Array=[];private var answerArr:Array=[];private function _init(restrict:String):void{answerArr=[];answer=[];cnt=[];nodes=[];rowArr=[];colArr=[];var i:int;var colLen:int=18;var rowLen:int=restrict.split(',').join('').length;for(i=0;i<rowLen;i++){var row:DancingNode = new DancingNode(i);rowArr.push(row);}for(i=0;i<=colLen;i++){var col:DancingNode = new DancingNode(-1,i);colArr.push(col);cnt.push(0);}var colArrLen:int = colArr.length;for(i=0;i<colArr.length;i++){var left:int = (i+colArrLen-1)%colArrLen;var right:int = (i+1)%colArrLen;DancingNode(colArr[i]).left=colArr[left];DancingNode(colArr[i]).right=colArr[right];}head = colArr[0];//create linksvar rowIndex:int=0;var arr1:Array = restrict.split(',');for(i=0;i<arr1.length;i++){var arr2:Array = String(arr1[i]).split('');for(var j:int=0;j<arr2.length;j++){var colIndex1:int=i+1;var colIndex2:int=9+int(arr2[j]);addNode(rowIndex,colIndex1,i,int(arr2[j]));addNode(rowIndex,colIndex2,i,int(arr2[j]));rowIndex++;}}for(i=0;i<rowLen;i++){DancingNode(rowArr[i]).left.right=DancingNode(rowArr[i]).right;DancingNode(rowArr[i]).right.left=DancingNode(rowArr[i]).left;}}private function addNode(r:int,c:int,r1:int,c1:int):void{var node:DancingNode = new DancingNode(r,c);node.rowValue=r1;node.colValue=c1;node.up = colArr[c].up;node.down = colArr[c];node.left = rowArr[r].left;node.right = rowArr[r];cnt[c]++;colArr[c].up=colArr[c].up.down=rowArr[r].left=rowArr[r].left.right=node;nodes.push(node);}private function remove(node:DancingNode):void{//trace("remove=>",node.col);node.left.right = node.right;node.right.left = node.left;for(var p:DancingNode=node.down;p!=node;p=p.down){for(var q:DancingNode=p.right;q!=p;q=q.right){q.up.down=q.down;q.down.up=q.up;cnt[q.col]--;}}}private function resume(node:DancingNode):void{//trace("resume=>",node.col);for(var p:DancingNode=node.down;p!=node;p=p.down){for(var q:DancingNode=p.right;q!=p;q=q.right){q.up.down=q;q.down.up=q;cnt[q.col]++;}}node.left.right = node;node.right.left = node;}private function dancing(depth:int):Boolean{//是否還有未覆蓋的列if(head.right==head){var arr:Array=[];for(var i:int=0;i<answer.length;i++){var node:DancingNode=answer[i];arr[node.rowValue]=node.colValue;}answerArr.push(arr);return true;}var pMin:DancingNode;var p:DancingNode;//找到節(jié)點數(shù)最少的一列,并覆蓋for(pMin=head.right,p=pMin.right;p!=head;p=p.right){if(cnt[pMin.col] > cnt[p.col]){pMin = p;}}remove(pMin);var q:DancingNode;for(p=pMin.down;p!=pMin;p=p.down){//選定此列上的一個節(jié)點,將此節(jié)點所在行上所有節(jié)點的對應(yīng)列進行覆蓋answer[depth]=p;for(q=p.right;q!=p;q=q.right){remove(colArr[q.col]);}if(dancing(depth+1)){if(answerArr.length > 10){return true;}}for(q=p.left;q!=p;q=q.left){resume(colArr[q.col]);}}resume(pMin);if(answerArr.length > 0){return true;}return false;}private function getSudokuLine(restricts:Array):Array{var arr:Array=[];for(var i:int=0;i<restricts.length;i++){arr.push((restricts[i] as Array).join(''));}_init(arr.join(','));if(dancing(0)){var line:Array = answerArr[int(answerArr.length*Math.random())];trace('getSudokuLine,answer length=>',answerArr.length);return line;}return [];}//得到隨機的1到9的排列private function getRandomArr(value:int):Array{var bak:Array = [];for(var i:int=1;i<=value;i++){bak.push(i);}var randLine:Array=[];while(bak.length>0){var index:int = bak.length*Math.random();randLine.push(bak[index]);bak.splice(index,1);}return randLine;}public function createFullSudoku():Array{var sudokuArr:Array=[];while(sudokuArr.length < 9){sudokuArr=[];sudokuArr.push(getRandomArr(9));for(var i:int=0;i<8;i++){var restricts:Array = getRestricts(sudokuArr);if(restricts.length==0){break;}var line:Array = getSudokuLine(restricts);if(line.length==0){break;}sudokuArr.push(line);}}return sudokuArr;}private function getRestricts(curLayout:Array):Array{var i:int;var ret:Array=[];for(i=0;i<9;i++){var arr:Array=getCandidateNums(curLayout,i);if(arr.length==0){return [];}ret.push(arr);}return ret;}//根據(jù)當(dāng)前布局curLayout,得到index列的候選數(shù)集合private function getCandidateNums(curLayout:Array,index:int):Array{var i:int;var line:Array = [0,1,2,3,4,5,6,7,8,9];if(curLayout.length==0){return [1,2,3,4,5,6,7,8,9];}//列排除for(i=0;i<curLayout.length;i++){line[curLayout[i][index]]=0;}//九宮格排除var col3_3:int = index/3;var row3_3:int = curLayout.length/3;var inRow3_3:int = curLayout.length%3;for(i=row3_3*3;i<row3_3*3+inRow3_3;i++){line[curLayout[i][col3_3*3] ]=0;line[curLayout[i][col3_3*3+1] ]=0;line[curLayout[i][col3_3*3+2] ]=0;}var ret:Array=[];for(i=0;i<line.length;i++){if(line[i]!=0){ret.push(i);}}return ret;}private function createSudoku():void{var arr:Array=createFullSudoku();var arr2:Array=[];for(var i:int=0;i<arr.length;i++){arr2.push((arr[i] as Array).join(','));}area.text = arr2.join('\n');}]]></fx:Script><s:VGroup horizontalCenter="0" verticalCenter="0"><s:TextArea width="300" height="300" id="area"/><s:Button label="get full sudoku" click="createSudoku()"/></s:VGroup> </s:WindowedApplication>package model {public class DancingNode{public var row:int;public var col:int;public var rowValue:int;public var colValue:int;public var up:DancingNode;public var down:DancingNode;public var left:DancingNode;public var right:DancingNode;public function DancingNode(r:int=-1,c:int=-1){row = r;col = c;up = this;down = this;left = this;right = this;}} }
總結(jié)
以上是生活随笔為你收集整理的简单易懂的Dancing links讲解(4)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 生产制造机器设备物联网技术方案
- 下一篇: 初中计算机入门教学计划,初中计算机教学计