Linked-list representation of disjoint sets - omission in Intro to Algorithms text?
我的上一个 CLRS 问题取得了成功,这是另一个问题:
算法导论,第二版,p。 501-502,描述了不相交集的链表表示,其中每个列表成员维护以下三个字段:
-
集合成员
-
指向 next 对象的指针
-
返回第一个对象的指针(集合 representative)。
虽然链表可以通过仅使用单个"Link"对象类型来实现,但教科书显示了一个辅助"Linked List"对象,其中包含指向"head"链接的指针和"尾"链接。拥有指向"tail"的指针有助于 Union(x, y) 操作,因此无需遍历较大集合 x 中的所有链接即可开始将较小集合 y 的链接附加到它.
然而,为了获得对尾部链接的引用,似乎每个链接对象都需要维护第四个字段:对链接列表辅助对象本身的引用。在这种情况下,为什么不完全删除 Linked List 对象并使用第四个字段直接指向尾部?
您会认为这是文本中的遗漏吗?
1
| why not drop the Linked List object entirely and use that fourth field to point directly to the tail? |
可以从路径压缩中获得见解。那里的所有元素都应该指向列表的头部。如果它没有发生,那么 find-set 操作就会这样做(通过更改 p[x] 并返回它)。你同样谈到尾巴。所以只有实现了这样的功能,我们才能使用它。
我刚刚打开文本,教科书的描述对我来说似乎很好。
据我了解,数据结构类似于:
1 2 3 4 5 6 7 8 9 10
| struct Set {
LinkedListObject * head;
LinkedListObject * tail;
};
struct LinkedListObject {
Value set_member;
Set *representative;
LinkedListObject * next;
}; |
我的书(第二版)中没有提到任何"辅助"链表结构。能贴出相关段落吗?
做一个联合会是这样的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| // No error checks.
Set * Union(Set *x, Set *y) {
x->tail->next = y->head;
x->tail = y->tail;
LinkedListObject *tmp = y->head;
while (tmp) {
tmp->representative = x;
tmp = tmp->next;
}
return x;
} |
- 您称为 Set 的对象是我认为是辅助"链接列表"对象的对象(我对术语的选择很差)。但是,您的实现中的每个 LinkedListObject 都需要维护对其 Set 对象的引用,不是吗?考虑 p 上的算法 MST-Kruskal。 569,其中 Find-Set(u) 必须在 O(1) 时间内执行。 MST-Kruskal 算法需要 Set 对象的句柄来执行 Union。
-
@kostmo:那不是代表吗?我已经编辑了结构/代码以使其清楚。你真的不需要第四个字段。这本书讨论了一个 Set 指针是 LinkedList 头。我同意,拥有一个额外的 Set 对象似乎更干净,但是您不需要像您声称的那样需要第四个字段。
-
在图 21.2 (p. 502) 中,每个 Link 对象的 representative 字段都指向第一个 Link 对象,这意味着它们属于同一类型。
-
@kostmo:您总是可以维护单独的尾指针和头指针,在这种情况下,书的描述是正确的。将它们放在一个结构中只会使它们保持在一起并且看起来更干净。例如,您可以有两个数组,一个带有头部,另一个带有尾部等。我不会称之为遗漏。
-
每个 head 和 tail 指针都必须绑定到一个特定的集合,因此使用 struct 无疑是组织它们的一种合乎逻辑的方式。如果维护为两个单独的数组,则这些数组的索引仍然必须以某种方式与集合相关联(通过将其存储在哪里?)。我觉得麻烦的是这本书如何将 representative 不是作为 Set 对象,而是作为 Link 对象。在该方案中,没有指向 Set 对象的指针,因此可以取消引用 tail。我更喜欢你的实现,其中 representative 是 Set 本身。
-
@Kostmo:您可以让 Head[i] 和 Tail[i] 对应,映射值是 i 或某个 setId 或其他取决于用例。这本书已经在摘要中明确了结构是什么。你如何选择实现它,不是作者的问题 :-) 深入研究实现的细节只会增加噪音(至少在这种情况下),只会让读者感到困惑。我认为要点很好。想象一下,如果这本书在每一点都开始讨论不相关的细节,那本书的厚度会是原来的两倍!
-
就图 21.2 中的实现细节而言,我同意即使现在它也会给读者带来困惑;也就是说,描绘了一个具有三个字段的 Link,它们都没有提供访问 tail 指针的方法。无论 head 和 tail 是实现为 structs 还是独立数组,在所描述的方案中都没有映射值(可能是 i、setId 或 struct 指针)允许一个在给定 Link 的情况下,确定 tail(在 O(1) 时间内)。
-
@kostmo:图 21.1 不是"实现细节"。否则如何显示这种链接列表?此外,他们所说的只是"也要保持尾巴",而没有详细说明如何(以及为什么要这样做?)。混乱就在你的脑海里,IMO。您似乎不是在抽象地思考,而是在思考实现。 IMO,非常清楚。无论如何,这个讨论变得毫无意义,我想我们俩可能从不同的angular都是对的。另外,我想你可以自由地给他们写信:-)。
-
好吧,无论如何感谢您的共鸣板。我想我必须把困惑说出来。
-
@kostmo:没问题:-)。您正在尝试制定实施方案是一件好事。