gitbook-way-to-algorithm
  • Introduction
  • Preface 前言
  • Content 目录
  • MathSymbolTable 数学符号表
  • Chapter-1 BasicKnowledge 第1章 基础知识
    • TimeComplexity 时间复杂度
    • Recursion 递归式
  • Chapter-2 Sort 第2章 排序
    • InsertSort 插入排序
    • BubbleSort 冒泡排序
    • QuickSort 快速排序
    • MergeSort 归并排序
  • Chapter-3 Search 第3章 搜索
    • BinarySearch 二分查找法(折半查找法)
    • AdditionMultiplicationPrinciple 加法乘法原理
    • BruteForce 暴力枚举
    • Recursion 递归
    • BreadthFirstSearch 广度优先搜索
    • BidirectionalBreadthSearch 双向广度搜索
    • AStarSearch A*搜索
    • DancingLink 舞蹈链
  • Chapter-4 DataStructure 第4章 数据结构
    • DisjointSet 并查集
    • PrefixTree(TrieTree) 前缀树
    • LeftistTree(LeftistHeap) 左偏树(左偏堆)
    • SegmentTree 线段树
    • FenwickTree(BinaryIndexedTree) 树状数组
    • BinarySearchTree 二叉查找树
    • AVLTree AVL平衡树
    • RedBlackTree 红黑树
    • SkipList 跳跃表
    • BPlusTree B+树
    • BMinusTree B-树
  • Chapter-5 DynamicProgramming 第5章 动态规划
    • Section-1 LinearDP 第1节 线性动规
      • LongestCommonSubsequence 最长公共子序列
      • LongestIncreasingSubsequence 最长递增子序列
      • BidirectionalSubsequence 双向子序列
      • MaximumContinuousSubsequenceSum 最大连续子序列和
      • LongestPalindromicSubsequence 最长回文子序列
    • Section-2 BagDP 第2节 背包问题
      • 01-Bag 01背包
      • CompleteBag 完全背包
      • TwoDimensionBag 二维背包
    • Section-3 RegionalDP 第3节 区域动规
      • MinimumMergeCost - 最小合并代价
      • UniquePath 唯一路径
      • TrianglePath 三角形路径
    • Section-4 TreeDP 第4节 树形动规
      • MaximumBinaryTree 最大二叉树
      • MaxMultipleTree 最大多叉树
      • MaximumBinaryTreeRadiusSum 最大二叉树和
  • Chapter-6 GraphTheory 第6章 图论
    • Section-1 Traverse 第1节 遍历
      • DepthFirstSearch 深度优先搜索
      • BreadthFirstSearch 广度优先搜索
      • TopologicalSort 拓扑排序
      • EulerCycle 欧拉回路
    • Section-2 MinSpanningTree 第2节 最小生成树
      • Kruskal Kruskal算法
      • Prim Prim算法
    • Section-3 ShortestPath 第3节 最短路径
      • BellmanFord BellmanFord算法
      • Dijkstra Dijkstra算法
      • FloydWarshall FloydWarshall算法
      • DifferentConstraints 差分约束
    • Section-4 StronglyConnectedComponents 第4节 强连通分支
      • Kosaraju Kosaraju算法
      • Tarjan Tarjan算法
      • 2-SAT 2-SAT问题
    • Section-5 NetworkFlow 第5节 网络流
      • EdmondsKarp EdmondsKarp算法(最短路径增广算法)
      • PushRelabel 压入与重标记算法
      • Dinic - Dinic算法
      • MinimumCostFlow - 最小费用流
      • MultipleSourceMultipleSinkMaxflow - 多源点多汇点最大流
    • Section-6 BinaryMatch 第6节 二分匹配
      • Hungarian 匈牙利算法
      • HopcroftKarp Hopcroft-Karp算法
      • MatchToMaxflow 二分匹配转化为最大流
      • KuhnMunkres Kuhn-Munkres算法
      • Introduction-Domination,Independent,Covering,Clique 介绍支配集、独立集、覆盖集和团
      • WeightedCoveringAndIndependentSet 最小点权覆盖和最大点权独立集
      • MinimumDisjointPathCovering 最小不相交路径覆盖
      • MinimumJointPathCovering 最小可相交路径覆盖
      • Coloring 染色问题
  • Chapter-7 CombinatorialMathematics 第7章 组合数学
    • FullPermutation 全排列
    • Combination 组合
    • Permutation 排列
    • PermutationGroup 置换群
  • Chapter-8 NumberTheory 第8章 数论
    • PrimeSieve 素数筛法
    • GreatestCommonDivisor 最大公约数
    • Euclid 欧几里得算法
    • ExtendedEuclid 扩展欧几里得算法
    • ChineseRemainerTheorem 中国剩余定理
    • ModularExponentiation 模幂运算
  • Chapter-9 LinearAlgebra 第9章 线性代数
    • Section-1 Matrix 第1节 矩阵
      • Strassen Strassen算法
      • GaussElimination 高斯消元法
      • LUP LUP分解
      • InverseMatrix 矩阵求逆
    • Section-2 LinearProgramming 第2节 线性规划
      • Simplex 单纯形算法
      • Dinkelback Dinkelback算法
  • Chapter-10 AnalyticGeometry 第10章 解析几何
    • Section-1 Polygon 第1节 多边形
      • Cross 向量叉积
      • SegmentIntersection 线段相交
      • Sweeping 扫除算法
      • ConvexPolygonArea 凸多边形面积
      • ConvexPolygonGravityCenter 凸多边形重心
      • NearestNeighbor 最近点对
    • Section-2 ConvexHull 第2节 凸包
      • GrahamScan Graham扫描算法
      • QuickHull 快速凸包算法
      • RotatingCalipers 旋转卡壳
  • Chapter-11 PatternMatch 第11章 文本匹配
    • SimpleMatch 简单匹配
    • AhoCorasickAutomata AC自动机
    • KnuthMorrisPratt KMP匹配算法
    • RabinKarp RabinKarp算法
    • BoyerMoore BoyerMoore算法
  • Chapter-12 GameTheory 第12章 博弈论
    • BashGame 巴什博弈
    • WythoffGame 威佐夫博弈
    • NimGame 尼姆博弈
Powered by GitBook
On this page
  • 问题
  • 重复覆盖
  • 精确覆盖
  • 源码
  • 测试
  1. Chapter-3 Search 第3章 搜索

DancingLink 舞蹈链

PreviousAStarSearch A*搜索NextChapter-4 DataStructure 第4章 数据结构

Last updated 6 years ago

问题

集合s=[x1,x2,…,xm]s = [x_{1}, x_{2}, \dots , x_{m}]s=[x1​,x2​,…,xm​]拥有mmm个元素,集合t=[s1,s2,…,sn]t = [ s_{1},s_{2}, \dots ,s_{n} ]t=[s1​,s2​,…,sn​]拥有nnn个元素,其中任意sis_{i}si​是集合sss的一个子集,sis_{i}si​包含sss中元素的数量为nin_{i}ni​(1≤i≤n,0≤ni≤m1 \le i \le n, 0 \le n_{i} \le m1≤i≤n,0≤ni​≤m)。

集合ppp是ttt的一个子集,且ppp中的子集所包含的sss的元素可以覆盖集合sss。设集合qqq是ppp中所有元素所包含的sss中元素组成的集合。例如

s=[1,2,3,4,5,6,7,8,9]t=[s1,s2,s3,s4,s5]p=[s2,s4,s5]q=[2,3,4,5,7,8,9]\begin{matrix} s & = & [1, 2, 3, 4, 5, 6, 7, 8, 9] \\ t & = & [ s_1, s_2, s_3, s_4, s_5 ] \\ p & = & [ s_2, s_4, s_5 ] \\ q & = & [ 2, 3, 4, 5, 7, 8, 9 ] \end{matrix}stpq​====​[1,2,3,4,5,6,7,8,9][s1​,s2​,s3​,s4​,s5​][s2​,s4​,s5​][2,3,4,5,7,8,9]​

其中

s1=[1,2,3]s2=[2,3,4,5]s3=[6]s4=[7,8]s5=[8,9]\begin{matrix} s_1 & = & [1, 2, 3] \\ s_2 & = & [2, 3, 4, 5] \\ s_3 & = & [6] \\ s_4 & = & [7, 8] \\ s_5 & = & [8, 9] \\ \end{matrix}s1​s2​s3​s4​s5​​=====​[1,2,3][2,3,4,5][6][7,8][8,9]​

重复覆盖:对于∀x∈s\forall x \in s∀x∈s,存在至少一个∃si∈p\exists s_{i} \in p∃si​∈p使得x∈six \in s_{i}x∈si​。例如集合s=[0,1,2,3]s = [ 0,1,2,3 ]s=[0,1,2,3],子集s1=[0,1],s2=[1,2],s3=[1,3]s_{1} = [ 0,1 ], s_{2} = [ 1,2 ], s_{3} = [ 1,3 ]s1​=[0,1],s2​=[1,2],s3​=[1,3]组成p=[s1,s2,s3]p = [ s_{1},s_{2},s_{3} ]p=[s1​,s2​,s3​],称这样的ppp是sss的重复覆盖。显然ppp允许两个si⋂sj≠∅s_{i} \bigcap s_{j} \ne \varnothingsi​⋂sj​=∅。

精确覆盖:对于∀x∈s\forall x \in s∀x∈s,存在且只存在一个∃si∈p\exists s_{i} \in p∃si​∈p使得x∈six \in s_{i}x∈si​。例如集合s=[0,1,2,3]s = [ 0,1,2,3 ]s=[0,1,2,3],子集s1=[0,1],s2=[1,2],s3=[2,3]s_{1} = [ 0,1 ], s_{2} = [ 1,2 ], s_{3} = [ 2,3 ]s1​=[0,1],s2​=[1,2],s3​=[2,3]组成p=[s1,s2]p = [ s_{1},s_{2} ]p=[s1​,s2​],称这样的ppp是sss的精确覆盖。显然ppp必然满足∀si,sj∈p,si⋂sj=∅,i≠j\forall s_{i}, s_{j} \in p, s_{i} \bigcap s_{j} = \varnothing, i \ne j∀si​,sj​∈p,si​⋂sj​=∅,i=j。

求集合sss和子集集合t=[s1,s2,…,sn]t = [s_{1}, s_{2}, \dots, s_{n}]t=[s1​,s2​,…,sn​]的重复覆盖和精确覆盖。

重复覆盖

设初始时重复覆盖p=∅p = \varnothingp=∅,ppp的所有子集中的元素所组成的集合为q=∅q = \varnothingq=∅。对于∀x∈s\forall x \in s∀x∈s,若x∉qx \notin qx∈/q,则在ttt中寻找sis_{i}si​满足x∈six \in s_{i}x∈si​,将该子集加入重复覆盖中p=[si]p = [ s_{i} ]p=[si​],将所有∀y∈si\forall y \in s_{i}∀y∈si​都加入qqq。每个子集只能加入ppp中一次,不能重复加入。当然也可以用染色来标记所有子集t=[s1,s2,…,sn]t = [ s_{1}, s_{2}, \dots ,s_{n} ]t=[s1​,s2​,…,sn​]中已经加入ppp的那些元素,防止重复。

精确覆盖

重复上述过程只有两种结果:

下面演示舞蹈链算法:

源码

测试

遍历完成后ppp即为重复覆盖。求重复覆盖的时间复杂度为O(n×m)O(n \times m)O(n×m)。

设初始时精确覆盖p=∅p = \varnothingp=∅,ppp的所有子集中的元素所组成的集合为q=∅q = \varnothingq=∅。

对于∀x∈s\forall x \in s∀x∈s,每个包含xxx的子集都可能是ppp的一员,我们用递归的方式寻找所有可能。利用ppp中任意两子集的交集为空的特性,若x∈six \in s_{i}x∈si​且si∈ps_{i} \in psi​∈p,那么只要sj⋂si≠∅s_{j} \bigcap s_{i} \ne \varnothingsj​⋂si​=∅,那么必然sj∉ps_{j} \notin psj​∈/p。

初始时将ttt中的所有子集标记为白色(未被使用过)。遍历sss的所有元素∀x∈s\forall x \in s∀x∈s,进行以下步骤:

若x∉qx \notin qx∈/q,这时有kkk个候选子集[si,sj,… ][ s_{i}, s_{j}, \dots ][si​,sj​,…](非红色,即未被使用过),它们都包含xxx。遍历这kkk个候选子集,假定选择sis_{i}si​加入ppp,将所有∀y∈si\forall y \in s_{i}∀y∈si​加入qqq。然后将ttt中所有包含sis_{i}si​中元素的其他子集染红(标记为已被使用);

(1)(1)(1) 所有xxx都已经加入qqq,这时的ppp即为精确覆盖;

(2)(2)(2) 所有xxx还没都加入qqq,这时所有的子集t=[s1,s2,…,sn]t = [s_{1}, s_{2}, \dots ,s_{n}]t=[s1​,s2​,…,sn​]都已经被染红。说明上次在候选子集中做出的选择是错的。

我们需要记录每次选择时染红了哪些子集,以及当时遍历到的元素xxx。将sis_{i}si​这次选择回退,把当时染红的子集重新染回白色,然后选择下一个候选者sjs_{j}sj​,再重复上述过程,指导找到精确覆盖。该过程是递归的。

s=[1,2,3,4,5,6,7]t=[s1,s2,s3,s4,s5,s6]s1=[1,3,5,6]s2=[1,4,7]s3=[2,6,7]s4=[2,3,6]s5=[4,5,7]s6=[5]\begin{matrix} s & = & [1, 2, 3, 4, 5, 6, 7] \\ t & = & [ s_1, s_2, s_3, s_4, s_5, s_6 ] \\ s_{1} & = & [1, 3, 5, 6] \\ s_{2} & = & [1, 4, 7] \\ s_{3} & = & [2, 6, 7] \\ s_{4} & = & [2, 3, 6] \\ s_{5} & = & [4, 5, 7] \\ s_{6} & = & [5] \end{matrix}sts1​s2​s3​s4​s5​s6​​========​[1,2,3,4,5,6,7][s1​,s2​,s3​,s4​,s5​,s6​][1,3,5,6][1,4,7][2,6,7][2,3,6][4,5,7][5]​

其中n=6,m=7n = 6, m = 7n=6,m=7。

用行来表示子集,列来表示集合中的元素,可得nnn行mmm列矩阵:

舞蹈链算法用十字链表这种特别的数据结构将所有元素串联起来,坐标为[i,j][i, j][i,j]的节点下标是i×m+ji \times m + ji×m+j,表示j∈s,j∈sij \in s, j \in s_{i}j∈s,j∈si​。沿着十字链表的行可以遍历子集sis_{i}si​的所有元素,沿着十字链表的列可以遍历所有包含jjj的子集。

(1)(1)(1) 对于上图中的示例,元素x=1x = 1x=1的候选子集有s1,s2s_{1}, s_{2}s1​,s2​,假定选择s1s_{1}s1​,可得p=[s1],q=[1,3,5,6]p = [s_{1}], q = [1, 3, 5, 6]p=[s1​],q=[1,3,5,6],然后将所有包含qqq中元素的子集染红(s1,s2,s3,s4,s5,s6s_{1}, s_{2}, s_{3}, s_{4}, s_{5}, s_{6}s1​,s2​,s3​,s4​,s5​,s6​);

(2)(2)(2) 接着考虑元素x=2x = 2x=2,其候选子集有s3,s4s_{3}, s_{4}s3​,s4​,但它们都被染红了无法使用,这时不存在一个可用的子集,说明上一轮选择错误。不选择上一轮的s1s_{1}s1​,将s1,s2,s3,s4,s5,s6s_{1}, s_{2}, s_{3}, s_{4}, s_{5}, s_{6}s1​,s2​,s3​,s4​,s5​,s6​染回白色,假定选择s2s_{2}s2​,可得p=[s2],q=[1,4,7]p = [s_{2}], q = [1, 4, 7]p=[s2​],q=[1,4,7],染红s1,s2,s3,s5s_{1}, s_{2}, s_{3}, s_{5}s1​,s2​,s3​,s5​;

(3)(3)(3) 再次考虑元素x=2x = 2x=2,其候选子集有s4s_{4}s4​(s3s_{3}s3​已被染红),假定选择s3s_{3}s3​,可得p=[s2,s4],q=[1,2,3,4,6,7]p = [s_{2}, s_{4}], q = [1, 2, 3, 4, 6, 7]p=[s2​,s4​],q=[1,2,3,4,6,7],染红s1,s3,s4s_{1}, s_{3}, s_{4}s1​,s3​,s4​(s1,s3s_{1}, s_{3}s1​,s3​上一轮已被染红);

(4)(4)(4) 元素3,4∈q3, 4 \in q3,4∈q,因此可以直接跳过;

(5)(5)(5) 考虑元素x=5x = 5x=5,其候选子集有s6s_{6}s6​(s5s_{5}s5​已被染红),假定选择s6s_{6}s6​,可得p=[s2,s4,s6],q=[1,2,3,4,5,6,7]p = [s_{2}, s_{4}, s_{6}], q = [1, 2, 3, 4, 5, 6, 7]p=[s2​,s4​,s6​],q=[1,2,3,4,5,6,7],染红s5,s6s_{5}, s_{6}s5​,s6​(s5s_{5}s5​之前已被染红);

(6)(6)(6) 元素6,7∈q6, 7 \in q6,7∈q,可以直接跳过。至此s=qs = qs=q,所有元素都被覆盖到,并且p=[s2,s4,s6]p = [s_{2}, s_{4}, s_{6}]p=[s2​,s4​,s6​]中任意两子集的交集为空,算法结束。

其实十字链表并不需要“染红”这个操作来标记一个子集是否可以使用,而是用添加、删除来操作链表上的节点。例如元素x=1x = 1x=1选择子集s2=[1,4,7]s_{2} = [1, 4, 7]s2​=[1,4,7]时,将节点111从头节点一行中删除,将包含x=1x = 1x=1的子集[s1,s2][s_{1}, s_{2}][s1​,s2​](包括s2s_{2}s2​自己)的元素也从所在的列中删除。如图所示:

仔细观察可知,删除之后仍然能够判定s1,s2s_{1}, s_{2}s1​,s2​包含元素111([1,8,15][1, 8, 15][1,8,15]拥有列关系),且可知s1=[1,3,5,6],s2=[1,4,7]s_{1} = [1, 3, 5, 6], s_{2} = [1, 4, 7]s1​=[1,3,5,6],s2​=[1,4,7]([8,10,12,13][8, 10, 12, 13][8,10,12,13],[15,18,21][15, 18, 21][15,18,21]拥有行关系)。这些遗留的列表指针可以恢复错误选择。对于子集s2=[1,4,7]s_{2} = [1, 4, 7]s2​=[1,4,7]上的所有元素进行相同的链表操作,等价于将s1,s2,s3,s5s_{1}, s_{2}, s_{3}, s_{5}s1​,s2​,s3​,s5​染红:

若尝试所有子集组合后仍无法找出精确覆盖,则说明该条件下不存在精确覆盖。舞蹈链算法的时间复杂度与递归的时间复杂度一样是O(nm)O(n^m)O(nm)。

DancingLink.h
DancingLink.cpp
DancingLinkTest.cpp
DancingLink1.png
DancingLink2.png
DancingLink3.png
DancingLink4.png
DancingLink5.png
DancingLink6.png
DancingLink7.png
DancingLink8.png