清晰理解堆排序

堆的定義

  • 一個徹底二叉樹中,任意父結點老是大於或等於(小於或等於)任何一個子節點,則爲大頂堆(小頂堆)。

堆的數組存儲方式

徹底二叉樹適合採用順序存儲的方式,所以一個數組能夠當作一個徹底二叉樹。java

  • 節點編號:樹根起,自上層到下層,每層從左至右,給全部結點順序編號,能獲得一個反映整個二叉樹結構的線性序列。

clip_image001

  • 編號特色

從一個結點的編號就可推得其雙親,左、右孩子,兄弟等結點的編號。假設編號爲i的結點是ki(1≤i≤n),則有:算法

  ①若i>1,則ki的雙親編號爲i/2;若i=1,則Ki是根結點,無雙親。數組

  ②若2i≤n,則Ki的左孩子的編號是2i;不然,Ki無左孩子,即Ki一定是葉子。所以徹底二叉樹中編號i>n/2的結點一定是葉結點。函數

  ③若2i+1≤n,則Ki的右孩子的編號是2i+1;不然,Ki無右孩子。ui

注:ki(0≤i≤n)知足數組下標時,則可能的左右孩子分別爲2i+一、2i+2。spa

堆排序的思想(以大頂堆爲例)

利用堆頂記錄的是最大關鍵字這一特性,每一輪取堆頂元素放入有序區,就相似選擇排序每一輪選擇一個最大值放入有序區,能夠把堆排序當作是選擇排序的改進3d

  1. 將初始待排序關鍵字序列(R0,R1,R2....Rn)構建成大頂堆,此堆爲初始的無序區;
  2. 將堆頂元素R[0]與最後一個元素R[n]交換,此時獲得新的無序區(R0,R1,R2,......Rn-1)和新的有序區(Rn);
  3. 因爲交換後新的堆頂R[0]可能違反堆的性質,所以須要對當前無序區(R0,R1,R2,......Rn-1)調整爲新堆。

不斷重複此二、3步驟直到有序區的元素個數爲n-1,則整個排序過程完成。blog

 

篩選算法

//最難理解的地方排序

  • 目標:一個全部子樹都爲堆的徹底二叉樹。意思就是這個二叉樹只差跟節點不知足堆的結構。//很重要,很重要,很重要

    以下圖:遞歸

clip_image002

  • 方法:首先將root和它的左右子樹的根結點進行比較,把最大的元素交換到root節點;而後順着被破壞的路徑一路調整下去,直至葉子結點,就獲得新的堆。

clip_image003

  • 運用:1.在上文提到的堆排序思想,2-3步驟中將無序區調整爲堆的時候用到。

          2.初始化堆

初始化堆

從最後一個非葉子節點i(i=n/2,n爲節點個數)開始,將以i爲根節點的二叉樹經過篩選調整爲堆。以第一張圖爲例,編號順序爲八、七、6...1。

從最後一個非葉子節就保證了篩選算法的正確性,由於篩選算法的目標是一個全部子樹都爲堆的徹底二叉樹

java實現堆排序

package sort;

import java.util.Arrays;
import util.MathUtil;
/**
 * 經過大頂堆實現堆排序,升序排序
 * 
 */

public class HeapSort {
    public static void main(String[] args) {
        int[] arr={9,6,12,32,23,11,2,100,85};
        sort(arr);
        System.out.println(Arrays.toString(arr));
    }
    //這裏將i定義爲徹底二叉樹的根
    //將徹底二叉樹調整爲大頂堆,前提是二叉樹的根的子樹已經爲大頂堆。
    public static void adjustHeap(int[]a ,int i,int size){
        int lChild=2*i+1;        //左孩子
        int rChild=2*i+2;        //又孩子
        int max=i;                //臨時變量
        if(i<size/2){            //若是i是葉子節點就結束
            if(lChild<size&&a[max]<a[lChild])
                max=lChild;
            if(rChild<size&&a[max]<a[rChild])
                max=rChild;
            if(max!=i){
                MathUtil.swap(a, max, i);//交換後破環了子樹的堆結構
                adjustHeap(a, max, size);//遞歸,調節子樹爲堆
            }
        }
    }
    
    //創建堆,堆是從下往上創建的,由於adjustHeap函數是創建在子樹已經爲大頂堆。
    public static void buildHeap(int[]a,int size){    
        for(int i=size/2;i>=0;i--){//從最後一個非葉子節點,才能構成adjustHeap操做的目標二叉樹
            adjustHeap(a, i, size);
        }        
    }

    //將數組分爲兩部分,一部分爲有序區,在數組末尾,另外一部分爲無序區。堆屬於無序區
    public static void sort(int[] arr){
        int size=arr.length;
        buildHeap(arr, size);
        for(int i=size-1;i>0;i--){//i爲無序區的長度,通過以下兩步,長度遞減
            //堆頂即下標爲0的元素
            MathUtil.swap(arr, i, 0);//1.每次將堆頂元素和無序區最後一個元素交換,即將無序區最大的元素放入有序區
            adjustHeap(arr, 0, i);   //2.將無順區調整爲大頂堆,即選擇出最大的元素。
        }
    }    
}

相關文章
相關標籤/搜索