基本解法
第一步
以LSD为例,假设原来有一串数值如下所示:
73,22,93,43,55,14,28,65,39,81
首先根据个位数的数值,在走访数值时将它们分配至编号0到9的桶子。
第二步
接下来将这些桶子中的数值重新串接起来,成为以下的数列:
81,22,73,93,43,14,55,65,28,39
接着再进行一次分配,这次是根据十位数来分配。
第三步
接下来将这些桶子中的数值重新串接起来,成为以下的数列:
14,22,28,39,43,55,65,73,81,93
这时候整个数列已经排序完毕;如果排序的对象有三位数以上,则持续进行以上的动作直至最高位数为止。LSD的基数排序适用于位数小的数列,如果位数多的话,使用MSD的效率会比较好。
MSD的方式与LSD相反,是由高位数为基底开始进行分配,但在分配之后并不马上合并回一个数组中,而是在每个“桶子”中建立“子桶”,将每个桶子中的数值按照下一数位的值分配到“子桶”中。在进行完最低位数的分配后再合并回单一的数组中。
效率分析
时间效率:设待排序列为n个记录,d个关键码,关键码的取值范围为radix,则进行链式基数排序的时间复杂度为O(d(n+radix)),其中,一趟分配时间复杂度为O(n),一趟收集时间复杂度为O(radix),共进行d趟分配和收集。空间效率:需要2*radix个指向队列的辅助空间,以及用于静态链表的n个指针。
实现方法
最高位优先(Most Significant Digit first)法,简称MSD法:先按k1排序分组,同一组中记录,关键码k1相等,再对各组按k2排序分成子组,之后,对后面的关键码继续这样的排序分组,直到按最次位关键码kd对各子组排序后。再将各组连接起来,便得到一个有序序列。
最低位优先(Least Significant Digit first)法,简称LSD法:先从kd开始排序,再对kd-1进行排序,依次重复,直到对k1排序后便得到一个有序序列。
实现原理
基数排序的发明可以追溯到1887年赫尔曼·何乐礼在打孔卡片制表机(Tabulation Machine)上的贡献。它是这样实现的:将所有待比较数值(正整数)统一为同样的数位长度,数位较短的数前面补零。
然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后,数列就变成一个有序序列。
基数排序的方式可以采用LSD(Least significant digital)或MSD(Most significant digital),LSD的排序方式由键值的最右边开始,而MSD则相反,由键值的最左边开始。
语言实现
C语言
#include
#include
testBS()
{
int a[]={2,343,342,1,123,43,4343,433,687,654,3};
int *a_p=a;
//计算数组长度
int size=sizeof(a)/sizeof(int);
//基数排序
bucketSort3(a_p,size);
//打印排序后结果
int i;
for(i=0;i
printf("%dn",a[i]);
}
int t;
scanf("%d",t);
}
//基数排序
void bucketSort3(int *p,int n)
{
//获取数组中的最大数
int maxNum=findMaxNum(p,n);
//获取最大数的位数,次数也是再分配的次数。
int loopTimes=getLoopTimes(maxNum);
int i;
//对每一位进行桶分配
for(i=1;i<=loopTimes;i++){
sort2(p,n,i);
}
}
//获取数字的位数
int getLoopTimes(int num)
{
int count=1;
int temp=num/10;
while(temp!=0){
count++;
temp=temp/10;
}
return count;
}
//查询数组中的最大数
int findMaxNum( int *p,int n)
{
int i;
int max=0;
for(i=0;i
if(*(p+i)>max){
max=*(p+i);
}
}
return max;
}
//将数字分配到各自的桶中,然后按照桶的顺序输出排序结果
void sort2(int *p,int n,int loop)
{
//建立一组桶此处的20是预设的根据实际数情况修改
int buckets={};
//求桶的index的除数
//如798个位桶index=(798/1)%10=8
//十位桶index=( 798/10 )%10=9
//百位桶index=( 798/100 )% 10=7
//tempNum为上式中的1、10、100
int tempNum=(int)pow(10,loop-1);
int i,j;
for( i=0;i
int row_index=(*(p+i)/tempNum)%10;
for(j=0;j<20;j++){
if(buckets[row_index][j]==NULL){
buckets[row_index][j]=*(p+i);
break;
}
}
}
//将桶中的数,倒回到原有数组中
int k=0;
for(i=0;i<10;i++){
for(j=0;j<20;j++){
if(buckets[i][j]!=NULL){
*(p+k)=buckets[i][j];
buckets[i][j]=NULL;
k++;
}
}
}
}
Java语言
public class RadixSort{
public static void sort(int[] number,int d){
int k=0;
int n=1;
int m=1;//控制键值排序依据在哪一位
int[][]temp=new int[number.length][number.length];
int[]order=new int[number.length];
while(m<=d){
for(int i=0;i
int lsd=((number[i]/n)%10);
temp[lsd][order[lsd]]=number[i];
order[lsd]++;
}
for(int i=0;i
if(order[i]!=0)
for(int j=0;j
number[k]=temp[i][j];
k++;
}
order[i]=0;
}
n*=10;
k=0;
m++;
}
}
public static void main(String[]args){
int[]data=
{73,22,93,43,55,14,28,65,39,81,33,100};
RadixSort.sort(data,10);
for(int i=0;i
System.out.print(data[i]+"");
}
}
pascal
type link=^node;
node=record
data:integer;
next:link;
end;
var i,j,l,m,k,n:integer;
a:array[1..100]of integer;
s:string;
q,head:array[0..9]of link;
p,p1:link;
begin
readln(n);
writeln('Enter data:');
for i:=1 to n do read(a[i]);
for i:=5 downto 1 do
begin
for j:=0to9 do
begin
new(head[j]);
head[j]^.next:=nil;
q[j]:=head[j]
end;
for j:=1to n do
begin
str(a[j],s);
for k:=1 to 5-length(s)do
s:='0'+s;
m:=ord(s[i])-48;
new(p);
p^.data:=a[j];
p^.next:=nil;
q[m]^.next:=p;
q[m]:=p;
end;
l:=0;
for j:=0to9 do
begin
p:=head[j];
while p^.next<>nil do
begin
l:=l+1;p1:=p;p:=p^.next;dispose(p1);a[l]:=p^.data;
end;
end;
end;
writeln('Sorted data:');
for i:=1to n do
write(a[i]:6);
end.
c++
int maxbit(int data[],int n)//辅助函数,求数据的最大位数
{
int d=1;//保存最大的位数
int p=10;
for(int i=0;i
{
while(data[i]>=p)
{
p*=10;
++d;
}
}
return d;
}
{
int d=maxbit(data,n);
int*tmp=new int[n];
int*count=new int;//计数器
int i,j,k;
int radix=1;
for(i=1;i<=d;i++)//进行d次排序
{
for(j=0;j<10;j++)
count[j]=0;//每次分配前清空计数器
for(j=0;j
{
k=(data[j]/radix)%10;//统计每个桶中的记录数
count[k]++;
}
for(j=1;j<10;j++)
count[j]=count[j-1]+count[j];//将tmp中的位置依次分配给每个桶
for(j=n-1;j>=0;j--)//将所有桶中记录依次收集到tmp中
{
k=(data[j]/radix)%10;
t
}
for(j=0;j
data[j]=tmp[j];
radix=radix*10;
}
}
C#实现基数排序
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace LearnSort
{
class Program
{
static void Main(string[] args)
{
int[]arr=CreateRandomArray(10);//产生随机数组
Print(arr);//输出数组
RadixSort(ref arr);//排序
Print(arr);//输出排序后的结果
Console.ReadKey();
}
public static void RadixSort(ref int[]arr)
{
int iMaxLength=GetMaxLength(arr);
RadixSort(ref arr,iMaxLength);
}
//排序
private static void RadixSort(ref int[] arr,int iMaxLength)
{
List list=new List();//存放每次排序后的元素
List[]listArr=new List;//十个桶
char currnetChar;//存放当前的字符,比如说某个元素123中的2
string currentItem;//存放当前的元素,比如说某个元素123
for(int i=0;i
listArr[i]=new List();
for(int i=0;i
{
foreach(int number in arr)//分桶
{
currentItem=number.ToString();//将当前元素转化成字符串
try{currnetChar=currentItem[currentItem.Length-i-1];}//从个位向高位开始分桶
catch{listArr.Add(number);continue;}//如果发生异常,则将该数压入listArr。比如说5 是没有十位数的,执行上面的操作肯定会发生越界异常的,这正是期望的行为,我们认为5的十位数是0,所以将它压入listArr的桶里。
switch(currnetChar)//通过currnetChar的值,确定它压人哪个桶中。
{
case'0':listArr.Add(number);break;
case'1':listArr.Add(number);break;
case'2':listArr.Add(number);break;
case'3':listArr.Add(number);break;
case'4':listArr.Add(number);break;
case'5':listArr.Add(number);break;
case'6':listArr.Add(number);break;
case'7':listArr.Add(number);break;
case'8':listArr.Add(number);break;
case'9':listArr.Add(number);break;
default:throw new Exception("unknow error");
}
}
for(int j=0;j
foreach(int number in listArr[j].ToArray())
{
list.Add(number);
listArr[j].Clear();//清空每个桶
}
arr=list.ToArray();//arr指向重新排列的元素
//Console.Write("{0}times:",i);
Print(arr);//输出一次排列的结果
list.Clear();//清空list
}
}
//得到最大元素的位数
private static int GetMaxLength(int[]arr)
{
int iMaxNumber=Int32.MinValue;
foreach(int i in arr)//遍历得到最大值
{
if(i>iMaxNumber)
iMaxNumber=i;
}
return iMaxNumber.ToString().Length;//这样获得最大元素的位数是不是有点投机取巧了...
}
//输出数组元素
public static void Print(int[]arr)
{
foreach(int i in arr)
System.Console.Write(i.ToString()+'t');
System.Console.WriteLine();
}
//产生随机数组。随机数的范围是0到1000。参数iLength指产生多少个随机数
public static int[]CreateRandomArray(int iLength)
{
int[] arr=new int[iLength];
Random random=new Random();
for (int i=0;i
arr[i]=random.Next(0,1001);
return arr;
}
}
}
AAuto
第一步
io.open();//打开控制台
/*
*-------------------------------------------------------
*基数排序
**------------------------------------------------------
*/
/*
第二步
基数排序从低位到高位进行,使得最后一次计数排序完成后,数组有序。
其原理在于对于待排序的数据,整体权重未知的情况下,
先按权重小的因子排序,然后按权重大的因子排序。
例如比较时间,先按日排序,再按月排序,最后按年排序,仅需排序三次。
但是如果先排序高位就没这么简单了。
基数排序源于老式穿孔机,排序器每次只能看到一个列,
很多教科书上的基数排序都是对数值排序,数值的大小是已知的,与老式穿孔机不同。
将数值按位拆分再排序,是无聊并自找麻烦的事。
算法的目的是找到最佳解决问题的方案,而不是把简单的事搞的更复杂。
基数排序更适合用于对时间、字符串等这些整体权值未知的数据进行排序。
这时候基数排序的思想才能体现出来,例如字符串,如果从高位(第一位)往后排就很麻烦。
而反过来,先对影响力较小,排序排重因子较小的低位(最后一位)进行排序就非常简单了。
这时候基数排序的思想就能体现出来。
又或者所有的数值都是以字符串形式存储,就象穿孔机一样,每次只能对一列进行排序。
这时候基数排序也适用,例如:对{"193";"229";"233";"215"}进行排序
下面我们使用基数排序对字符串进行排序。
对每个位循环调用计数排序。
*/
第三步
//计数排序算法
radix_sort=function(array,maxlen){
//AAuto在字符串索引越界时,会返回0,这使基数排序的实现更加简单。
//我们首先找出最大的排序长度,然后对于不足此长度的字符串,尾部都假定以0补齐。
//对于超出此长度的位在比较时忽略
if(!maxlen){
maxlen=0;
for(i=1;#array;1){
maxlen=math.max(maxlen,#array[i])
}
}
//else{
//最大排序长度也可以从参数中传过来,这样就不用遍历所有字符串了
//}
第四步
//从字符串的最后一位开始,到第一位
for(pos=maxlen;1;-1){
//按当前位的字节码计数排序
var array_sorted={};
var count={};
for(i=0;256){
count[i]=0;
}
var bytecode;
for(i=1;#array;1){
//如果pos大于字符串长度,AAuto会返回0,这使基数排序的实现更容易
bytecode=array[i][pos];
count[bytecode]++;//count[n]包含等于n的个数
}
第五步
//统计位置
for(i=1;256;1){
count[i]+=count[i-1];//count[i]包含小于等于i的个数
}
var n;
for(i=#array;1;-1){
n=array[i][pos]
array_sorted[count[n]]=array[i];
count[n]--;//防止相同的元素n再次出现,将计数减一
}
array=array_sorted;
}
return array
}
io.print("----------------")
io.print("基数排序(线性时间排序)")
io.print("----------------")
array={"AAuto is quicker and better,just try it!";"AAuto Quicker";"193";"229";"233";"215";"Hello
Word";"abc";"abcd";"xd";"adcd";"eddd";"ah";"ai";"aj";"ajkk"};
第六步
//排序
array=radix_sort(array)
第七步
//输出结果
for(i=1;#array;1){
io.print(array[i])
}
execute("pause")//按任意键继续
io.close();//关闭控制台