答案
MSD基数排序的迭代次数取决于输入大小,而LSD基数排序的迭代次数取决于关键字长度。这通常导致MSD基数排序需要比LSD基数排序少得多的迭代次数,因此更快。
内存分配不是问题,因为MSD基数排序可以轻松地原地实现。
原理
我已经为LSD和MSD基数排序都做了一个实现,以便我能看到它们具有什么特性,使得MSD基数排序比LSD基数排序更快。
我将它们的速度与100,000,000个随机正63位整数的数组上的std::sort进行了比较(我使用了std::sort的结果,也用于验证排序后的数组),并得到了以下结果:
- 纯LSD排序:10.5秒
- std::sort:9.5秒
- 纯MSD排序:9.3秒
- MSD排序+插入排序:7.6秒
所以,它比std::sort稍微快一点,如果叶子节点使用插入排序进行排序,它就会快得多。
为什么MSD基数排序可能比LSD基数排序更快?
- 有缓存局部性,但我怀疑这是否真的很重要,因为LSD基数排序也会扫描整个数组,而不是执行随机访问。
- MSD基数排序可以被实现成空间复杂度为O(d k)的算法,因此只取决于基数和项的长度。这可以在堆栈上分配,几乎是免费的。因此,它基本上是一种原地排序算法。
- 底层可以被修剪。即当一个桶仅包含1个元素时,它已经排序,因此不需要对该桶进行递归。因此,MSD基数排序只需要执行大约log(n)/log(d)次迭代。而LSD基数排序总是必须执行k次迭代。
我认为这最后一点是MSD基数排序通常比LSD基数排序更快的原因。如果输入数据均匀随机分布,则期望运行时间为O(n log(n)/log(d)),而LSD基数排序的运行时间为O(n k)。通常n远小于k^d。只有当n=o(k^d)时,LSD基数排序才会更快。但是,在这种情况下,也可以使用计数排序(k=1的基数排序)。
实现
inline void insertion_sort(int64_t * array, int n) {
for (int i=1; i<n; i++) {
int64_t val = array[i];
int j = i;
while (j>0 && array[j-1] > val) {
array[j] = array[j-1];
j--;
}
array[j] = val;
}
}
void msd_sort(int64_t * array, int n, int64_t bit=60) {
const int64_t mask = INT64_C(7);
int count[9]={};
for (int i=0; i<n; i++) {
count[((array[i]>>bit) & mask)+1]++;
}
int loc[8];
int64_t unsorted[8];
int live = 0;
for (int i=0; i<8; i++) {
loc[i] = count[i];
count[i+1]+=count[i];
unsorted[live] = array[loc[i]];
if (loc[i] < count[i+1]) {
live++;
}
}
live--;
for (int i=0; i<n; i++) {
int64_t val = unsorted[live];
int64_t d = (val>>bit) & mask;
array[loc[d]] = val;
loc[d]++;
unsorted[live] = array[loc[d]];
if (loc[d] == count[d+1]) {
live--;
}
}
if (bit>0) {
for (int i=0; i<8; i++) {
n = count[i+1] - count[i];
if (n > 20) {
msd_sort(array + count[i], n, bit-3);
} else {
insertion_sort(array + count[i], n);
}
}
}
}
void lsd_sort(int64_t * array, int n) {
const int64_t mask = INT64_C(7);
std::vector<int64_t> buffer(n);
for (int64_t bit=0; bit<63; bit+=3) {
int count[9]={};
for (int i=0; i<n; i++) {
buffer[i] = array[i];
count[((array[i]>>bit) & mask) + 1]++;
}
for (int i=0; i<8; i++) {
count[i+1]+=count[i];
}
for (int i=0; i<n; i++) {
int64_t val = buffer[i];
int64_t d = (val>>bit) & mask;
array[count[d]] = val;
count[d]++;
}
}
}