Knuth舞蹈与次要专栏的链接

时间:2019-01-28 14:10:20

标签: algorithm doubly-linked-list knuth

我已实施Knuth's "Dancing Links" algorithm来研究广义的精确覆盖问题(即,带有辅助列)。该代码可按预期工作,以实现完全覆盖(即所有列均为主列),因此适用于简单的稀疏矩阵:

[0, 1, 0]
[0, 0, 1]
[1, 0, 0]
[1, 1, 0]
[0, 1, 1]
[1, 1, 1]

我的代码返回以下行集作为解决方案:

[0, 1, 2]
[1, 3]
[2, 4]
[5]

我还用许多其他确切的封面示例对此进行了测试,并进行了检验。但是,有关次要列的详细信息有点含糊。从各种Knuth / non-Knuth资源中收集的信息来看,您需要做的是:

  

唯一的区别是我们通过以下方式初始化数据结构:   仅主要列的列标题的循环列表。每个标头   第二列应具有指向其自身的L和R字段。其余的   算法的过程与以前完全相同,因此我们仍将其称为算法DLX。

对矩阵/节点/标题的表示方式进行了这些更改,然后将第一列设置为第二列(即,只有第2列和第3列是主要列)之后,我得到了以下几组行作为解决方案:

[0, 1]
[1, 3]
[4]
[5]

虽然所有这些都是有效的解决方案,并且有些与确切的掩护解决方案重叠,但似乎缺少其他解决方案(即,某些来自确切的掩护解决方案集合):

[0, 1, 2]
[2, 4]

也许这对我来说是一个误会,但我在概念上是否有所遗漏,还是Knuth的解释不完整?

如果您可以证明您的算法可以生成完整的解决方案,这甚至会有所帮助,这将帮助我确认我的算法不完整!

不幸的是,即使是关于舞蹈链接的Knuth's "Art of Computer Programming"似乎也没有提供太多帮助。

1 个答案:

答案 0 :(得分:1)

定义

这是将确切的覆盖问题扩展到Pre-Fascicle 5C(跳舞链接)第7页(当前)上的非主要项目的原因:

  

...确切的掩护问题涉及N个不同的项目,其中N1是主要项目,N2 = N-N1是次要项目。它由一系列选项定义,每个选项都是项目的子集。 每个选项必须至少包含一个主要项目。任务是找到选项的所有子集,其中(i)每个主要项目恰好包含一个,并且(ii)每个次要项目最多包含一次。

     

(纯辅助选项从该新定义中排除,因为在我们对其进行优化后,它们永远不会被算法X选择。如果由于某些原因您不喜欢该规则,则可以随时返回松弛选项的概念。练习19讨论了另一个有趣的选择。)

您的问题将由第一段和第二段中强调的句子(由Knuth回答)回答。 Knuth解决的确切封面问题不允许(或忽略)无法帮助覆盖主要项目的选项,即仅由次要项目组成。

示例

在您的问题示例中,我们将其称为A,B,C列,其中A为第二列,B和C为第一列。选项是:

[0, 1, 0] -- option 0: [B]
[0, 0, 1] -- option 1: [C]
[1, 0, 0] -- option 2: [A]
[1, 1, 0] -- option 3: [A, B]
[0, 1, 1] -- option 4: [B, C]
[1, 1, 1] -- option 5: [A, B, C]

因此,第三行[1 0 0],即选项2,不包含任何主要项目。

您可以在名为(例如)foo.dlx的文件中使用以下输入来运行Knuth的程序DANCE或DLX1:

B C | A
B
C
A
A B
B C
A B C

程序找到相同的四个解决方案:

$ ./dance 1 < foo.dlx
1:
 C (1 of 3)
 B (1 of 2)
2:
 C (1 of 3)
 B A (2 of 2)
3:
 C B (2 of 3)
4:
 C A B (3 of 3)
Altogether 4 solutions, after 12 updates.

% ./dlx1 m1 < foo.dlx
Option ignored (no primary items): A
(5 options, 2+1 items, 14 entries successfully read)
1:
 C (1 of 3)
 B (1 of 2)
2:
 C (1 of 3)
 B A (2 of 2)
3:
 C B (2 of 3)
4:
 C A B (3 of 3)
Altogether 4 solutions, 261+231 mems, 12 updates, 360 bytes, 6 nodes.

(请注意第二个程序中的明确警告,将忽略仅包含第二项A的选项2。)

解决方案1:更改问题

如果删除不包含主要项目(列)的选项(行),则该程序已经可以使用:对于新问题,您获得的解决方案确实是详尽无遗的。

解决方案2:松弛选项

正如Knuth在引用的第二段中所说(忽略“练习19”替代方案;它是为了解决一个不同的问题),如果您确实想包含仅包含次要项目的选项,则可以回到松弛选项的概念。在2000年的论文中,这个想法是您引用的段落之后的下一个句子:

  

如果我们为每个第二列简单地添加一行,而在该列中只包含一个1,则广义的覆盖问题可以转换为等效的精确覆盖问题。

(也就是说,对于每个次要项目,我们添加一个仅包含该项目的选项,现在将其视为仅主要项目的确切覆盖问题。)

更详细地说,我假设您想解决以下问题:

  • 有N个不同的项目(列),其中一些是主要的,其他是次要的。

  • 有一些选项(行),每个选项集都是一组项目(列)。 其中一些可能不包含主要项目。

  • 找到包含每个主要项目一次,每个次要项目最多一次的选项的所有子集。

要解决此问题,我们可以执行以下操作:

  • 在给定的选项(行)中,标识仅包含次要项目(列)的选项。因此,您将给定的所有行的集合划分为两组:一组(称为X),其中每个选项至少包含一个主项;一组(称为Y),其中每个选项仅包含次要项目。

  • 对于每个辅助项目,形成仅包含该项目的选项(行)。令Z为所有此类单项(松弛)选项的集合。

  • 现在,将选项列表(X + Y)替换为(X + Y + Z),其中+为并集:Y和Z之间可能存在一些重叠,但是您只会保留一个每个选项。

  • 最后,解决原始的精确覆盖问题(所有选项均为主要选项)。您将获得一些解决方案。

  • 对于每种获得的解决方案,

    • 首先丢弃(忽略)所有不在X或Y中的选项(即,是您另外添加的松弛选项之一)

    • 其余选项的
    • 在解决方案中形成仅包含次要项目的选项集Y'。令X'为剩余集合(即解决方案中的至少包含一个主要项目的选项)。

    • 为Y'的每个子集附加解决方案(X'union S)。

在您的示例问题中:X是以下集合:

[0, 1, 0] -- option 0: [B]
[0, 0, 1] -- option 1: [C]
[1, 1, 0] -- option 3: [A, B]
[0, 1, 1] -- option 4: [B, C]
[1, 1, 1] -- option 5: [A, B, C]

Y是以下集合:

[1, 0, 0] -- option 2: [A]

Z与Y相同,因此在这种情况下您无需添加任何内容。

您可以解决原始的确切掩盖问题(一切都是主要的),并获得以下解决方案:

  • [0,1,2]。这里X'= [0,1],而Y'= [2]并具有两个子集:空集([])和Y本身([2])。因此,添加两个解决方案[0,1]和[0,1,2]。

  • [1,3]。 X'= [1,3],Y'= []。添加解决方案[1,3]。

  • [2,4]。 X'= [4],Y'= [2]。添加两个解决方案[4]和[2,4]。

  • [5]。 X'= [5],Y'= []。添加解决方案[5]。

这提供了所有六个解决方案。