排序基础之非比较的计数排序、桶排序、基数排序(Java实现)
转载请注明原文地址: http://www.cnblogs.com/ygj0930/p/6639353.html
比较和非比较排序
快速排序、归并排序、堆排序、冒泡排序等比较排序,每个数都必须和其他数进行比较,才能确定自己的位置。
冒泡排序之类的排序,问题规模为n,又因为需要比较n次,所以平均时间复杂度为O(n²)。
归并排序、快速排序之类的排序,问题规模通过分治法削减为logN次,所以时间复杂度平均O(nlogn)。
比较排序适用于各种规模的数据,也不在乎数据的分布,都能进行排序,适用于一切需要排序的情况。
计数排序、基数排序、桶排序则属于非比较排序。非比较排序是通过求取每个元素前面应该有多少个元素来确定当前元素位置。针对数组arr,计算arr[i]之前有多少个元素,则唯一确定了arr[i]在排序后数组中的位置。
非比较排序只要确定每个元素之前的已有的元素个数即可,所以一次遍历即可解决。算法时间复杂度O(n)。
非比较排序时间复杂度底,但由于非比较排序需要占用空间来确定唯一位置。所以对数据规模和数据分布有一定的要求。
一:计数排序
作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数,并且必须是集中的数据;因为它借助一个辅助数组来排序,而辅助数组的大小是根据待排序数组的最大值来决定的。
算法思想如下:
1:首先,遍历待排序数组A[],得到A数组最大值Max,那么A数组的值必定处于0~Max之间;
2:创建辅助C[],大小为Max+1,即C数组下标范围为 0~Max,对应了待排数组的值的区间;
3:计数:统计A[]数组中每个元素出现了多少次。元素值对应C数组下标,次数对应C[A[i]]取值。
4:得出排序后A中元素值在结果数组中位置:C[]中存储了下标值对应的数据在A[]中出现的次数,那么C[i-1]+C[i]就等于A中值小于等于i的元素有多少个,也就是值为i的元素在排序后数组中的第几位。例如:A中小于等于5的数有3个,那么5就是结果数组中的第3位(因为等于啊!什么叫等于?小于等于自己的有n个,那么自己当然是等于自己的那个啊,也就是说,自己就是这n个当中的最后一个啊),由于数组下标从0开始,那么对应结果下标就是2.
代码实现如下:
public class CountingSort { public int[] countingSort(int[] A, int n) { if(A==null||A.length<=0){ return A; } //找出待排序数组最大值 int Max=A[0]; for(int i=1;i<n;++i){ Max=(Max>A[i])?Max:A[i]; } //由待排数据最大值,创建辅助数组 int[] count=new int[Max+1]; //计数 for(int i=0;i<n;++i){ count[A[i]]++; } //计算出值i在结果数组中的位置(第k位) for(int i=1;i<Max+1;++i){ count[i]=count[i-1]+count[i]; } int[] res=new int[n]; int val; int pos; for(int i=0;i<n;++i){ val=A[i]; //count[val]为val值在结果数组中的第count[val]位,由于数组下标从0开始,那么对应的下标就是count[val]-1 pos=count[val]-1; //把val填到结果数组中自己的位置上 res[pos]=val; //值为val的数已经排好了一个,那么小于等于val的值就要减少一个了 count[val]=count[val]-1; } return res; } }
二:桶排序
桶排序可用于最大最小值相差较大的数据情况,但数据的分布必须均匀,否则可能导致数据都集中到一个桶中。
算法思想如下:
1:遍历待排数组A,求出最大值Max和最小值Min,得出待排序数据区间Min~Max;
2:一个桶最大为A.length(极端情况下,数据十分集中,都进同一个桶),所以Min~Max的数据需要(Max—Min)/A.length+1(+1是为了上取整)个桶来存放;
3:遍历数组A,把A[i]放入桶中:桶是一个数据区间,区间大小为A.length,所以求A[i]属于第几个桶,只需(A[i]-min)/A.length即可。
4:A中数据全部入桶后,对每个桶(每个数据区间)进行小范围的排序
5:每个桶(区间)排好序后,从第一个桶到最后一个桶的数据排列顺序实际上就是A排好序后的结果。
代码实现如下:
public class RadixSort { public int[] radixSort(int[] A, int n) { if(A==null||A.length<=0){ return A; } //求最大值最小值 int Max=Integer.MIN_VALUE; int Min=Integer.MAX_VALUE; for(int i:A){ if(i<Min){ Min=i; } if(i>Max){ Max=i; } } //得到区间 int range=Max-Min; //得到桶总数 int total=range/n+1; //创建桶数组,由于每个桶是动态添加数据的,所以使用ArrayList ArrayList<ArrayList<Integer>> buckets=new ArrayList<ArrayList<Integer>>(total); for(int i=0;i<total;++i){ buckets.add(new ArrayList<Integer>()); } //遍历待排数组,计算出A[i]所处区间,然后入桶 int pos; for(int i=0;i<n;++i){ pos=A[i]/range; buckets.get(pos).add(A[i]); } //对每个桶内数据进行小范围排序 for(ArrayList arr:buckets){ Collections.sort(arr); } //从排序后桶数组中依次取出数据,即为排序后结果 int[] res=new int[n]; int index=0; for(ArrayList arr:buckets){ for(Object i:arr){ res[index]=(int)i; index++; } } return res; } }
三:基数排序
基数排序已经不再是一种常规的排序方式,它更多地像一种排序方法的应用。
基数排序的总体思路就是将待排序数据拆分成多个关键字进行排序:第1个排序关键字,第2个排序关键字,第3个排序关键字......然后,从低位到高位依据关键字对待排序数据进行排序。在前一位排序结果的基础上,依据当前位上数字进行排序......直到依据最高位进行排序,即可得到正确的拍完序结果。这里要注意,依据每一位上数字,对待排数组进行排序时,使用的是另一种排序方法,一般使用计数排序。
public class RadixSort { public int[] radixSort(int[] A, int n) { if(A==null||A.length<=0){ return A; } //找出待排数组最大值,得到待排数据最高位 Integer Max=Integer.MIN_VALUE; for(int i:A){ if(i>Max){ Max=i; } }
int maxindex=(Max.toString().length()); //创建一个临时数组,存放从低位到高位排序期间的临时结果 int[] temp=new int[n]; //计数排序所用的计数数组 int[] buckets=new int[10]; int rate=1;//从低位开始,倍数为1.后面为10,100...用于计算排序位是哪位 //从低位到高位,根据排序位值排序 for(int index=1;index<=maxindex;++index) { //把上一次排序位排序后的A数组,赋值到临时数组中 System.arraycopy(A,0,temp,0,n); //重置计数排序数组 Arrays.fill(buckets, 0); //遍历临时数组temp for(int j=0;j<n;++j){ //计算出元素的当前排序位对应的值 int subkey=(temp[j]/rate)%10; //计数 buckets[subkey]++; } //计数排序:计算排序位值小于等于l的有多少个,则buckets[l]为排序位值为l的待排数据在当前排序位排序结果中的位置(位置=下标+1) for(int l=1;l<10;++l){ buckets[l]=buckets[l-1]+buckets[l]; } //进行排序位排序,遍历上一次排序结果 for(int m=n-1;m>=0;--m){ //计算当前元素的排序位值 int subkey=(temp[m]/rate)%10; //由排序位值得到该元素在当前次排序结果中的下标 int pos=buckets[subkey]-1; //根据下标,把当前元素填到合适位置 A[pos]=temp[m]; //因为取了一个出来,所以计数数组对应值减一 buckets[subkey]--; } //每排完一位后,往更高一位进行排序 rate*=10; } return A; } }
本次做题偶得:
1:数组越界问题,用IDE或者java命令运行程序,看异常提示,找到出错的行,然后在前面添加Sys.out.println()语句打印相关变量值,看是哪个数据造成的越界,为何越界。对症修改。
2:求一个数的长度,无需用位运算、循环除以10等,只需借助String对象的length()即可:
Integer number;
int maxindex=(number.toString().length());