一,基本概念
算法简介
舞蹈链算法(Dancing Links,简称 DLX)是一种高效解决精确覆盖问题的算法,实际上是一种数据结构,可以用来实现 X算法,以解决精确覆盖问题。由高德纳(Donald E. Knuth)提出 。
精准覆盖
什么是精确覆盖(Exact Cover)问题呢?就是在一个全集X中若干子集的集合为S。S* 是 S的一个子集,当且仅当X中的每一个元素在S*中恰好出现一次时,S*称之为一个精确覆盖。在计算机科学中,精确覆盖问题指找出这样的一种覆盖,或证明其不存在。这是一个NP-完全问题。
舞蹈链
其实是一种特殊的数据结构,用于高效地实现对精确覆盖问题的求解。它基于双向循环链表,每个节点除了包含指向左右节点的指针外,还包含指向上方和下方节点的指针,这种结构使得在搜索过程中能够快速地对链表进行插入、删除和恢复操作。
数据结构设计
每个1的节点包含四个指针:left
, right
, up
, down
,形成双向十字链表。
每列有一个列头节点,记录该列中1的数量(用于优化搜索顺序)。
算法流程
-
选择列:优先选择当前剩余1最少的列(减少搜索分支)。
-
覆盖列:删除该列及其关联的所有行(避免后续搜索冲突)。
-
递归搜索:对剩余矩阵重复上述步骤。
-
回溯恢复:若当前路径无解,恢复被删除的列和行,尝试其他分支。
- 结束条件:当舞蹈链中的所有列都被覆盖(即矩阵中所有列都被删除)时,找到了一个精确覆盖解;如果遍历完所有可能的分支都没有找到解,则说明该问题无解。
二,示例
例如,S = {A,B,C,D,E,F} 是全集 X = {1,2,3,4,5,6,7} 的一个子集的集合,其中:
A = {1, 4, 7}
B = {1, 4}
C = {4, 5, 7}
D = {3, 5, 6}
E = {2, 3, 6, 7}
F = {2, 7}
那么,S的一个子集 S* = {B, D, F} 是X的一个精确覆盖,因为 X 中的每个元素恰好在S*中出现了一次。
可以用0-1矩阵来表示精确覆盖问题。我们用矩阵的每行表示S的一个元素,也就是X的一个子集;用矩阵的每列表示X的一个元素。矩阵中的1代表这一列的元素存在于这一行对应的子集中,0代表不存在。那么精确覆盖问题可以转化成求出矩阵若干行的集合,使得集合中的每一列恰好都有一个1。
比如前面的问题可以用矩阵的形式表示成
那么选择红色的B,D,F能满足每列都恰好包含一个1。
可以用 Knuth 提出的X算法来解决精确覆盖问题。X算法是一个非确定性的深度优先回溯算法。它的具体步骤如下:
1. 如果矩阵
为空(没有任何列),则当前局部解即为问题的一个解,返回成功;否则继续。
2. 根据一定方法选择第 c 列。如果某一列中没有 1,则返回失败,并去除当前局部解中最新加入的行。
选择第 r 行,使得
(该步是非确定性的)。
将第 r 行加入当前局部解中。
对于满足
的每一列j,从矩阵
中删除所有满足
的行,最后再删除第 j 列。
对所得比 A 小的新矩阵递归地执行此算法。
让我们用 X算法解决上面的精确覆盖问题。
首先,当前矩阵不为空,算法继续进行。那么先选择1最少的一列。因为 1,2,3,5,6 列都只有 2 个 1,因此我们随便选择 1 个,比如第 1 列。