今天,由于this slide的第13页,我与某人就Kruskal最小生成树算法进行了讨论。
演示文稿的作者说,如果我们使用(双重)链表实现不相交集,制作和查找的效果将是 O(1分别和 O(1)。操作时间 Union(u,v)为 min(nu,nv),其中 nu 和 nv 是存储u和v的集合的大小。
我说我们可以通过使每个成员的表示指针指向定位器来缩短 Union(u,v) O(1)的时间包含指向集合的实际表示的指针。
在Java中,数据结构如下所示:
class DisjointSet {
LinkedList<Vertex> list = new LinkedList<Vertex>(); // for holding the members, we might need it for print
static Member makeSet(Vertex v) {
Member m = new Member();
DisjointSet set = new DisjointSet();
m.set = set;
set.list.add(m);
m.vertex = v;
Locator loc = new Locator();
loc.representation = m;
m.locator = loc;
return m;
}
}
class Member {
DisjointSet set;
Locator locator;
Vertex vertex;
Member find() {
return locator.representation;
}
void union(Member u, Member v) { // assume nv is less than nu
u.set.list.append(v.set.list); // hypothetical method, append a list in O(1)
v.set = u.set;
v.locator.representation = u.locator.representation;
}
}
class Locator {
Member representation;
}
对于简约代码,我们深表歉意。如果可以这样做,那么每个不相交的设置操作( Make,Find,Union )的运行时间将是 O(1)。但我与之讨论过的那个人看不到改进。我想知道你对此的看法。
此外,各种实现中Find / Union的最快性能是什么?我不是数据结构方面的专家,但通过在互联网上快速浏览,我发现没有恒定时间的数据结构或算法来做到这一点。
答案 0 :(得分:3)
我的直觉与你的同事一致。你说:
u.set.list.append(v.set.list); // hypothetical method, append a list in O(1)
看起来你的意图是联盟是通过追加来完成的。但是,要实现Union,您必须删除重复项才能将结果作为集合。所以我可以看到一个固定集合大小的O(1)算法,例如......
Int32 set1;
Int32 set2;
Int32 unionSets1And2 = set1 | set2;
但这让我感到作弊。如果您对N的一般情况执行此操作,我不会看到您如何避免某种形式的迭代(或散列查找)。这将使其成为O(n)(或者最好是O(log n))。
仅供参考:我很难跟踪您的代码。在makeSet中,构造一个永远不会转义函数的本地定位器。它看起来不像什么。目前还不清楚你的意图是什么。可能想要编辑和详细说明你的方法。
答案 1 :(得分:3)
使用Tarjan's version of the Union-Find structure(带路径压缩和等级权重联合), m Finds和(n-1)混合联合的序列将在O(m.α(m,n)),其中α(m,n)是Ackermann function的倒数,对于 m 和 n 具有值4.因此,这基本上意味着Union-Find具有最坏情况下的摊销常数操作,但不完全。
据我所知,不可能获得更好的理论复杂性,尽管改进已经带来更好的实际效率。
对于语言理论中使用的不相交集的特殊情况,已经证明线性(即,O(1)中的所有内容)适应是可能的 - 主要是通过分组节点在一起---但这些改进无法转化为一般问题。另一方面,有一个类似的核心思想已被用于制作最小生成树(Chazelle算法)的O(n)算法,并取得了巨大的成功和独创性。
所以你的代码不正确。错误是Moron指出的:当你创建两个集合的联合时,你只更新每个列表的前导的“表示”,而不是更新所有其他元素 - 同时在find函数中假设每个元素直接知道它的代表性。