任何计算机相关专业的学生都学过很多排序算法,然而在算法竞赛中,我们会发现大部分排序算法都不怎么用得上(尤其是那堆
但在竞赛中,这样的复杂度有时是不够的。而我们要排序的数据,有时恰恰满足一些特殊性质。例如当待排序数据是不太大的自然数(均满足
// 计数排序
int Cnt[MAXM];
void counting_sort(unsigned A[], int len)
{
for (int i = 0; i < len; ++i)
Cnt[A[i]]++;
int pos = 0;
for (int i = 0; pos < len; ++i)
if (Cnt[i])
for (int j = 0; j < Cnt[i]; ++j)
A[pos++] = i;
}
对计数排序来说,当
基数排序也是针对自然数(其他数可以考虑转化为自然数)的排序,它的思想是,对数字的每一位分别进行计数排序。所谓的基数(radix),也就是“进制”的意思,所谓的“每一位”是该进制下的位,现在我们以10为基数作为例子:
第一趟计数排序结束后,所有数已按最后一位排好序。
第二趟结束后,所有数末两位已排序。
……
做gif太累了,就不把整个过程做完了,现在我们已经可以观察到基数排序是怎样运作的了:从低位到高位,依次进行计数排序。当然并非必须用计数排序,只要保证是稳定的排序即可,但用计数排序的时间复杂度最好。至于基数排序的正确性,可以归纳地证明。
以65536为基数的基数排序代码如下:
inline void radix_sort(unsigned A[], int len)
{
unsigned *B = new unsigned[len];
int r = 65535, L[r + 1] = {0}, H[r + 1] = {0};
for (int i = 0; i < len; ++i)
L[A[i] & r]++, H[(A[i] >> 16) & r]++; // 计数
for (int i = 1; i <= r; ++i)
L[i] += L[i - 1], H[i] += H[i - 1]; // 求前缀和
for (int i = len - 1; i >= 0; --i)
B[--L[A[i] & r]] = A[i]; // 对低位进行计数排序
for (int i = len - 1; i >= 0; --i)
A[--H[(B[i] >> 16) & r]] = B[i]; // 对高位进行计数排序
delete[] B;
}
这里解释一下为什么要求前缀和,例如数列3,1,4,3进行计数后得到的数组L应该是:
那么求前缀和后,L数组就变为:
这时每个(原来存在的)数i对应的L[i],就是排序后最后一个i的位置加上1。
一般而言,对于32位整数而言,选择65536作为基数是很合理的(快且好写,时间复杂度接近 )。但洛谷P4604 挑战这道(毒瘤)题为了利用硬件原理卡常数要把基数设为256,以256位基数的代码可以参见我学长的一个回答。
自己想了个新的排序算法,想写成论文,可以发哪些期刊呢?www.zhihu.com在他的回答中,还给出了用基数排序排浮点数的一个方法。但其实排序浮点数的需求是相对较少的,倒是时不时需要排序有符号数,这时我们只需要先把所有数加上0x80000000,排序结束后再减去即可。
Pecco:算法学习笔记(目录)zhuanlan.zhihu.com