基数排序(Radix sort)是一种非比较型整数排序算法,其原理是将整数按位数切割成不同的数字,然后按每个位数分别比较。由于整数也可以表达字符串(比如名字或日期)和特定格式的浮点数,所以基数排序也不是只能使用于整数。基数排序的发明可以追溯到1887年赫尔曼·何乐礼在打孔卡片制表机(Tabulation Machine)上的贡献。时间复杂度是 O(k·n),其中n是排序元素个数,k是数字位数。——来自
维基百科
空间复杂度为O(B),其中B为基数Base,如10或16等,不是一个稳定排序。
维基百科上有C++实现的代码,够简练,看了一段时间才看明白,在每次基于各个数位上的数字散列和合并的时候,用的方式够巧妙,是我没想出来的!特在此处贴出来,然后解释之~
bottomBox数组不会变化,因为它存储的是每个“桶”的初始元素,而topBox数组一直在变化,因为它始终指向“桶”中最近被添加的数字,这是由"基数排序中的每个"桶"必须在链表尾部添加新的数字"决定的,链表的尾部,就是topBox(可以把这个"桶"想象成一个开口向下的倒着的桶~)。起始状态,bottomBox与topBox里的每个元素都是一样的,因为每个"桶"里都没有数字,随着散列的进行,有些"桶"里就被先后放入多个数字,此时topBox随之变化,使自己总是指向"桶"最顶部的数字,也就是最新被添加的那个数字。
空间复杂度为O(B),其中B为基数Base,如10或16等,不是一个稳定排序。
维基百科上有C++实现的代码,够简练,看了一段时间才看明白,在每次基于各个数位上的数字散列和合并的时候,用的方式够巧妙,是我没想出来的!特在此处贴出来,然后解释之~
我仿着维基百科给的C++代码,实现如下:
#include <iostream>
#include <cstring>
#include <ctime>
using namespace std;
struct Num{
int v;
Num *next;
Num(){
next=0;
}
};
void sort(int *arr,int len){
const int RADIX=10;//以10为基进行排序
int tmp;
Num *topBox[RADIX];//桶中最新添加进去的元素
Num *bottomBox[RADIX];//桶底元素
for(int i=0;i<RADIX;++i){
topBox[i]=bottomBox[i]=new Num;
}
//找最大值
tmp=arr[0];
for(int i=0;i<len;++i){
if(tmp<arr[i])
tmp=arr[i];
}
//计算最大值有多少个数位
int digCnt=1;
tmp/=RADIX;
while(tmp){
++digCnt;
tmp/=RADIX;
}
//将数组转换成链表
Num *head=new Num;
Num *cur;
cur=head;
for(int i=0;i<len;++i){
cur->next=new Num;
cur->next->v=arr[i];
cur=cur->next;
}
//开始基数排序
int factor=1;
for(int i=0;i<digCnt;++i){
//散列
for(cur=head->next;cur;cur=cur->next){
tmp=(cur->v/factor)%RADIX;
topBox[tmp]->next=cur;
topBox[tmp]=topBox[tmp]->next;
}
//合并
cur=head;
for(int j=0;j<RADIX;++j){
if(topBox[j]!=bottomBox[j]){
cur->next=bottomBox[j]->next;
cur=topBox[j];
topBox[j]=bottomBox[j];
}
}
//必须将最后一个数字的next赋值为NULL,否则导致链表形成环,导致再次"散列"时会死循环
cur->next=0;
//扩大因子,用于取下一个数字
factor*=RADIX;
}
//使用链表给数组赋值
cur=head->next;
for(int i=0;i<len;++i,cur=cur->next){
arr[i]=cur->v;
}
}
void test(){
int len=20;
int *heap=new int[len];
srand(time(0));
for(int i=0;i<len;++i){
heap[i]=rand()%10000;
}
cout<<"初始数组:"<<endl;
printArray(heap,len);
//mySort(heap,0,len-1);
sort(heap,len);
printArray(heap,len);
delete heap;
}
bottomBox数组不会变化,因为它存储的是每个“桶”的初始元素,而topBox数组一直在变化,因为它始终指向“桶”中最近被添加的数字,这是由"基数排序中的每个"桶"必须在链表尾部添加新的数字"决定的,链表的尾部,就是topBox(可以把这个"桶"想象成一个开口向下的倒着的桶~)。起始状态,bottomBox与topBox里的每个元素都是一样的,因为每个"桶"里都没有数字,随着散列的进行,有些"桶"里就被先后放入多个数字,此时topBox随之变化,使自己总是指向"桶"最顶部的数字,也就是最新被添加的那个数字。
散列完成之后,需要将本次的散列结果按“在数组中散列到的桶号”串联起来,用于下次散列,串联的时候,每个“桶”中,开始的元素由bottomBox存储,终止元素由topBox存储,而且,如果该“桶”是空的,那么bottomBox对应的值与topBox对应的值相等,反过来说:凡是bottomBox中与topBox对应值不相等的那个“桶”里肯定有数字串,这个串的起始数字由bottomBox存储,终止数字由topBox存储。很巧妙吧!