Dancing links

 

基本思路(Main Thoughts):

     Dancing link是一种十分优美的数据结构。

     通常配合IDA*,二分等方法解决可以转化为精确覆盖和重复覆盖的题目。

     精确覆盖:在一个01矩阵中选几行,使得这几行组合起来的矩阵每列有且只有一个1

     重复覆盖:每列可以有多个1


 

 

实现步骤(Implementation Steps):

     如果是正常的搜索

     解决步骤:

  1. 选最左边第一个没有被删除的列
  2. 选择一个在1中选出的列中有1的行,其他行直接删除
  3. 删掉2中选出的行剩余的元素所在的列
  4. 重复1,直到有一次没有可选列
  5. 如果一列没有被删除且列中已经没有元素,则回溯重新选择一行

  DLX基本上用到的就是这种思想

     辅助数组:

         R,L,U,D代表当前元素在链表中右左上下的元素

         C代表i点所在列

    S代表i列元素个数

         F,last,Last用于建表,分别是i行第一个元素,i行上一个元素,i列上一个元素。

    

  执行步骤:

    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,退出函数。

  由于篇幅有限,在此贴出大神blog : http://www.cnblogs.com/grenet/p/3145800.html

  以供大家参考。

 

模板(Code):

 

//感谢诚叙同学的修改和注释 http://www.cnblogs.com/Robert-Yuan/

  1 void build(){    //初始化
  2     /*构造矩阵第一行:
  3     1.将第一行的两端环状处理。[提示]:第一行的元素的标号就是其列的标号。
  4     2.对于第一行的所有元素:
  5         连接其左右;标注其行列;清空列中表示的元素个数;清空表示列的上一个元素的d数组。*/
  6 
  7     L[0]=m,R[m]=0;
  8     for(int i=1;i<=m;i++){
  9         L[i]=i-1,R[i-1]=i;
 10         c[i]=i;r[i]=0;
 11         s[i]=0;
 12         d[i]=0;
 13     }
 14     
 15     //清空所有行上的表示上一个元素的last数组和表示首元素的f数组
 16     for(int i=1;i<=n;i++) last[i]=f[i]=0;
 17 }
 18 
 19 void del(int col){    //删列操作
 20     /*
 21         1.在第一行中删除这个列节点。
 22         2.找到每个在这条列上的节点i
 23             将节点i所在的行从列表中删除
 24     */
 25     
 26     L[R[col]]=L[col],R[L[col]]=R[col];    //在第一行中删除这个列节点。
 27     
 28     for(int i=D[col];i!=col;i=D[i])
 29         for(int j=R[i];j!=i;j=R[j])    //将节点i所在的行删除干净[因为是环状的,所以可以删掉这行上的所有节点]
 30             U[D[j]]=U[j],D[U[j]]=D[j],s[c[j]]--;    //这些节点都在列表中不再被查询得到。
 31 }
 32 
 33 void add(int col){    //恢复列操作
 34     /*
 35         1.在第一行中恢复这个列节点。
 36         2.找到每个在这条列上的节点i
 37             将节点i所在的行有顺序的从列表中恢复
 38     */
 39     
 40     R[L[col]]=col,L[R[col]]=col;    //因为删除时其实是间接删除[将其两边元素的指针改变],恢复只需要从它自己出发恢复两边即可。
 41     
 42     for(int i=U[col];i!=col;i=U[i])    
 43         for(int j=L[i];j!=i;j=L[j])    //同删除的顺序相反的添加回来[即后删去的先添加],才保证了顺序,不会错
 44             U[D[j]]=j,D[U[j]]=j,s[c[j]]++;    //同删除的感觉,通过行表找到这些丢失的点,然后从它自己来恢复列表中它们的位置。
 45 }
 46 
 47 bool search(int k){
 48     if(R[0]==0){    //第一行中的所有元素都被删除[即列都被标记,完成了精确覆盖]了
 49         printf("%d",k);
 50         for(int i=1;i<=k;i++)
 51             printf(" %d",r[ans[i]]);
 52         putchar('\n');
 53         return true;
 54     }
 55     int Min=INF,C;
 56     for(int i=R[0];i;i=R[i])
 57         if(Min>s[i]) Min=s[i],C=i;    //优先选择列中元素少的列来处理
 58     del(C);
 59     for(int i=D[C];i!=C;i=D[i]){    //确定要删掉这列,但是要考虑删掉哪一行[当然是必须要与这一列相交的行]
 60         ans[k+1]=i;
 61         for(int j=R[i];j!=i/*环状链表的好处,可以循环寻找,找到所有元素*/;j=R[j]) del(c[j]);    //当确定删掉i行时,还要删掉所有与其相交的列
 62         if(search(k+1)) return true;
 63         for(int j=L[i];j!=i;j=L[j])    /*环状链表的另一个好处,可以让添加的顺序与删除的顺序相反,从而达到后删去的先添加的形式*/
 64             add(c[j]);    //回溯过程,补充回原来删去的列。
 65     }
 66     add(C);
 67     return false;
 68 }
 69 
 70 void link(int row/**/,int col/**/){
 71     size++;    //点的数目++
 72     s[col]++;    //所在列的元素个数++
 73     
 74     /*行操作:
 75         如果这一行之前没有元素了,这个元素作为本行的首元素,记录在f[row]中;并记录在表示上一个元素的last[row]中。
 76         若所在行之前还有元素,将这个元素与上一个元素连起来,更新last[];并将这个元素的右端与该行的首元素连起来,形成一个环状的结构。*/
 77     if(!last[row]) 
 78         last[row]=size,
 79         R[size]=size,L[size]=size,
 80         f[row]=size;
 81     else
 82         L[size]=last[row],R[last[row]]=size,
 83         last[row]=size,
 84         R[size]=f[row],L[f[row]]=size;
 85     
 86     /*列操作:
 87         【与行操作有所不同】:我们事先已经构造出了矩阵的第一行,不存在所谓首元素,因为首元素就是这一列的标号。
 88         如果这一列之前没有添加过元素,将当前元素与这一列的标号首尾相连,更新表示上一个元素的d[col]。
 89         若之前添加过元素,将这个元素与上一个元素连起来;并将这个元素充当此行的末尾,与这一列的标号相连构成一个环状结构;更新d[col]。*/
 90     if(!d[col])
 91         U[col]=size,D[size]=col,
 92         D[col]=size,U[size]=col,
 93         d[col]=size;
 94         
 95     else 
 96         U[col]=size,D[size]=col,
 97         U[size]=d[col],D[d[col]]=size,
 98         d[col]=size;
 99     
100     c[size]=col,r[size]=row; //处理完与其它节点的关系后,再给自己打上行列的标号
101 }
View Code

 

时间&空间复杂度(Time & Memory Complexity):

     空间:O(?)

     时间:O(?)

主要用途&优缺点(Main Applications & Advantages & Disadvantages):

  主要用途: 通常配合IDA*,二分等方法解决可以转化为精确覆盖和重复覆盖的题目。

       优点:快

       缺点:长

推荐题目&数据(Recommendatory Problems & Data) :

     Hustoj 1017 裸的精确覆盖

     UVA 1603 破坏正方形 DLX重复覆盖

 

 

posted @ 2015-12-16 22:36  puck_just_me  阅读(...)  评论(... 编辑 收藏