回答
MSD 基数中的迭代次数取决于输入大小,而 LSD 基数排序中的迭代次数取决于密钥长度。这通常导致 MSD 基数排序比 LSD 基数排序需要更少的迭代,因此速度更快。
内存分配不是问题,因为 MSD 基数排序可以很容易地就地实现。
基本原理
我已经为 LSD 和 MSD 基数排序做了一个实现,所以我可以看到它们具有哪些属性使 MSD 基数排序比 LSD 基数排序更快。
我已经将它们的速度与 std::sort 在 100.000.000 个随机正 63 位整数数组上进行了比较(我使用了 std::sort 的结果,我也用于验证已排序的数组)并得到以下结果:
- 纯LSD排序:10.5s
- 标准::排序:9.5s
- 纯 MSD 排序:9.3s
- MSD 排序 + 插入排序:7.6 秒
所以,它比 std::sort 稍微快一点,如果叶子是用 insert_sort 排序的,它会快很多。
为什么 MSD 基数排序可能比 LSD 基数排序快?
- 有缓存局部性,尽管我怀疑这是否真的很重要,因为 LSD 基数排序也会扫描数组,而不是执行随机访问。
- 可以实现 MSD 基数排序,使其空间复杂度为 O(dk),因此仅取决于基数d和项目k的长度。这可以在堆栈上分配,几乎是免费的。因此,它基本上是一种就地排序算法。
- 可以修剪底层。即当一个桶只包含1个元素时,它已经排序,因此不需要在那个桶上递归。因此,MSD 基数排序只需要执行大约 log(n)/log(d) 迭代。而 LSD 基数排序总是必须执行 k 次迭代。
我相信最后一点是 MSD 基数排序通常比 LSD 基数排序更快的原因。如果输入数据是均匀随机分布的,那么预期的运行时间是 O(n log(n)/log(d)),而 LSD 基数排序的运行时间是 O(nk)。通常 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);
// Count bucket sizes
int count[9]={};
for (int i=0; i<n; i++) {
count[((array[i]>>bit) & mask)+1]++;
}
// Create holes.
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--;
// Perform sort
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) { // If replaced by n > 1, insertion_sort is not needed.
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) {
// Copy and count
int count[9]={};
for (int i=0; i<n; i++) {
buffer[i] = array[i];
count[((array[i]>>bit) & mask) + 1]++;
}
// Init writer positions
for (int i=0; i<8; i++) {
count[i+1]+=count[i];
}
// Perform sort
for (int i=0; i<n; i++) {
int64_t val = buffer[i];
int64_t d = (val>>bit) & mask;
array[count[d]] = val;
count[d]++;
}
}
}