更新 2 正如@David Eisenstat 在下面的评论中指出的那样,在 O(E) 时间内进行排序的更简单的方法是使用 |E| 进行桶排序。桶。区间 [0, 1) 可分为 |E|长度为 1/|E| 的桶每个,编号为 0、1、2 ... |E|-1,区间内的任何权重 w 都属于编号为 k = floor(|E| w) 的桶。每个桶中的预期权重数量为 O(1),因此可以使用 |E| 进行排序每个插入排序的大小为 O(1),因此这给出了 O(E alpha(V)) 的预期时间 Kruskal 算法。
注意:作为@G。 Bach 指出,上述假设可以在 O(1) 时间内执行权重和 floor(|E|w) 浮点乘法的比较,这可能需要一定的怀疑。对于非常大的 |E| 值,这两个操作可能仍然有 O(lg E) 的贡献。
更新 正如 G. Bach 在下面指出的那样,第一轮 O(1) 位基数排序后的 bin 大小将始终是 Omega(E),所以下面的答案在技术上不是保证在 O(E) 时间内排序。但是,可以选择小于 O(lg E) 的位数,也许是 O(lg lg E) 或 O(sqrt lg E)?这样排序所需的时间少于 O(E lg E) 的预期时间。
原始答案
这是 CLRS 中的练习 23.2-6。我很确定 Kruskal 会更快(因此这里的其他答案是错误的)。权重的分布确实很重要;无关紧要的是图的密度/稀疏度。
普通版本主要是 O(E lg E) 时间从边缘权重排序。当边缘权重从均匀分布中提取时,我们可以对某个恒定位数执行基数排序,然后通过进一步的基数排序来修复连续子数组中的冲突。那是 O(E) 时间。
然后,剩下的就是普通的 Kruskal:使用具有联合秩和路径压缩的不相交集(如 CLRS 的第 23 章)剩下的工作是 O(E alpha(V)),其中 alpha(V) 是逆Ackermann 函数,并且对于任何正常的 V 值都
因此,对于基数排序,Kruskal 是线性 O(E),概率任意接近 1。
关于基数排序的注意事项:
可以通过使用更多位数使预期的碰撞次数(即前 15 位相同的边权重)任意小,但如果位数为 O(lg E),则为 O(1)。当然,这意味着 O(E lg E) 基数排序,这会破坏目的。但是,我们实际上并不需要完全避免碰撞,只需限制它们的大小,以便它们可以在线性时间内修复。
因此,我们可以考虑在一轮“基数排序”中对一些恒定位数(如 15)进行排序,这会将权重数组分成具有相同 15 位数字的连续子数组(也称为“bins”),然后在下一轮,使用第二“轮”基数排序对每个子数组的 16-30 位进行排序。
正式证明将涉及与生日悖论类似的计算,但由于冲突的概率随着使用的额外数字的数量呈指数下降,因此应该可以使用 O(1)“轮数”对上述内容进行完全排序,即将导致 O(E) 总排序时间。