浅谈排序算法(下)

编程语言3年前 (2021)更新 peterx
1,203 0 0

软件设计有两种方式:一种方式是,使软件过于简单,明显没有缺陷;另一种方式是,使软件过于复杂,没有明显的缺陷。

——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)的辅助空间了,既最高效率又最节省空间;堆排序效率相对稳定

缺点:维护问题,实际场景中的数据是频繁发生变动的,而对于待排序序列的每次更新(增,删,改),都要重新做一遍堆的维护。



结语

好了,几种常见的排序算法已经分享完毕,这几种算法应该算是所有程序员或者技术从业人员掌握的基础了,只有打下最牢固的基础,才能永攀新技术的高峰,谢谢大家!

© 版权声明

相关文章

暂无评论

暂无评论...