示例代码不是传统的合并排序。合并函数会移动数组,而不是合并原始数组和临时工作数组之间的运行并返回。
我测试了自上而下和自下而上的合并排序,两者都需要大约 42 毫秒 == 0.042 秒来对 500,000 个 32 位整数进行排序,而图中的明显结果则慢了 1000 倍,大约需要 42 秒,而不是 42 毫秒。我还测试了 10,000,000 个整数,排序需要 1 秒多一点的时间。
过去,我使用 C++ 将自下而上合并排序与混合自下而上合并/插入排序进行比较,对于 1600 万 (2^24 == 16,777,216) 32 位整数,使用 S 的混合排序大约快 8% == 16. S == 64 比 S == 16 稍慢。 Visual Studio std::stable_sort 是自下而上合并排序(临时数组是原始数组大小的 1/2)和插入排序的变体,并使用 S == 32。
对于小型数组,插入排序比合并排序更快,这是缓存局部性和使用插入排序对小型数组进行排序所需的更少指令的结合。对于伪随机数据和 S == 16 到 64,插入排序的速度大约是合并排序的两倍。
相对增益随着阵列尺寸的增加而减小。考虑到自底向上归并排序的影响,当S == 16时,只优化了4次归并排序。在我的测试用例中,有 2^24 == 16,777,216 个元素,即 4/24 = 1/6 ~= 16.7% 的传递次数,导致大约 8% 的改进(因此插入排序的速度大约是合并的两倍对这 4 个通道进行排序)。仅合并排序的总时间约为 1.52 秒,混合排序的总时间约为 1.40 秒,比仅需要 1.52 秒的过程增加了 0.12 秒。对于自上而下的合并排序,当 S == 16 时,将优化 4 个最深的递归级别。
更新 - 时间复杂度为 O(n log(n)) 的混合就地合并排序/插入排序的 Java 代码示例。 (注意 - 由于递归,辅助存储仍然在堆栈上消耗。)就地部分是在合并步骤期间通过交换合并到的区域中的数据与合并自的区域中的数据来完成的。这不是稳定的排序(由于合并步骤期间的交换,不保留相等元素的顺序)。对 500,000 个整数进行排序大约需要 1/8 秒,因此我将其增加到 1600 万 (2^24 == 16777216) 个整数,这需要 4 秒多一点的时间。不使用插入排序时,排序耗时约 4.524 秒,使用 S == 64 的插入排序时,排序耗时约 4.150 秒,增益约 8.8%。使用基本相同的 C 代码,改进较小:从 2.88 秒缩短到 2.75 秒,大约提高了 4.5%。
package msortih;
import java.util.Random;
public class msortih {
static final int S = 64; // use insertion sort if size <= S
static void swap(int[] a, int i, int j) {
int tmp = a[i]; a[i] = a[j]; a[j] = tmp;
}
// a[w:] = merged a[i:m]+a[j:n]
// a[i:] = reordered a[w:]
static void wmerge(int[] a, int i, int m, int j, int n, int w) {
while (i < m && j < n)
swap(a, w++, a[i] < a[j] ? i++ : j++);
while (i < m)
swap(a, w++, i++);
while (j < n)
swap(a, w++, j++);
}
// a[w:] = sorted a[b:e]
// a[b:e] = reordered a[w:]
static void wsort(int[] a, int b, int e, int w) {
int m;
if (e - b > 1) {
m = b + (e - b) / 2;
imsort(a, b, m);
imsort(a, m, e);
wmerge(a, b, m, m, e, w);
}
else
while (b < e)
swap(a, b++, w++);
}
// inplace merge sort a[b:e]
static void imsort(int[] a, int b, int e) {
int m, n, w, x;
int t;
// if <= S elements, use insertion sort
if (e - b <= S){
for(n = b+1; n < e; n++){
t = a[n];
m = n-1;
while(m >= b && a[m] > t){
a[m+1] = a[m];
m--;}
a[m+1] = t;}
return;
}
if (e - b > 1) {
// split a[b:e]
m = b + (e - b) / 2;
w = b + e - m;
// wsort -> a[w:e] = sorted a[b:m]
// a[b:m] = reordered a[w:e]
wsort(a, b, m, w);
while (w - b > 2) {
// split a[b:w], w = new mid point
n = w;
w = b + (n - b + 1) / 2;
x = b + n - w;
// wsort -> a[b:x] = sorted a[w:n]
// a[w:n] = reordered a[b:x]
wsort(a, w, n, b);
// wmerge -> a[w:e] = merged a[b:x]+a[n:e]
// a[b:x] = reordered a[w:n]
wmerge(a, b, x, n, e, w);
}
// insert a[b:w] into a[b:e] using left shift
for (n = w; n > b; --n) {
t = a[n-1];
for (m = n; m < e && a[m] < t; ++m)
a[m-1] = a[m];
a[m-1] = t;
}
}
}
public static void main(String[] args) {
int[] a = new int[16*1024*1024];
Random r = new Random(0);
for(int i = 0; i < a.length; i++)
a[i] = r.nextInt();
long bgn, end;
bgn = System.currentTimeMillis();
imsort(a, 0, a.length);
end = System.currentTimeMillis();
for(int i = 1; i < a.length; i++){
if(a[i-1] > a[i]){
System.out.println("failed");
break;
}
}
System.out.println("milliseconds " + (end-bgn));
}
}