基数排序又称桶排序,相比快排、冒泡一类的比较式排序比较特殊,它并不直接对元素进行比较,也不进行元素交换,我们要做的是将元素进行分类,所以称为分配式排序,基数排序的时间复杂度下限是O(nlogn),这一点是基数排序相比于基于比较排序算法的优势。
基数排序
计数排序
我们可以将它理解为计数排序基础上的一种排序方式,首先我们先了解一下计数排序。
对于一定范围内的整数,它的时间复杂度能够做到O(n+k),它的基本思想是对于给定输入序列中的每一个元素x,确定该序列中值小于x的元素的个数,一旦确定这个信息,就可以将x放置到输出序列的正确地位置上。
计数排序可以分为三步:确认最大元素,并根据最大元素确认辅助空间大小,为每个输入序列的元素在辅助空间内提供一个计数位;统计每个元素的个数以及小于该元素值的个数;最后将每个元素放置到正确地位置
算法实现:
public class SortUtil{
public static int[] countSort(int[] a){
int len=a.length;
if(len<1) return;
int min=a[0];
int max=a[0];
//遍历数组得到最大最小值
for(int i=0;i<len;i++){
if(a[i]<min) min=a[i];
if(a[i]>max) max=a[i];
}
//辅助数组的长度
int k=max-min+1;
int[] tmp=new int[k];
//统计每一位元素的个数
for(int i=0;i<len;i++){
tmp[a[i]-min]++;
}
//统计小于等于该位元素的个数
for(int i=1;i<k;i++){
tmp[i]=tmp[i]+tmp[i-1];
}
//将每一位元素放到正确地位置
int[] res=new int[len];
for(int i=len-1;i>=0;i--){
int value=a[i];
//找到该值应该放置的位置
int pos=tmp[value-min];
res[pos-1]=value;
tmp[value-min]--;
}
return res;
}
}
算法分析:
空间复杂度:我们可以看到该算法使用了一个长度为k的辅助数组,因此其空间复杂度为O(n+k);
时间复杂度:3个n一个k循环,同样是O(n=k)。
计数排序是一种稳定排序算法,在排序前后相同值的元素的顺序并不会发生变化。
基数排序
了解计数排序的思想是为了帮助我们更好地理解基数排序,在基数排序中,我们需要一个更大的辅助数组,它是二维的,大小则是[10*n];
算法过程
基数排序里有MSD(most significant digital)和LSD(most significant digital)两种方式,我们这里讨论LSD
- 初始化:构造一个10*n的二维数组,一个长度为n的数组来存储每次位排序时,每个桶子里有多少元素;
- 循环操作:从低位开始,将所有元素对应该位的数字存储到相应的桶子里去(对应二维数组中的那一列)。然后将所有桶子里的元素按照桶子标号从小到大依次取出,对于同一个桶子来说,就是先进先出,这也保证了基数排序的稳定性。这样该位的排序完成,原数组按照该位排序,然后再进入下一位直到最高位完成。
算法实现
public class SortUtil{
public static radixSort(int[] a,int d){
//指示位数
int n=1;
//保存每一位排序的结果用于下一位的输入
int k=0;
int len=a.length;
//桶数组用于存储中间结果,每一位排序相同的元素放在相同的桶中
int[][] bucket=new int[10][len];
//用于保存每个桶中有多少元素
int[] order=new int[len];
while(n<d){
//每个元素放到相应的桶中
for(int num:a){
int digit=(num/n)%10;
bucket[digit][order[digit]]=num;
order[digit]++;
}
//将上一个循环里生成的桶里的数据覆盖到原数组中以保存该位排序的结果
for(int i=0;i<len;i++){
//如果这个桶不为空,则遍历这个桶,并将数据保存到原数组中
if(order[i]!=0){
for(int j=0;j<order[i];j++){
a[k]=bucket[i][j];
k++;
}
}
//将桶里的计数器重置,用于下一次排序
order[i]=0;
}
n*=10;
k=0;
}
}
}
这一篇有一点东拼西凑,不过本意就是需要自己来记一遍才能加深印象,到处乱看最后啥都记不住。