软件设计有两种方式:一种方式是,使软件过于简单,明显没有缺陷;另一种方式是,使软件过于复杂,没有明显的缺陷。
——C.A.R. Hoare
本文将讲述剩下的最后两种排序算法:基数排序和堆排序。
基数排序
核心思想:
基数排序属于“分配式排序”,又称“桶子法”(bucket sort),顾名思义,它是透过键值的部份资讯,将要排序的元素分配至某些“桶”中,借以达到排序的作用。
基数排序将所有待比较数值统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后, 数列就变成一个有序序列。
算法描述:
- 取得数组中的最大数,并取得位数;
- arr为原始数组,从最低位开始取每个位组成radix数组;
- 对radix进行计数排序(利用计数排序适用于小范围数的特点);
基数排序动画演示
Java实现基数排序
public static void jishuSort(int[] arr){
int[][] bucket = new int[10][arr.length];
int[] bucketElementCounts = new int[10];
long start = new Date().getTime();
int max = arr[0];
for (int i = 0; i < arr.length; i++) {
if (arr[i] > max) max = arr[i];
}
int time = 1;
int beishu = 10;
while (max/beishu >= 1 ){
time++;beishu*=10;
}
System.out.println("最大位数是:"+ time);
for (int i = 0; i < time; i++) {
// 先存
for (int j = 0; j < arr.length; j++) {
int index = (arr[j] / (int) Math.pow(10,i)) % (10);
bucket[index][bucketElementCounts[index]] = arr[j];
bucketElementCounts[index]++;
}
// 再取出来合并新的arr
int arrindex = 0;
for (int j = 0; j < 10; j++) {
if (bucketElementCounts[j] != 0){
for (int k = 0; k < bucketElementCounts[j]; k++) {
arr[arrindex] = bucket[j][k];
arrindex++;
}
}
bucketElementCounts[j] = 0;
}
System.out.println("第一次基数排序:" + Arrays.toString(arr));
}
long end = new Date().getTime();
System.out.println("消耗时间:" + (end-start) + "ms");
}
优点:基数排序用链表实现可以不用进行物理上的移动,速度快。
缺点:需要额外的O(N)的空间。
堆排序
核心思想:
堆排序可谓最难理解的排序算法之一(也许没有之一),堆排序是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。
算法描述:
- 将初始待排序关键字序列(R1,R2….Rn)构建成大顶堆,此堆为初始的无序区;
- 将堆顶元素R[1]与最后一个元素R[n]交换,此时得到新的无序区(R1,R2,……Rn-1)和新的有序区(Rn),且满足R[1,2…n-1]<=R[n];
- 由于交换后新的堆顶R[1]可能违反堆的性质,因此需要对当前无序区(R1,R2,……Rn-1)调整为新堆,然后再次将R[1]与无序区最后一个元素交换,得到新的无序区(R1,R2….Rn-2)和新的有序区(Rn-1,Rn)。不断重复此过程直到有序区的元素个数为n-1,则整个排序过程完成。
堆排序动画演示
Java实现堆排序
// 堆排序
public static void heapSort(int[] arr){
long start = new Date().getTime();
//1.构建大顶堆
for(int i=arr.length/2-1;i>=0;i--){
//从第一个非叶子结点从下至上,从右至左调整结构
adjustHeap(arr,i,arr.length);
}
//2.调整堆结构+交换堆顶元素与末尾元素
for(int j=arr.length-1;j>0;j--){
//将堆顶元素与末尾元素进行交换
int temp=arr[0];
arr[0] = arr[j];
arr[j] = temp;
adjustHeap(arr,0,j);//重新对堆进行调整
}
long end = new Date().getTime();
System.out.println("消耗时间:" + (end-start) + "ms");
for (int a: arr){
System.out.println(a);
}
}
// 构建大顶锥
public static void adjustHeap(int[] arr, int i, int length){
int temp = arr[i];//先取出当前元素i
for(int k=i*2+1;k<length;k=k*2+1){//从i结点的左子结点开始,也就是2i+1处开始
if(k+1<length && arr[k]<arr[k+1]){//如果左子结点小于右子结点,k指向右子结点
k++;
}
if(arr[k] >temp){//如果子节点大于父节点,将子节点值赋给父节点(不用进行交换)
arr[i] = arr[k];
i = k;
}else{
break;
}
}
arr[i] = temp;//将temp值放到最终的位置
}
优点:堆排序的效率与快排、归并相同,都达到了基于比较的排序算法效率的峰值(时间复杂度为O(nlogn)),除了高效之外,只需要O(1)的辅助空间了,既最高效率又最节省空间;堆排序效率相对稳定
缺点:维护问题,实际场景中的数据是频繁发生变动的,而对于待排序序列的每次更新(增,删,改),都要重新做一遍堆的维护。
结语
好了,几种常见的排序算法已经分享完毕,这几种算法应该算是所有程序员或者技术从业人员掌握的基础了,只有打下最牢固的基础,才能永攀新技术的高峰,谢谢大家!
© 版权声明
文章版权归作者所有,未经允许请勿转载。
相关文章
暂无评论...