论文http://sqybi.com/works/dlxcn/;
其实论文已经翻译的很清楚了,这里列出了里面要重点理解的一些地方。最后加了一个自己实现的代码。需要说明的是这个算法所能解决的问题是一类有精确覆盖模型的问题,只要能转化成这种模型,就可用这个算法比较高效的解决。
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) |
精确覆盖问题。阐明Dancing Links威力的一种方法就是考虑一个能大致描述如下的一般问题:给定一个由0和1组成的矩阵,是否能找到一个行的集合,使得集合中每一列都恰好包含一个1?例如,下面这个矩阵
0 0 1 0 1 1 0
1 0 0 1 0 0 1
0 1 1 0 1 1 0
1 0 0 1 0 0 0
0 1 0 0 0 0 1
0 0 0 1 1 0 1 (3)
就包含了这样一个集合(第1,4,5行)
解决精确覆盖问题。对于接下来的非确定性算法,由于我们没有想到更好的名字,我们将称之为X算法,它能够找到由特定的01矩阵A定义的精确覆盖问题的所有解。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算法的好方法就是将矩阵A中的每个1用一个有5个域L[x]、R[x]、U[x]、D[x]、C[x]的数据对象(data object)x来表示。矩阵的每行都是一个经由域L和R(“左”和“右”)双向连接的环状链表;矩阵的每列是一个经由域U和D(“上”和“下”)双向连接的环状链表。每个列链表还包含一个特殊的数据对象,称作它的表头(list header)。
这些表头是一个称作列对象(column object)的大型对象的一部分。每个列对象y包含一个普通数据对象的5个域L[y]、R[y]、U[y]、D[y]和C[y],外加两个域S[y](大小)和N[y](名字);这里“大小”是一个列中1的个数,而“名字”则是用来标识输出答案的符号。每个数据对象的C域指向相应列头的列对象。
表头的L和R连接着所有需要被覆盖的列。这个环状链表也包含一个特殊的列对象称作“根”,h,它相当于所有活动表头的主人。而且它不需要U[h]、D[h]、C[h]、S[h]和N[h]这几个域。
我们寻找所有精确覆盖的不确定性算法现在可以定型为下面这个明析、确定的形式,即一个递归过程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的覆盖(见下)并且返回。
为了选择一个列对象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。
注意到还原操作正好与覆盖操作执行的顺序相反,我们利用操作(2)来取消操作(1)。(其实没必要严格限制“后执行的先取消”,由于j可以以任何顺序穿过第i行;但是从下往上取消对行的移除操作是非常重要的,因为我们是从上往下把这些行移除的。相似的,对于第r行从右往左取消列的移除操作也是十分重要的,因为我们是从左往右覆盖的。)
我实现代码,完全按里面的步骤写的,各位大神指教: