最高有效位基数排序如何比最低有效位基数排序更有效?
Posted
技术标签:
【中文标题】最高有效位基数排序如何比最低有效位基数排序更有效?【英文标题】:how is the most significant bit radix sort more efficient than the least significant bit radix sort? 【发布时间】:2015-05-07 04:59:42 【问题描述】:我只是在阅读以下问题: Radix sort most significant first or least significant, which is faster?
接受答案的作者建议 MSD 基数排序确实更快。但是我不明白为什么。
我已经实现了 LSD 和 MSD(基于二进制的移位操作),LSD 是迭代的,只需要一个桶数组,而 MSD 是递归的,每次递归调用都需要一个单独的桶数组。
如果你创建一个包含 1000 万个整数的随机数组,我看不出 MSD 会比 LSD 快多少,因为你每次重新进入函数时都会分配额外的桶数组,而且你将不得不面对递归调用自己。
我可以看到 MSD 和 LSD 的组合如何提供全面提升(对前几位运行 MSD,对其余位运行 LSD 以减少缓存未命中),但如何单独使用 MSD考虑到 LSD 的递归性质以及每次递归调用都需要一个新的桶数组这一事实,它比 LSD 更高效,这与 LSD 不同,它既是迭代的,而且整个排序过程只需要一个桶数组?
【问题讨论】:
您可以重构代码以避免这些不必要的分配和释放。请注意,哪个更快是高度依赖于实现的;我可以同时实现它们,以便 MSD 排序更快或 LSD 排序更快。 以及如何避免重新分配,因为您需要在每个递归调用中使用存储桶数组来查找稍后将递归到的组?使用全局存储桶数组或通过引用下一个递归调用来发送副本,在这两种情况下都会改变你的存储桶数组,因此无法在之前的递归调用中找到下一组。 每个级别只需要一个桶数组。那是对数很多;没有理由为每次通话分配和释放一个。 这是真的............现在我为每个递归调用使用一个桶。这将需要 nxbucketSize 空间,这与 bucketSizexlogn 空间相比非常糟糕...... 其实我也想过这个,我还是一头雾水。您将如何在每个级别使用一个存储桶阵列?这有什么参考吗?当您从一个级别进入下一个级别时,您将递归到一堆组。在每个组中,您将检查下一个 j 位,每个这样的 j 位序列都可以指向存储桶数组中的任何位置。那么,如何确保不同的组不会相互冲突呢?我正在使用@lcgldr,***.com/questions/28726933/… 提供的以下实现 【参考方案1】:回答
MSD 基数中的迭代次数取决于输入大小,而 LSD 基数排序中的迭代次数取决于密钥长度。这通常导致 MSD 基数排序比 LSD 基数排序需要更少的迭代,因此速度更快。
内存分配不是问题,因为 MSD 基数排序很容易就地实现。
基本原理
我已经实现了 LSD 和 MSD 基数排序,因此我可以看到它们具有哪些属性使 MSD 基数排序比 LSD 基数排序更快。
我已经将它们的速度与 std::sort 在 100.000.000 个随机正 63 位整数数组上进行了比较(我使用了 std::sort 的结果,我也用于验证已排序的数组)并得到以下结果:
纯 LSD 排序:10.5 秒 std::sort : 9.5s 纯 MSD 排序:9.3 秒 MSD 排序 + 插入排序:7.6 秒所以,它比std::sort稍微快一点,如果叶子是用insertion_sort排序的,它会快很多。
为什么 MSD 基数排序比 LSD 基数排序更快?
存在缓存局部性,但我怀疑这是否真的很重要,因为 LSD 基数排序也会扫描数组,而不是执行随机访问。 MSD 基数排序可以实现为使其空间复杂度为 O(d k),因此仅取决于基数 d 和项目 k 的长度。这可以在堆栈上分配,几乎是免费的。因此,它基本上是一种就地排序算法。 可以修剪底层。 IE。当一个桶只包含 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);
// 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]++;
【讨论】:
【参考方案2】:您所指的问题是仅对单个位执行排序。因此,每次递归仅将数组分成两组。因此,递归时实际上不需要存储太多内容。
您下降的较小组 - 较大的组,您放入堆栈以供稍后执行。在最坏的情况下,堆栈中最多有w
元素,其中w
是数据类型的宽度(以位为单位)。
现在,已经证明递归并不是那么糟糕在这种情况下,argumentation of Niklas B. 为什么 MSD 有机会运行得更快(即缓存位置)。
【讨论】:
以上是关于最高有效位基数排序如何比最低有效位基数排序更有效?的主要内容,如果未能解决你的问题,请参考以下文章