Javascript 合并排序-自下而上比自上而下快吗?

Javascript 合并排序-自下而上比自上而下快吗?,javascript,algorithm,sorting,language-agnostic,mergesort,Javascript,Algorithm,Sorting,Language Agnostic,Mergesort,我一直在阅读Sedgewick&Wayne的“Algorithms,4th Ed”,并一直在实现JavaScript中讨论的算法 我最近拿了书中提供的mergesort示例来比较自上而下和自下而上的方法。。。但我发现自下而上的方法运行得更快(我认为)。在我的博客上看到我的分析。 - 我还没有找到任何讨论,说一种合并排序方法应该比另一种更快。我的实现(或分析)是否有缺陷 注意:我的分析测量的是算法的迭代循环,而不是严格的数组比较/移动。也许这是有缺陷的或不相关的 编辑:我的分析实际上没有计算速度,

我一直在阅读Sedgewick&Wayne的“Algorithms,4th Ed”,并一直在实现JavaScript中讨论的算法

我最近拿了书中提供的mergesort示例来比较自上而下和自下而上的方法。。。但我发现自下而上的方法运行得更快(我认为)。在我的博客上看到我的分析。 -

我还没有找到任何讨论,说一种合并排序方法应该比另一种更快。我的实现(或分析)是否有缺陷

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


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

如果你说的更快是指更少的“迭代”,那么是的。如果你想知道执行时间的话

原因是21513次迭代中的一些比22527次迭代做得更多

从源代码看,图中的一些叶节点似乎被排序在一起,而不是单独排序,从而导致更少的合并和排序,但它们需要更长的时间

我还没有找到任何讨论,说一种合并排序方法应该比另一种更快

自底向上和自顶向下的合并排序以及其他变体在90年代得到了很好的研究。简言之,如果将成本作为单个键的比较次数来衡量,则最佳成本是相同的(~(n lg n)/2),自顶向下的最坏成本低于或等于自下而上的最坏情况(但都是~n lg 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)中,如果要节省内存,需要在模式中使用别名(别名=…)。这里的技巧是在不知道列表长度的情况下找到列表的中间部分。这是由CUTR/3完成的,它处理输入列表的两个指针:一个增加一个,另一个增加两个,因此当第二个到达结束时,第一个在中间。(我从Olivier Danvy的一篇论文中学到了这一点。)这样,你就不需要跟踪长度,也不需要复制列表后半部分的单元格,因此你只需要(1/2)n lg n额外的空间,而不是n lg n。这并不为人所知

人们经常声称,对于函数式语言或链表(Knuth、Panny、Prodinger),自下而上的变体更可取,但我认为这不是真的

我和你一样对合并排序缺乏讨论感到困惑,所以我做了自己的研究,并写了一大章。我目前正在准备一个新版本,其中包含更多关于合并排序的资料

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


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

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

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


请注意,本课程是用Java编程语言(不是Javascript)讲授的.

比较和交换是排序分析中的关键成本项目,我非常确定。@是的,它们通常是比较不同排序算法时要分析的项目。但在这种情况下,它们应该是相同的……它们是相同的算法,所以这不是我想要的。我的实现反映了书中的内容。。。是否可能自底向上在数组上/通过数组使用较少的循环,但具有相同的比较/移动次数?@NiklasB。我明白你的观点……但这些并没有造成我迭代计数的差异。如果你看我的代码,我只跟踪递归/迭代循环中的迭代。Math.floor()与此无关-我没有使用基于时间的分析也许在我的原始帖子中“运行得更快”是不正确的。我发现在数组上自底向上循环的次数更少,但这可能是错误的