Here's an implementation 只遍历列表一次,收集运行,然后以与 mergesort 相同的方式安排合并。
复杂度为 O(n log m),其中 n 是项目数,m 是运行次数。最好的情况是 O(n)(如果数据已经排序),最坏的情况是 O(n log n)。
它需要 O(log m) 临时内存;排序是在列表中就地完成的。
(在下面更新。评论者提出了一个很好的观点,我应该在这里描述它)
算法的要点是:
while list not empty
accumulate a run from the start of the list
merge the run with a stack of merges that simulate mergesort's recursion
merge all remaining items on the stack
累积跑数不需要太多解释,但趁机累积上升跑和下降跑(反向)是很好的。在这里,它预先添加小于运行头部的项目,并附加大于或等于运行结束的项目。 (请注意,前置应使用严格小于以保持排序稳定性。)
将合并代码粘贴到这里是最简单的:
int i = 0;
for ( ; i < stack.size(); ++i) {
if (!stack[i])
break;
run = merge(run, stack[i], comp);
stack[i] = nullptr;
}
if (i < stack.size()) {
stack[i] = run;
} else {
stack.push_back(run);
}
考虑对列表进行排序 (d a g i b e c f j h)(忽略运行)。堆栈状态如下:
[ ]
[ (d) ]
[ () (a d) ]
[ (g), (a d) ]
[ () () (a d g i) ]
[ (b) () (a d g i) ]
[ () (b e) (a d g i) ]
[ (c) (b e) (a d g i ) ]
[ () () () (a b c d e f g i) ]
[ (j) () () (a b c d e f g i) ]
[ () (h j) () (a b c d e f g i) ]
然后,最后,合并所有这些列表。
请注意,stack[i] 处的项目(运行)数为零或 2^i,并且堆栈大小以 1+log2(nruns) 为界。每个堆栈级别的每个元素合并一次,因此 O(n log m) 比较。这里与 Timsort 有一个相似之处,尽管 Timsort 使用类似于 Fibonacci 序列的东西来维护它的堆栈,其中它使用了 2 的幂。
累积运行利用任何已排序的数据,因此对于已排序的列表(一次运行),最佳情况复杂度为 O(n)。由于我们同时累积升序和降序运行,因此运行的长度总是至少为 2。(这会将最大堆栈深度至少减少 1,首先要支付查找运行的成本。)最坏情况的复杂性是O(n log n),正如预期的那样,对于高度随机化的数据。
(嗯...第二次更新。)
或者只是在bottom-up mergesort 上查看维基百科。