【圖解算法】排序算法——堆排序

簡介

關於堆排序(HeapSort),堆這種數據結構比這種排序算法更爲有價值。

堆排序(Heapsort)是指利用堆積樹(堆)這種數據結構所設計的一種排序算法,它是選擇排序的一種。可以利用數組的特點快速定位指定索引的元素。堆分爲大根堆和小根堆,是完全二叉樹。大根堆的要求是每個節點的值都不大於其父節點的值,即A[PARENT[i]] >= A[i]。在數組的非降序排序中,需要使用的就是大根堆,因爲根據大根堆的要求可知,最大的值一定在堆頂。(from 百度百科)

這裏需要補充介紹一下堆這種數據結構:

堆通常是一個可以被看做一棵樹的數組對象。堆總是滿足下列性質:

  1. 堆中某個節點的值總是不大於或不小於其父節點的值;
  2. 堆總是一棵完全二叉樹。

最大二叉推

我們可以根據性質一的描述將堆分爲最大堆(子節點永遠小於父節點)、最小堆(子節點永遠大於父節點),上圖所示是一個最大堆。我們可以在計算機中使用一個數組來存儲一個堆。

[ -, 90, 36, 17, 25, 26, 7, 1, 2, 3, 10 ]

注意我們將數組下標爲 0 的位置棄用了,根據圖示我們不難得出一個節點的父節點和子節點的下標位置。

  1. 父節點的位置: i/2
  2. 左子節點的位置: i*2
  3. 右子節點的位置: i*2 + 1

維護堆

維護一個堆需要做兩件事情:進隊和出隊

進隊

HeapIn

進隊需要做的事情很簡單,先將元素丟進隊列尾,然後和父節點進行比較,比父節點大時則於其交換位置,比父節點小時則表示進隊完成。

出隊

HeapOut

出隊像上圖所示,也非常簡單,只需要將隊列頭位置的元素取出(下標爲 1 的元素),然後將隊列尾的元素填充到隊列頭,接下來和子節點較大者對換位置,直至比子節點的元素都要大時結束交換位置。

排序

在弄清楚了堆的維護之後我們只需要將數組中的元素入隊和出隊,即可完成整個排序過程。

Java 實現

MaxHeap.java

/**
 * 最大二叉堆
 * 概念: 父結點的鍵值總是大於或等於任何一個子節點的鍵值
 */
public class MaxHeap {
    // 模擬最大堆的數組,記錄從 1 開始

    private int[] heapArr;

    // 當前位置的計數,由此可得出:
    // 父節點位置 count/2 ;
    // 左邊的子節點 count * 2 ;
    // 右邊的子節點 count * 2 + 1 。
    private int count;

    // 堆的大小
    private int length;

    /**
     * @param length 申明 heapArr 的長度
     */
    public MaxHeap(int length) {
        this.heapArr = new int[length + 1];
        this.count = 0;
        this.length = length;
    }

    public int size() {
        return count;
    }

    public boolean isEmpty() {
        return count == 0;
    }

    /**
     * 插入一個新的元素
     * @param item
     */
    public void insert(int item) {
        if(count + 1 > length) throw new ArrayIndexOutOfBoundsException();
        heapArr[count + 1] = item;
        count ++;
        shiftUp(count);
    }

    /**
     * 取出最大(優先)的元素
     * @return 存在即取出改元素,若不存在則返回 Integer 最小值
     */
    public int extractMax() {
        if(count < 1) throw new ArrayIndexOutOfBoundsException();
        int ret = heapArr[1];
        swap(heapArr, 1, count );
        count --;
        shiftDown(1);
        // 由於上面的 count-- 了,即 count >= 0 表明下標爲 1 的位置存在值
        return count >= 0?ret:Integer.MIN_VALUE;
    }

    /**
     * 將堆中最大的元素出隊,並調整最大二叉堆的位置,保持最大二叉堆的定義
     */
    private void shiftDown(int n) {
        while (n*2 <= count) { // 當 n 位置的元素存在子節點時
           // 較出左節點和右節點的較大的元素,然後將較大元素與 n 位置元素比較,若 n 位置元素較小則與其交換位置
            int j = n*2; // 較大子節點下標,初始化爲左子節點
            if(j + 1 <= count) { // 存在右節點
                j = heapArr[j + 1] > heapArr[j]?j+1:j;
            }
            if(heapArr[n] < heapArr[j]) {
                swap(heapArr, n, j);
                n = j;
            }else {
                break;
            }
        }
    }

    /**
     * 調整位置爲 n 的元素的位置,即保持最大二叉堆的定義 -> 父結點的鍵值總是大於或等於任何一個子節點的鍵值
     * @param n 需要調整的元素的位置
     */
    private void shiftUp(int n) {
        while (n > 1 && heapArr[n/2] < heapArr[n]) {
            swap(heapArr, n/2, n);
            n = n/2;
        }
    }

    /**
     * 交換數組中的兩個元素
     * @param arr
     * @param a
     * @param b
     */
    private void swap(int[] arr,int a,int b){
        int temp = arr[a];
        arr[a] = arr[b];
        arr[b] = temp;
    }

}

測試方法


    public static void main(String[] args) {
        MaxHeap maxHeap = new MaxHeap(100);
        int [] testArr = {2,7,26,25,19,17,1,90,3,36};
        for (int i = 0; i < testArr.length; i++) {
            maxHeap.insert(testArr[i]);
        }

        for (int i = 0; i < testArr.length; i++){
            System.out.print( maxHeap.extractMax() );
            if(i != testArr.length - 1) System.out.print(", ");
        }

    }

HeapSort
堆排序的過程

最後

過兩天就新年了,祝願各位朋友新年快樂!

還有需要一提的是: 該算法存在優化的途徑,比如說在入隊時可以採取直接整個數組入隊的方式;以及在交換方式上優化,使用賦值的方式取代(直接算出需要交換的值,避免多次交換值),諸君加油。

圖解算法目錄

【圖解算法】排序算法——冒泡排序、選擇排序

【圖解算法】排序算法——插入排序

【圖解算法】排序算法——歸併排序

【圖解算法】排序算法——快速排序

【圖解算法】Java GC算法

【圖解算法】排序算法——堆排序

【圖解算法】並查集 —— 聯合查找算法

【圖解算法】排序算法——計數排序

Gif Power By https://visualgo.net

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章