Mergesort - 自上而下快于自上而下吗?

时间:2012-04-14 11:53:10

标签: javascript algorithm sorting language-agnostic mergesort

我一直在阅读Sedgewick&amp ;;的“算法,第四版”。 Wayne,以及我一直在实现JavaScript中讨论的算法。

我最近采用了书中提供的mergesort示例来比较自上而下和自下而上的方法......但我发现自下而上的运行速度更快(我认为)。在我的博客上查看我的分析。    - http://www.akawebdesign.com/2012/04/13/javascript-mergesort-top-down-vs-bottom-up/

我无法找到任何讨论说一个mergesort方法应该比另一个快。我的实施(或分析)是否存在缺陷?

注意:我的分析测量算法的迭代循环,而不是严格的数组比较/移动。也许这有缺陷或无关紧要?

编辑:我的分析实际上没有时间速度,所以我关于它运行“更快”的说法有点误导。我通过递归方法跟踪“迭代”(顶部 - 向下)和for循环(自下而上) - 自下而上似乎使用更少的迭代。

3 个答案:

答案 0 :(得分:13)

  

我无法找到任何讨论说一个mergesort方法应该比另一个方法更快。

自下而上和自上而下的合并排序以及其他变体在90年代得到了很好的研究。简而言之,如果您将成本测量为单个密钥的比较次数,则最佳成本相同(〜(n lg n)/ 2),自上而下的最差成本低于或等于最差成本自下而上的情况(但两者都是n n n)和自上而下的平均成本低于或等于自下而上的平均情况(但都是〜n lg n),其中“lg n”是二进制对数。差异源于线性项。当然,如果n = 2 ^ p,则两个变体实际上完全相同。这意味着,从比较的角度来看,自上而下总是好于自下而上。此外,已经证明自上而下合并排序的“半”分裂策略是最优的。研究论文来自Flajolet,Golin,Panny,Prodinger,Chen,Hwang和Sedgewick。

以下是我在Erlang中出版的纯功能程序的设计与分析(大学出版社,英国)中所提出的内容:

tms([X|T=[_|U]]) -> cutr([X],T,U);
tms(T)           -> T.

cutr(S,[Y|T],[_,_|U]) -> cutr([Y|S],T,U);
cutr(S,    T,      U) -> mrg(tms(S),tms(T)).

mrg(     [],    T)            -> T;
mrg(      S,   [])            -> S;
mrg(S=[X|_],[Y|T]) when X > Y -> [Y|mrg(S,T)];
mrg(  [X|S],    T)            -> [X|mrg(S,T)].

请注意,这是稳定排序。此外,在Erlang(和OCaml)中,如果要节省内存,则需要在模式中使用别名(ALIAS = ...)。这里的技巧是在不知道其长度的情况下找到列表的中间部分。这是由cutr / 3完成的,它处理两个指向输入列表的指针:一个递增一个而另一个递增两个,所以当第二个到达结尾时,第一个指向中间。 (我是从Olivier Danvy的一篇论文中学到的。)这样,你不需要跟踪长度,也不需要复制列表后半部分的单元格,所以你只需要(1/2) )n lg n额外空间,相对于n lg n。这不是众所周知的。

通常声称自下而上的变体更适合函数式语言或链表(Knuth,Panny,Prodinger),但我不认为这是真的。

由于缺乏关于合并类型的讨论,我对你感到困惑,所以我做了自己的研究,写了一篇关于它的大篇章。我目前正在准备一个新版本,其中有更多关于合并排序的材料。

顺便说一下,还有其他变种:队列合并排序和在线合并排序(我在书中讨论了后者)。

[编辑:由于成本的衡量标准是比较次数,因此选择数组与链表之间没有区别。当然,如果您使用链接列表实现自上而下的变体,您必须聪明,因为您不一定知道键的数量,但每次都需要遍历至少一半的键,并且重新分配,总共(1/2)n lg n个细胞(如果你聪明的话)。与链接列表的自下而上合并排序实际上需要更多额外的内存,n lg n + n个单元格。因此,即使使用链接列表,自上而下的变体也是最佳选择。就程序的长度而言,您的里程可能会有所不同,但在功能语言中,如果不需要稳定性,自上而下的合并排序可以比自下而上更短。有一些论文讨论了合并排序的实现问题,例如就地(你需要数组)或稳定性等。例如,对Mergesort程序的细致分析,作者:Katajainen和Larsson Traff (1997)。]

答案 1 :(得分:7)

我在2012年8月版this course的课程论坛上提出了同样的问题。 Kevin Wayne教授(普林斯顿大学)回答说,在许多情况下,递归比迭代更快,因为缓存提高了性能。

所以我当时得到的简短回答是,由于缓存原因,自顶向下合并排序将比自下而上合并排序更快。

请注意,该课程是用Java编程语言(而不是Javascript)教授的。

答案 2 :(得分:4)

如果更快,则意味着更少的“迭代”,然后是。如果你想知道执行时间可能。

原因是这些21,513次迭代中的一些迭代次数超过22,527次。

通过查看源代码,您的图表中的某些叶节点似乎不是单独排序,导致合并和排序更少,但需要更长的时间。