(11)堆排序

堆排序与归并排序一样,堆排序的时间复杂度为O(nlgn),但是不同于归并排序的是堆排序具有空间原址性:任何时候都需要常数个额外的元素空间存储临时数据,基本在原数组进行交换数据
堆排序还引入了另外一种算法设计技巧,可以构造一种有效的优先队列,堆一般分为最大堆和最小堆,本文以最大堆为例

1、堆

(二叉)堆给人的感觉是一个二叉树,但是其本质是一种数组对象,因为对堆进行操作的时候将堆视为一颗完全二叉树,树种每个节点与数组中的存放该节点值的那个元素对应。所以堆又称为二叉堆,堆与完全二叉树的对应关系如下图所示:


(11)堆排序_第1张图片
堆排序.png

通常给定节点i,可以根据其在数组中的位置求出该节点的父亲节点、左右孩子节点。书上介绍的时候,该数组是从1开始的,所以数组A[1...length]
给定一个节点的下标i,则计算它的父节点,左节点,右节点的小标如下:PARENT(i)=i/2  LEFT(i) = 2i   RIGHT(i) = 2i+1。

根据节点数值满足的条件,可以将分为最大堆和最小堆。最大堆的特性是:除了根节点以外的每个节点i,有A[PARENT(i)] >= A[i],最小堆的特性是:除了根节点以外的每个节点i,有A[PARENT(i)] >=A[i]。

把堆看成一个棵树,有如下的特性:

  • 含有n个元素的堆的高度是lgn。
  • 当用数组表示存储了n个元素的堆时,叶子节点的下标是n/2+1,n/2+2,……,n。
  • 在最大堆中,最大元素该子树的根上;在最小堆中,最小元素在该子树的根上。
2、保持堆的性质

堆个关键操作过程是如何保持堆的特有性质,给定一个节点i,要保证以i为根的子树满足堆性质。书中以最大堆作为例子进行讲解,并给出了递归形式的保持最大堆性的操作过程MAX-HEAPIFY。先从看一个例子,操作过程如下图所示:


(11)堆排序_第2张图片
最大堆性质.png

从图中可以看出,在节点i=2时,不满足最大堆的要求,需要进行调整,选择节点2的左右孩子中最大一个进行交换,然后检查交换后的节点i=4是否满足最大堆的要求,从图看出不满足,接着进行调整,直到没有交换为止。

3、建堆

建立最大堆的过程是自底向上地调用最大堆调整程序将一个数组A[1.....N]变成一个最大堆。将数组视为一颗完全二叉树,从其最后一个非叶子节点(n/2)开始调整。调整过程如下图所示:


(11)堆排序_第3张图片
建堆.png
4、堆排序算法

堆排序算法过程为:先调用创建堆函数将输入数组A[1...n]造成一个最大堆,使得最大的值存放在数组第一个位置A[1],然后用数组最后一个位置元素与第一个位置进行交换,并将堆的大小减少1,并调用最大堆调整函数从第一个位置调整最大堆:
(1)创建最大堆,数组第一个元素最大,执行后结果下图:


(11)堆排序_第4张图片
堆排序1.png

(2)进行循环,从length(A)到2,并不断的调整最大堆,给出一个简单过程如下:


(11)堆排序_第5张图片
堆排序2.png
5、完整的代码实现
/**
 * @Project: 10.dataStructure
 * @description: 堆排序
 * @author: sunkang
 * @create: 2018-08-21 20:49
 * @ModificationHistory who      when       What
 **/
public class HeapSort {
    /**
     *  维护最大堆的性质
     *  时间复杂度为O(lgn)
     *  最大堆的性质为:就是除了跟节点以为,所有节点要满足父节点大于子节点 A[Parent[i]] >=A[i]
      * @param A 数组A
     * @param i 要排序的数组的下标
     * @param heapSize 堆元素的个数
     */
   public void  maxHeapify(int[] A,int i,int heapSize){
       int  left = 2*i; //左子节点
       int rigth = 2*i +1; //右子节点
       int largest = i ;  //largest记录临时的最大节点下标
       //i-1 表示数组的真实下标,与堆的下标是有一个1的差距
       if( left<= heapSize && A[left-1]>A[largest-1]){
           largest = left;
       }
       if(rigth <= heapSize && A[rigth-1]> A[largest-1]){
           largest = rigth;
       }
       if( largest != i){
        swap(A,i-1,largest-1);//交换数据,数据大下沉
        maxHeapify(A,largest,heapSize);//保证下沉的数据也要保证堆的最大性质
       }
    }

    /**
     * 该方法作用是:建堆过程把一个大小为n= A.length的数组A[1...n]转换成最大堆
     * 由于A[n/2+1...n]中的元素都是树的叶点,所有叶节点不需要重新构造堆的最大性质,已经满足了该性质
     * 还剩下 A.length/2 需要进行构造
     *
     * 建堆过程从最后一个节点的父节点开始保证每一个节点保证堆的有序性,如果顺序想法则不行。
     * @param A 数组A
     */
    public void buildMaxHeap(int[] A){
        int length = A.length/2;
        for(int i=length;i>0;i--){
            maxHeapify(A,i,A.length);
        }
    }

    /**
     * 堆排序算法
     * 时间复杂度为O(nlgn),功能是对一个数组进行原址排序
     * @param A
     */
    public void  heapSort(int[] A){
        buildMaxHeap(A);//已经存在一个最大堆的元素了
        for (int i =A.length ;i>1;i--){
            swap(A,0,i-1);//把最大元素移到最后一个
            maxHeapify(A,1,i-1);
        }
    }
    /**
     * 交换数组A的两个数据
     * @param A
     * @param i
     * @param j
     */
    private   void   swap(int[] A,int i ,int j  ){
        //暂存的数据
        int temp = A[i];
        //A[i]的数据引用指向A[j],完成A[i]的数据变成A[j]
        A[i]= A[j];
        //A[j]的数据执行引用temp,完成A[j]的数据变成A[i]
        A[j]=temp;
    }
    public  void display(int[] arr){
        for(Integer in:arr){
            System.out.print(in+",");
        }
        System.out.println();
    }
    public static void main(String[] args) {
        int[] arr  = new int[]{1,2,7,4,3,9,8,14};
        HeapSort  heapSort = new HeapSort();
        heapSort.heapSort(arr);
        heapSort.display(arr);
    }
}

你可能感兴趣的