我一直在阅读Sedgewick&amp ;;的“算法,第四版”。 Wayne,以及我一直在实现JavaScript中讨论的算法。
我最近采用了书中提供的mergesort示例来比较自上而下和自下而上的方法......但我发现自下而上的运行速度更快(我认为)。在我的博客上查看我的分析。 - http://www.akawebdesign.com/2012/04/13/javascript-mergesort-top-down-vs-bottom-up/
我无法找到任何讨论说一个mergesort方法应该比另一个快。我的实施(或分析)是否存在缺陷?
注意:我的分析测量算法的迭代循环,而不是严格的数组比较/移动。也许这有缺陷或无关紧要?
编辑:我的分析实际上没有时间速度,所以我关于它运行“更快”的说法有点误导。我通过递归方法跟踪“迭代”(顶部 - 向下)和for循环(自下而上) - 自下而上似乎使用更少的迭代。
答案 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次。
通过查看源代码,您的图表中的某些叶节点似乎不是单独排序,导致合并和排序更少,但需要更长的时间。