1. 程式人生 > >堆排序演算法及其Java實現(以大根堆為例)

堆排序演算法及其Java實現(以大根堆為例)

(二叉)堆資料結構是一種陣列物件,如圖所示(下標從0開始),它完全可以被視為一棵完全二叉樹。
陣列對應的完全二叉樹
接下來要出現的幾個詞語,這裡介紹一下:
length[A]: 陣列A中元素的個數
heap-size[A]: 存放在陣列A中堆的元素的個數,是要排序的元素的個數,在進行堆排序時,這個是會變的(減1)
A[0]是樹的根,A[i]是陣列中的第i個元素(從0開始計數)
PARENT(i): 第i個元素父結點的位置,值為 (i - 1)/2
LEFT(i): 第i個元素左孩子的位置,值為2i + 1
RIGHT(i): 第i個元素右孩子的位置,值為 2i + 2

static int parent(int
i) { return (i - 1)/2; } static int left(int i) { //左孩子 return 2*i + 1; } static int right(int i) { //右孩子 return 2*i + 2; }

操作:
MAX-HEAPIFY: 保持大根堆性質,執行時間O(lg n)
BUILD-MAX-HEAP: 大根堆初始建堆,即在無序的輸入基礎上建堆,執行時間O(n)
HEAPSORT: 堆排序,對一個數組進行原地排序,執行時間O(n*lg n)

一、MAX-HEAPIFY(保持大根堆性質)
大根堆的性質:A[PARENT(i)] >= A[i],MAX-HEAPIFY是對大根堆進行操作的重要子程式,後面要經常用到。
輸入:以a[i]為根結點的子樹,且以a的左孩子和a的右孩子為根節點的子樹都滿足大根堆性質
輸出:樹形沒有變,但結點順序可能發生改變的子樹(該樹的所有子樹的根節點的值都大於它左右孩子的值,保持了大根堆性質)

static void maxHeapfy(int []a,int i,int heapSize) {   //陣列a,第i個結點,heapSize是陣列中實際要排序的元素的長度
    int left = left(i);     //有的時候能夠遞迴到葉子結點,葉子結點無後繼,下面兩個if都注意到了這一點
    int right = right(i);   
    int largest = i;
    if(left < heapSize && a[left] > a[largest]) {   //
        largest = left;
    }
    if
(right < heapSize && a[right] > a[largest]) { largest = right; } if(largest != i) { //把最大值給父結點 a[largest] = a[largest] ^ a[i]; a[i] = a[largest] ^ a[i]; a[largest] = a[largest] ^ a[i]; maxHeapfy(a,largest,heapSize); //發生交換之後還要保證大根堆性質 } }

二、BUILD-MAX-HEAP(初始建堆)
輸入:無序的一組數
輸出:一組數,按層序對應一棵完全二叉樹,並且在它的每個子樹中,根節點的值都大於其後代結點的值
步驟:自底向上,從最後一個非葉子結點開始呼叫MAX-HEAPFY操作,以下圖為例
初始化建堆過程
初始化建堆過程
初始化建堆過程
程式碼實現:

static void buildMaxHeap(int []a,int heapSize) {
    for(int i = (heapSize-1)/2;i >= 0;i--) {
        maxHeapfy(a,i,heapSize);
    }
}

三、堆排序(HEAPSORT)
通過初始建堆,陣列中最大的元素在A[0],此時我們把A[0]與A[n-1]互換,對新陣列A[0]~A[n-2]重新建堆,然後第二大的元素又落在了A[0]的位置上,此時我們把A[0]與A[n-2]互換…..以此類推,堆的大小由n-1一直降到2,我們得到原輸入序列的升序排列
HeapSort
程式碼:

static void heapSort(int []a) {
    for(int i = a.length-1;i > 0;i--) {
        buildMaxHeap(a,i+1);   //堆的大小從n到2
        a[i] = a[0] ^ a[i];    //交換
        a[0] = a[0] ^ a[i];
        a[i] = a[0] ^ a[i];
    }
}

在初始建堆之後,原根結點的左右子樹均滿足最大堆的性質,所以實際上在對A[0]~A[n-2]進行初始建堆的時候,只有在根結點A[0]處執行了MAX-HEAPIFY操作,下面的程式碼換一種更直觀的寫法:

    static void heapSort(int []a) {
        int len = a.length;
         buildMaxHeap(a,len);        //初始建堆
         a[len-1] = a[0] ^ a[len-1];    //交換
         a[0] = a[0] ^ a[a.length-1];
         a[len-1] = a[0] ^ a[len-1];
         for(int i = 1;i<len-1;i++) { //初始建堆之後還要排a.length-2次
             maxHeapfy(a,0,len-i);
             a[len-1-i] = a[0] ^ a[len-1-i];    //交換
             a[0] = a[0] ^ a[len-1-i];
             a[len-1-i] = a[0] ^ a[len-1-i];
         }
    }

//如有錯誤,歡迎指正
整個程式程式碼如下:

package ex;
import java.util.Arrays;

public class Sort {
    public static void main(String args[]) {  
        int []a = new int[] {16,25,34,27,30,5,7,4,41,55};
        Sort.heapSort(a);
        System.out.println(Arrays.toString(a));
    }
    static int parent(int i) {
        return (i - 1)/2;
    }
    static int left(int i) {
        return 2*i + 1;
    }
    static int right(int i) {
        return 2*i + 2;
    }
    static void maxHeapfy(int []a,int i,int heapSize) {   //陣列a,第i個結點,heapSize是陣列種實際要排序的元素的長度
        int left = left(i);     //有的時候能夠遞迴到葉子結點,葉子結點無後繼,下面兩個if都注意到了這一點
        int right = right(i);   
        int largest = i;
        if(left < heapSize && a[left] > a[largest]) {   //
            largest = left;
        }
        if(right < heapSize && a[right] > a[largest])  
        {
            largest = right;
        }
        if(largest != i) {      //把最大值給父結點
            a[largest] = a[largest] ^ a[i];
            a[i] = a[largest] ^ a[i];
            a[largest] = a[largest] ^ a[i];
            maxHeapfy(a,largest,heapSize);    //發生交換之後還要保證大根堆性質
        }
    }
    static void buildMaxHeap(int []a,int heapSize) {
        for(int i = (heapSize-2)/2;i >= 0;i--) {
            maxHeapfy(a,i,heapSize);
        }
    }
    static void heapSort(int []a) {
        int len = a.length;
         buildMaxHeap(a,len);  //初始建堆
         a[len-1] = a[0] ^ a[len-1];    //交換
         a[0] = a[0] ^ a[a.length-1];
         a[len-1] = a[0] ^ a[len-1];
         for(int i = 1;i<len-1;i++) { //初始建堆之後還要排a.length-2次
             maxHeapfy(a,0,len-i);
             a[len-1-i] = a[0] ^ a[len-1-i];    //交換
             a[0] = a[0] ^ a[len-1-i];
             a[len-1-i] = a[0] ^ a[len-1-i];
         }
    }
}
//輸出結果:
//[4, 5, 7, 16, 25, 27, 30, 34, 41, 55]