From: http://blog.csdn.net/keyboardlabourer/article/details/13015689

1.概述

Dacing Links (DLX) 算法是Donald Knuth [2]提出,用以解决精确覆盖(exact cover)问题,是X算法在计算机上的优化。

1.1 精确覆盖问题

所谓精确覆盖,是指两两不相交的子集的集合,这些子集的并集可以得到全集。完整的定义 [1]如下:

在一个全集X中若干子集的集合为S,精确覆盖是指,S的子集S*,满足X中的每一个元素在S*中恰好出现一次。

举例:令 S = {N, O, E, P} 是集合X = {1, 2, 3, 4}的一个子集,并满足:
N = { }
O = {1, 3}
E = {2, 4}
P = {2, 3}.
其中一个子集 {O, E} 是 X的一个精确覆盖,因为 O = {1, 3} 而 E = {2, 4} 的并集恰好是 X = {1, 2, 3, 4}。同理, {N, O, E} 也是 X 的一个精确覆盖。

用关系矩阵来表示S的每个子集与X的元素之间包含关系,矩阵每行表示S的一个子集,每列表示X中的一个元素。矩阵行列交点元素为1表示对应的元素在对应的集合中,不在则为0。

精确覆盖问题转化成了求矩阵的若干个行的集合,使每列有且仅有一个1。S* = {B, D, F} 便是一个精确覆盖。

1.2 双向十字链表

实现DLX算法的数据结构是双向十字链表,现在先简单介绍一下双向十字链表。

双向十字链表用LRUD来记录,LR来记录左右方向的双向链表,UD来记录上下方向的双向链表。比如,对6*7矩阵

用双向十字链表可以表示如下:

其中,h代表总的头链表head,ABCDEFG为列的指针头。

双向十字链表可以用数组来加以模拟。对4*4的01矩阵([4]中的一个例子)

1 1  0 0

0 0  0 1

0 1  1 1

1 0  1 0

LRUD的双向十字链表结构如下:

其中,把头节点head编号为0,列分别编号为1,2,3,4。第一行的两个1编号为5,6,第二行的一个1编号为7,第三行三个1编号为8,9,10。第四行两个1,编号为11,12。编号的顺序都是从左到右。1列的下一个节点就是编号为5的1,编号为5的1的下面又是编号11的1,编号为5的1的左边和右边都是编号为6的1。

1.3 DLX算法描述

对精确覆盖问题,容易想到一个启发式的递归算法:(1)选中关系矩阵A的列c,则满足A(i, c)=1的行i均不可用,删除列c与所有的行i;(2)对选中的列c,选中行r满足A(r, c)=1;则满足A(r, j)=1的列j也均不可用,删除行与所有的列j;(3)对删除后的A进行递归(1)(2)处理。

上述非确定算法即是X算法,伪代码如下:

如果A是空的,问题解决;成功终止。
否则,选择一个列c(确定的)。
选择一个行r,满足 A[r, c]=1 (不确定的)。
把r包含进部分解。
对于所有满足 A[r,j]=1 的j,
  从矩阵A中删除第j列;
  对于所有满足 A[i,j]=1 的i,
    从矩阵A中删除第i行。
在不断减少的矩阵A上递归地重复上述算法。

对X算法的优化一:在X算法的步骤(2)中选择的行r有可能是错的,为了减少递归次数,则需要回溯。为了便于X算法中有查找、删除等操作以及回溯,可采用双向十字链表。假设x 指向双向链的一个节点;L[x] 和R[x] 分别表示x 的前驱节点和后继节点。每个程序员都知道如下操作:

L[R[x]] ← L[x], R[L[x]] ← R[x]               (1) 

将x 从链表删除的操作。但是只有少数程序员意识到如下操作:

L[R[x]] ← x, R[L[x]] ← x                        (2) 

是把x重新链接到双向链中。关于操作(2)的研究促使Knuth写了论文[2],操作(2)为了回溯用的,也正是DLX算法的精髓。

对X算法的优化二:在选择列c时,应选择的是A中所有列中1元素最少的一列。至于为什么选择最少的一列,不在本文讨论之列。如果去掉优化二,写的代码很有可能TLE。

为建立关系矩阵A的双向十字链表、加快运行速度。对每一个对象,记录如下几个信息:

  • L[x], R[x], U[x], D[x], C[x];LR来记录左右方向的双向链表,UD来记录上下方向的双向链表;C[x]是指向其列指针头的地址,即表示x所在的列。
  • head指向总的头指针,head通过LR来贯穿的列指针头。
  • 每一列都有列指针头。行指针头可有可无,为了更方便地建立左右方向的双向链表,加个行指针头还是很有必要的。
  • 另外,开两个数组S[x], O[x];S[x]记录列链表中结点的总数,O[x]用来记录搜索结果。

DLX算法的伪代码如下:

其中,R[h]=h即表示A为空,cover column操作即为X算法中步骤(1),uncover colunm操作即为回溯。关于DLX算法的演示过程请参看[6]。

DLX算法的C代码:

[cpp] view plaincopy
  1. /*remove column c and all row i that A(i,c)==1*/
  2. void re_move(int c)
  3. {
  4. int i,j;
  5. L[R[c]]=L[c];                      //remove column c
  6. R[L[c]]=R[c];
  7. for(i=D[c];i!=c;i=D[i])            //remove row i that (i,c)==1
  8. for(j=R[i];j!=i;j=R[j])
  9. {
  10. U[D[j]]=U[j];
  11. D[U[j]]=D[j];
  12. S[C[j]]--;                 //decrease the count of column C[j]
  13. }
  14. }
  15. /*backtrack, resume*/
  16. void resume(int c)
  17. {
  18. int i,j;
  19. for(i=U[c];i!=c;i=U[i])
  20. for(j=L[i];j!=i;j=L[j])
  21. {
  22. S[C[j]]++;
  23. U[D[j]]=j;
  24. D[U[j]]=j;
  25. }
  26. L[R[c]]=c;
  27. R[L[c]]=c;
  28. }
  29. int dfs(int depth)
  30. {
  31. int i,j,c,min=20;
  32. if(R[0]==0) return 1;               //the matrix A is empty
  33. for(i=R[0];i!=0;i=R[i])             //select the column c which has the fewest number of element
  34. if(S[i]<min)
  35. {
  36. min=S[i];
  37. c=i;
  38. }
  39. re_move(c);
  40. for(i=D[c];i!=c;i=D[i])
  41. {
  42. O[depth]=i;                     //record the result
  43. for(j=R[i];j!=i;j=R[j])
  44. re_move(C[j]);
  45. if(dfs(depth+1))   return 1;
  46. for(j=L[i];j!=i;j=L[j])         //backtrack
  47. resume(C[j]);
  48. }
  49. resume(c);
  50. return 0;
  51. }

2. Referrence

[1] 维基百科,精确覆盖问题.

[2] Donald Knuth, Dancing Links.

[3] 吴豪,隋清宇(sqybi),Dancing Links中文版.

[4] momodi, Dancing Links在搜索中的应用.

[5] mu399,简单易懂的Dancing links讲解(1).

[6] mu399,简单易懂的Dancing links讲解(2).

3. 问题

3.1 POJ 3740

用到了行指针头H[ ],以建立左右方向的双向链表,采用的是头插法。

O[ ] H[ ]数组开成了16, TLE了3次。O[ ] 应该开成最多列数300,H[ ]应该开成17。

源代码:

3740 Accepted 212K 266MS C 1665B 2013-10-24 22:14:13
[cpp] view plaincopy
  1. #include "stdio.h"
  2. #include "string.h"
  3. #define MAX 5000
  4. int L[MAX],R[MAX],U[MAX],D[MAX],C[MAX],S[300],O[300],H[17];
  5. int m,n;
  6. /*remove column c and all row i that A(i,c)==1*/
  7. void re_move(int c)
  8. {
  9. int i,j;
  10. L[R[c]]=L[c];                      //remove column c
  11. R[L[c]]=R[c];
  12. for(i=D[c];i!=c;i=D[i])            //remove row i that (i,c)==1
  13. for(j=R[i];j!=i;j=R[j])
  14. {
  15. U[D[j]]=U[j];
  16. D[U[j]]=D[j];
  17. S[C[j]]--;                 //decrease the count of column C[j]
  18. }
  19. }
  20. /*backtrack, resume*/
  21. void resume(int c)
  22. {
  23. int i,j;
  24. for(i=U[c];i!=c;i=U[i])
  25. for(j=L[i];j!=i;j=L[j])
  26. {
  27. S[C[j]]++;
  28. U[D[j]]=j;
  29. D[U[j]]=j;
  30. }
  31. L[R[c]]=c;
  32. R[L[c]]=c;
  33. }
  34. int dfs(int depth)
  35. {
  36. int i,j,c,min=20;
  37. if(R[0]==0) return 1;               //the matrix A is empty
  38. for(i=R[0];i!=0;i=R[i])             //select the column c which has the fewest number of element
  39. if(S[i]<min)
  40. {
  41. min=S[i];
  42. c=i;
  43. }
  44. re_move(c);
  45. for(i=D[c];i!=c;i=D[i])
  46. {
  47. O[depth]=i;                     //record the result
  48. for(j=R[i];j!=i;j=R[j])
  49. re_move(C[j]);
  50. if(dfs(depth+1))   return 1;
  51. for(j=L[i];j!=i;j=L[j])         //backtrack
  52. resume(C[j]);
  53. }
  54. resume(c);
  55. return 0;
  56. }
  57. void init()
  58. {
  59. int i,j,temp,count;
  60. for(i=1;i<=n;i++)            //初始化列的指针头
  61. {
  62. L[i]=i-1;   R[i]=i+1;
  63. U[i]=i;     D[i]=i;
  64. C[i]=i;
  65. }
  66. L[0]=n;   R[0]=1;
  67. R[n]=0;
  68. memset(H,-1,sizeof(H));
  69. memset(S,0,sizeof(S));
  70. count=n+1;
  71. for(i=1;i<=m;i++)
  72. for(j=1;j<=n;j++)
  73. {
  74. scanf("%d",&temp);
  75. if(!temp)  continue;
  76. if(H[i]==-1)                              //为行i的第一个非零元素
  77. H[i]=L[count]=R[count]=count;
  78. else
  79. {
  80. L[count]=L[H[i]];    R[count]=H[i];   //连接同一行的左右节点
  81. R[L[H[i]]]=count;    L[H[i]]=count;
  82. }
  83. U[count]=U[j];   D[count]=j;              //连接同一列的上下节点
  84. D[U[j]]=count;   U[j]=count;
  85. C[count]=j;                               //该节点属于列j
  86. S[j]++;
  87. count++;
  88. }
  89. }
  90. int main()
  91. {
  92. while(scanf("%d%d",&m,&n)!=EOF)
  93. {
  94. init();
  95. if(dfs(0))
  96. printf("Yes, I found it\n");
  97. else
  98. printf("It is impossible\n");
  99. }
  100. return 0;
  101. }

【算法】Dancing Links (DLX) I相关推荐

  1. Dancing Links DLX

    Dancing Links DLX Dancing Links 用来解精准覆盖问题. 精准覆盖问题有两种版本. 精准覆盖 : 给一个01矩阵,如何选出若干行,使得每列都有且仅有一个1. 可以求最少行数 ...

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

    精确覆盖问题的定义:给定一个由0-1组成的矩阵,是否能找到一个行的集合,使得集合中每一列都恰好包含一个1 例如:如下的矩阵 就包含了这样一个集合(第1.4.5行) 如何利用给定的矩阵求出相应的行的集合 ...

  3. 浅入 dancing links x(舞蹈链算法)

    abastract:利用dancing links 解决精确覆盖问题,例如数独,n皇后问题:以及重复覆盖问题. 要学习dacning links 算法,首先要先了解该算法适用的问题,精确覆盖问题和重复 ...

  4. 【转载】浅入 dancing links x(舞蹈链算法)

    转载自原文出处 浅入 dancing links x(舞蹈链算法) abastract:利用dancing links 解决精确覆盖问题,例如数独,n皇后问题:以及重复覆盖问题. 要学习dacning ...

  5. Dancing Links算法

    Dancing Links略述 Dancing Links算法主要用于解决精确覆盖问题,精确覆盖问题就的定义:给定一个由0-1组成的矩阵,是否能找到一个行的集合,使得每个集合中每一列恰好只包含一个1. ...

  6. 浅谈 Dancing Links X 算法

    博客园同步 前置知识: 一维链表.(单向,双向,循环) 部分集合运算,如 ⋂ \bigcap ⋂, ⋃ \bigcup ⋃. 前言 在计算机科学中,X算法可用来求解精确覆盖问题. 精确覆盖问题 是哪一 ...

  7. Dancing Links算法(舞蹈链)

    原文链接:跳跃的舞者,舞蹈链(Dancing Links)算法--求解精确覆盖问题 作者:万仓一黍 出处:http://grenet.cnblogs.com/ 本文版权归作者和博客园共有,欢迎转载,但 ...

  8. dancing links x(舞蹈链算法)详解

    dancing links x 详解 大佬万仓一黍的blog 夜深人静写算法(九)- Dancing Links X(跳舞链) 精确覆盖问题的定义:给定一个由0-1组成的矩阵,是否能找到一个行的集合, ...

  9. 算法帖——用舞蹈链算法(Dancing Links)求解俄罗斯方块覆盖问题

    问题的提出:如下图,用13块俄罗斯方块覆盖8*8的正方形.如何用计算机求解? 解决这类问题的方法不一而足,然而核心思想都是穷举法,不同的方法仅仅是对穷举法进行了优化 用13块不同形状的俄罗斯方块(每个 ...

最新文章

  1. 新公司研发能力低下,何去何从?
  2. 聊一聊Java 泛型通配符 T,E,K,V,?
  3. CV入门赛最全思路上分技巧汇总!
  4. Ubuntu 16.04下截图工具Shutter
  5. Spring Cloud 2020.0.4 发布!
  6. maven2中snapshot快照库和release发布库的应用
  7. 玩转HTML5+跨平台开发[4] HTML表格标签
  8. Java 高级—— IO 基础
  9. cups支持的打印机列表_lpadmin-配置CUPS套件中的打印机和类
  10. shell 列表_Shell文本编辑之转录因子(TF)列表的获取
  11. 计算机软考高级科目试题及答案,软考高级哪个含金量高 2018计算机软考信息系统项目管理师单选试题及答案...
  12. 骇客基础_骇客基础知识:第3部分
  13. Cesium开发:简单箭头画法
  14. 一体化3团队项目记录
  15. prometheus监控mysql慢查询_使用Grafana+Prometheus监控mysql服务性能
  16. python自动排版 html_python自动生成易于阅读的html文档——使用Sphinx
  17. 六年级下册第二单元计算机,六年级下册语文第二单元作文(精选10篇)
  18. 内核自带的基于GPIO的LED驱动学习(三)
  19. 外面的世界到底是什么
  20. 如何利用知识管理软件提高员工工作效率

热门文章

  1. 给飞行中的飞机换发动机
  2. 关于Adobe Premiere安装失败,错误代码为1的解决办法
  3. 宗海图绘制的关键问题
  4. 如何重新设置苹果id密码_路由器密码忘记了怎么重新设置 路由器密码忘了怎么办?详解路由器密码忘记解决办法...
  5. KGB SFX 脱壳
  6. 大学生职业生涯规划计算机科学与,计算机科学与技术专业大学生职业生涯规划书...
  7. openSUSE网络配置
  8. 计算机实验室项目建议书,传统演播实验室项目项目建议书电子版
  9. 牛客网SQL实战二刷 | Day2
  10. css3之制作旋转小风车