1.基数排序基本介绍
基数排序是一种非比较型整数排序算法,其原理是将整数按位数切割成不同的数字,然后按每个位数分别比较。由于整数也可以表达字符串(比如名字或日期)和特定格式的浮点数,所以基数排序也不是只能使用于整数。
2.算法思想
原理是将整数按位数切割成不同的数字,然后按每个位数分别比较。基数排序的方式可以采用LSD(Least significant digital)或MSD(Most significant digital),LSD的排序方式由键值的最右边开始,而MSD则相反,由键值的最左边开始。
- MSD:先从高位开始进行排序,在每个关键字上,可采用计数排序
- LSD:先从低位开始进行排序,在每个关键字上,可采用桶排序
① 将所有待比较数值(正整数)统一为同样的数位长度,数位较短的数前面补零。
② 从最低位开始,依次进行一次排序。
③ 这样从最低位排序一直到最高位排序完成以后, 数列就变成一个有序序列。
3.动画演示
4. 性能分析
时间复杂度:O(k*N)
空间复杂度:O(k + N)
稳定性:稳定
设待排序的数组R[1..n],数组中最大的数是d位数,基数为r(如基数为10,即10进制,最大有10种可能,即最多需要10个桶来映射数组元素)。
处理一位数,需要将数组元素映射到r个桶中,映射完成后还需要收集,相当于遍历数组一遍,最多元素数为n,则时间复杂度为O(n+r)。所以,总的时间复杂度为O(d*(n+r))。
基数排序过程中,用到一个计数器数组,长度为r,还用到一个rn的二位数组来做为桶,所以空间复杂度为O(rn)。
基数排序基于分别排序,分别收集,所以是稳定的。
5.代码实现
对自然数的排序
#include <algorithm>
#include <stack>
#include <tuple>
#include <vector>
using std::copy; // from <algorithm>
using std::make_tuple;
using std::stack;
using std::tie;
using std::tuple;
using std::vector;
typedef unsigned int u32;
typedef unsigned int* u32ptr;
void MSD_radix_sort(u32ptr first, u32ptr last) {
const size_t maxW = 0x100000000llu;
const u32 maxlogW = 32; // = log_2 W
const u32 W = 256; // 计数排序的值域
const u32 logW = 8;
const u32 mask = W - 1; // 用位运算替代取模,详见下面的 key 函数
u32ptr tmp =
(u32ptr)calloc(last - first, sizeof(u32)); // 计数排序用的输出空间
typedef tuple<u32ptr, u32ptr, u32> node;
stack<node, vector<node>> s;
s.push(make_tuple(first, last, maxlogW - logW));
while (!s.empty()) {
u32ptr begin, end;
size_t shift, length;
tie(begin, end, shift) = s.top();
length = end - begin;
s.pop();
if (begin + 1 >= end) continue; // elements <= 1
// 计数排序
u32 cnt[W] = {};
auto key = [](const u32 x, const u32 shift) { return (x >> shift) & mask; };
for (u32ptr it = begin; it != end; ++it) ++cnt[key(*it, shift)];
for (u32 value = 1; value < W; ++value) cnt[value] += cnt[value - 1];
// 求完前缀和后,计算相同关键字的元素范围
if (shift >= logW) {
s.push(make_tuple(begin, begin + cnt[0], shift - logW));
for (u32 value = 1; value < W; ++value)
s.push(make_tuple(begin + cnt[value - 1], begin + cnt[value],
shift - logW));
}
u32ptr it = end;
do {
--it;
--cnt[key(*it, shift)];
tmp[cnt[key(*it, shift)]] = *it;
} while (it != begin);
copy(tmp, tmp + length, begin);
}
}
对字符串的排序
#include <algorithm>
#include <stack>
#include <tuple>
#include <vector>
using std::copy; // from <algorithm>
using std::make_tuple;
using std::stack;
using std::tie;
using std::tuple;
using std::vector;
typedef char* NTBS; // 空终止字节字符串
typedef NTBS* NTBSptr;
void MSD_radix_sort(NTBSptr first, NTBSptr last) {
const size_t W = 128;
const size_t logW = 7;
const size_t mask = W - 1;
NTBSptr tmp = (NTBSptr)calloc(last - first, sizeof(NTBS));
typedef tuple<NTBSptr, NTBSptr, size_t> node;
stack<node, vector<node>> s;
s.push(make_tuple(first, last, 0));
while (!s.empty()) {
NTBSptr begin, end;
size_t index, length;
tie(begin, end, index) = s.top();
length = end - begin;
s.pop();
if (begin + 1 >= end) continue; // elements <= 1
// 计数排序
size_t cnt[W] = {};
auto key = [](const NTBS str, const size_t index) { return str[index]; };
for (NTBSptr it = begin; it != end; ++it) ++cnt[key(*it, index)];
for (char ch = 1; value < W; ++value) cnt[ch] += cnt[ch - 1];
// 求完前缀和后,计算相同关键字的元素范围
// 对于 NTBS,如果此刻末尾的字符是 \0 则说明这两个字符串相等,不必继续迭代
for (char ch = 1; ch < W; ++ch)
s.push(make_tuple(begin + cnt[ch - 1], begin + cnt[ch], index + 1));
NTBSptr it = end;
do {
--it;
--cnt[key(*it, index)];
tmp[cnt[key(*it, index)]] = *it;
} while (it != begin);
copy(tmp, tmp + length, begin);
}
free(tmp);
}
LSD 基数排序实现的对 k - 关键字元素的排序
const int N = 100010;
const int W = 100010;
const int K = 100;
int n, w[K], k, cnt[W];
struct Element {
int key[K];
bool operator<(const Element& y) const {
// 两个元素的比较流程
for (int i = 1; i <= k; ++i) {
if (key[i] == y.key[i]) continue;
return key[i] < y.key[i];
}
return false;
}
} a[N], b[N];
void counting_sort(int p) {
memset(cnt, 0, sizeof(cnt));
for (int i = 1; i <= n; ++i) ++cnt[a[i].key[p]];
for (int i = 1; i <= w[p]; ++i) cnt[i] += cnt[i - 1];
// 为保证排序的稳定性,此处循环i应从n到1
// 即当两元素关键字的值相同时,原先排在后面的元素在排序后仍应排在后面
for (int i = n; i >= 1; --i) b[cnt[a[i].key[p]]--] = a[i];
memcpy(a, b, sizeof(a));
}
void radix_sort() {
for (int i = k; i >= 1; --i) {
// 借助计数排序完成对关键字的排序
counting_sort(i);
}
}