关于这方面知识,牛叉的论文(中文翻译稿):
http://sqybi.com/works/dlxcn/
首先介绍:
Donald Knuth'sAlgorithm X:http://en.wikipedia.org/wiki/Algorithm_X
伪算法:
- If the matrix A is empty, the problem is solved; terminate successfully.
- Otherwise choose a column c (deterministically).
- Choose a row r such that Ar, c = 1 (nondeterministically).
- Include row r in the partial solution.
- For each column j such that Ar, j = 1,
-
for each row
i such that
A
i, j = 1,
- delete row i from matrix A;
- delete column j from matrix A.
-
for each row
i such that
A
i, j = 1,
- Repeat this algorithm recursively on the reduced matrix A.i
1.指定当前目标行,指定的方法为:
(1)统计出各列中1的个数,选取第一个个数最少的列。从伪算法的标记中说明这一步应该是“确定的”,当然我觉得也可以随意采用其他的策略,保证算法正确性即可。
(2)从上面选取的列中,选取一个为1的元素所在行为目标行。下面的两部动作就是围绕这个目标行来进行的。
2.删除目标行中为1的元素所在的列。因为我们假设当前目标行就是我们需要的一个行,既然我们选中了这行,那么该行中为1元素所在的列相当于也选好了,后面不用考虑了。
3.删除上一步中被删除的每一个列的其中为1元素所在的行。因为这些行都跟我们的目标行是冲突的。
4.重复上述步骤知道程序结束,两种方式结束:
(1)当前数组为空了,那么现在找到的目标行的集合就是所要的结果。(回溯过程中会删除当前假设不合理的目标行)
(2)当前数组不为空,但是上述1、2、3步无法正常执行下去。没有合适的解。
而我理解Dancing Links则是上述算法的一种实现。使用了双向十字链表:
双向十字链表实现
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)
dancingLinks的精髓就体现在第二个公式,因为它很方便很高效的实现了链表的恢复图1:dancingLinks的链表结构示意图
伪算法:
我们寻找所有精确覆盖的不确定性算法现在可以定型为下面这个明析、确定的形式,即一个递归过程search(k),它一开始被调用时k=0:
如果 R[h]=h ,打印当前的解(见下)并且返回。
否则选择一个列对象c(见下)。
覆盖列c(见下)。
对于每个r←D[c],D[D[c]],……,当 r!=c,
设置 Ok<-r;
对于每个j←R[r],R[R[r]],……,当 j!=r,
覆盖列j(见下);
search(k+1);
设置 r←Ok 且 c←C[r];
对于每个j←L[r],L[L[r]],……,当 j!=r,
取消列j的覆盖(见下)。
取消列c的覆盖(见下)并且返回。
输出当前解的操作很简单:我们连续输出包含O0、O1、……、Ok-1的行,这里包含数据对象O的行可以通过输出N[C[O]]、N[C[R[O]]]、N[C[R[R[O]]]]……来输出。
为了选择一个列对象c,我们可以简单地设置c<-R[h];这是最左边没有覆盖的列。或者如果我们希望使分支因数达到最小,我们可以设置s<-无穷大,那么接下来:
对于每个j←R[h],R[R[h]],……,当 j!=h,
如果 S[j]<s 设置 c←j 且 s←S[h]。
那么c就是包含1的序数最小的列(如果不用这种方法减少分支的话,S域就没什么用了)。
覆盖列c的操作则更加有趣:把c从表头删除并且从其他列链表中去除c链表的所有行。
设置 L[R[c]]←L[c] 且 R[L[c]]←R[c]。
对于每个i←D[c],D[D[c]],……,当 i!=c,
对于每个j←R[i],R[R{i]],……,当 j!=i,
设置 U[D[j]]←U[j],D[U[j]]←D[j],
并且设置 S[C[j]]←S[C[j]]-1。
操作(1),就是我在本文一开始提到的,在这里他被用来除去水平、竖直方向上的数据对象。
最后,我们到达了整个算法的尖端,即还原给定的列c的操作。这里就是链表舞蹈的过程:
对于每个i←U[c],U[U[c]],……,当 j!=i,
对于每个j←L[i],L[L[i]],……,当 j!=i,
设置 S[C[j]]←S[C[j]]+1,
并且设置 U[D[j]]←j,D[U[j]]←j。
设置 L[R[c]]←c 且 R[L[c]]←c。