• 算法学习总结(六):堆排序


    一、算法简介

      堆排序利用了大根堆(或小根堆)堆顶记录的关键字最大(或最小)这一特征,使得在当前无序区中选取最大(或最小)关键字的记录变得简单。

    二、算法描述

    1、什么是堆?

          我们这里提到的堆一般都指的是二叉堆,它满足二个特性:

               1---父结点的键值总是大于或等于(小于或等于)任何一个子节点的键值。

               2---每个结点的左子树和右子树都是一个二叉堆(都是最大堆或最小堆)。

         如下为一个最小堆(父结点的键值总是小于任何一个子节点的键值)

    2、什么是堆调整(Heap Adjust)?

            这是为了保持堆的特性而做的一个操作。对某一个节点为根的子树做堆调整,其实就是将该根节点进行“下沉”操作(具体是通过和子节点交换完成的),一直下沉到合适的位置,使得刚才的子树满足堆的性质。

           例如对最大堆的堆调整我们会这么做:

                  1、在对应的数组元素A[i], 左孩子A[LEFT(i)], 和右孩子A[RIGHT(i)]中找到最大的那一个,将其下标存储在largest中。

                  2、如果A[i]已经就是最大的元素,则程序直接结束。

                  3、否则,i的某个子结点为最大的元素,将A[largest]与A[i]交换。

                  4、再从交换的子节点开始,重复1,2,3步,直至叶子节点,算完成一次堆调整。

           这里需要提一下的是,一般做一次堆调整的时间复杂度为log(n)。

           如下为我们对4为根节点的子树做一次堆调整的示意图,可帮我们理解。

    3、如何建堆

             建堆是一个通过不断的堆调整,使得整个二叉树中的数满足堆性质的操作。在数组中的话,我们一般从下标为n/2的数开始做堆调整,一直到下标为0的数(因为下标大于n/2的数都是叶子节点,其子树已经满足堆的性质了)。下图为其一个图示:

      很明显,对叶子结点来说,可以认为它已经是一个合法的堆了即20,60, 65, 4, 49都分别是一个合法的堆。只要从A[4]=50开始向下调整就可以了。然后再取A[3]=30,A[2] = 17,A[1] = 12,A[0] = 9分别作一次向下调整操作就可以了。

     4、如何进行堆排序

            堆排序是在上述3中对数组建堆的操作之后完成的。

            数组储存成堆的形式之后,第一次将A[0]与A[n - 1]交换,再对A[0…n-2]重新恢复堆。第二次将A[0]与A[n-2]交换,再对A[0…n-3]重新恢复堆,重复这样的操作直到A[0]与A[1]交换。由于每次都是将最小的数据并入到后面的有序区间,故操作完成后整个数组就有序了。

    如下图所示:

    最差时间复杂度 O(n log n)
    最优时间复杂度 O(n log n)
    平均时间复杂度 O(n log n)
    最差空间复杂度 需要额外辅助空间O(n)

    三、示例代码

    public class HeapSort {
        public int[] heapSort(int[] A, int n) {
            // write code here
            for(int i=n/2; i>=0; i--){
                heapAdjust(A,i,n);
            }
            
            for(int i=n-1;i>0;i--){
                swap(A,0,i);
                heapAdjust(A,0,i);
            }
            return A;
        }
        
        void heapAdjust(int[] A,int index,int length){
            int childLeft;
            int temp = A[index];
            for( ;index*2+1 < length;index = childLeft){
                childLeft = index*2+1;
                if(childLeft !=length-1 && A[childLeft] < A[childLeft+1]){
                     childLeft++;
                }
                if(temp > A[childLeft]){
                    break;
                    
                }                
                else {
                    A[index] = A[childLeft];
                    index = childLeft;
                }           
            }
             A[index] = temp;
            
        }
        
        static void  swap(int[] A,int m,int n){
            int temp = A[m];
            A[m] = A[n];
            A[n] = temp;
        }
    }
    -
  • 相关阅读:
    docker私有仓库搭建及使用
    服务器ip迁移纪要
    Windows 下QT程序发布
    Prometheus监控软件部署方法
    android的listview控件,加了行内按钮事件导致行点击失效的问题
    惊奇!Android studio内部在调用Eclipse
    关于Android Stuido2.3和Eclipse4.4
    XCODE9.1的一些新问题
    osx12.6设置全屏
    IEEE754浮点数与字节数互转工具
  • 原文地址:https://www.cnblogs.com/gugibv/p/5695605.html
Copyright © 2020-2023  润新知