二叉堆及堆排序

什麼是二叉堆

二叉堆是一種特殊的堆,二叉堆是徹底二元樹(二叉樹)或者是近似徹底二元樹(二叉樹)。算法

二叉堆有兩種:最大堆和最小堆。

最大堆:父結點的鍵值老是大於或等於任何一個子節點的鍵值;
最小堆:父結點的鍵值老是小於或等於任何一個子節點的鍵值。數組

對於二叉堆,以下有幾種操做

插入節點bash

二叉堆的節點插入,插入位置是徹底二叉樹的最後一個位置。好比咱們插入一個新節點,值是 0。ui

MacDown logo

這時候,咱們讓節點0的它的父節點5作比較,若是0小於5,則讓新節點「上浮」,和父節點交換位置。spa

MacDown logo

繼續用節點0和父節點3作比較,若是0小於3,則讓新節點繼續「上浮」。3d

MacDown logo

繼續比較,最終讓新節點0上浮到了堆頂位置。code

MacDown logo

刪除節點cdn

二叉堆的節點刪除過程和插入過程正好相反,所刪除的是處於堆頂的節點。好比咱們刪除最小堆的堆頂節點1。blog

MacDown logo

這時候,爲了維持徹底二叉樹的結構,咱們把堆的最後一個節點10補到本來堆頂的位置。排序

MacDown logo

接下來咱們讓移動到堆頂的節點10和它的左右孩子進行比較,若是左右孩子中最小的一個(顯然是節點2)比節點10小,那麼讓節點10「下沉」。

MacDown logo

繼續讓節點10和它的左右孩子作比較,左右孩子中最小的是節點7,因爲10大於7,讓節點10繼續「下沉」。

MacDown logo

這樣一來,二叉堆從新獲得了調整。

構建二叉堆

構建二叉堆,也就是把一個無序的徹底二叉樹調整爲二叉堆,本質上就是讓全部非葉子節點依次下沉。

咱們舉一個無序徹底二叉樹的例子:

MacDown logo

首先,咱們從最後一個非葉子節點開始,也就是從節點10開始。若是節點10大於它左右孩子中最小的一個,則節點10下沉。

MacDown logo

接下來輪到節點3,若是節點3大於它左右孩子中最小的一個,則節點3下沉。

MacDown logo

接下來輪到節點1,若是節點1大於它左右孩子中最小的一個,則節點1下沉。事實上節點1小於它的左右孩子,因此不用改變。

接下來輪到節點7,若是節點7大於它左右孩子中最小的一個,則節點7下沉。

MacDown logo

節點7繼續比較,繼續下沉。

MacDown logo

這樣一來,一顆無序的徹底二叉樹就構建成了一個最小堆。

堆的代碼實現

二叉堆通常用數組來表示。若是根節點在數組中的位置是1,第n個位置的子節點分別在2n和 2n+1。所以,第1個位置的子節點在2和3,第2個位置的子節點在4和5。以此類推。這種基於1的數組存儲方式便於尋找父節點和子節點。

如上圖所示二叉堆所示用數組方式表示爲 | 1 | 5 | 2 | 6 | 7 | 3 | 8 | 9 | 10 |

堆得代碼表示

/**
     * 上浮調整
     *
     * @param array 待調整的堆
     */
    public static void upAdjust(int[] array) {
        int childIndex = array.length - 1;
        int parentIndex = (childIndex - 1) / 2;
        // temp保存插入的葉子節點值,用於最後的賦值
        int temp = array[childIndex];
        while (childIndex > 0 && temp < array[parentIndex]) {
            //無需真正交換,單向賦值便可
            array[childIndex] = array[parentIndex];
            childIndex = parentIndex;
            parentIndex = (parentIndex - 1) / 2;
        }
        array[childIndex] = temp;
    }

    /**
     * 下沉調整
     *
     * @param array       待調整的堆
     * @param parentIndex 要下沉的父節點
     * @param length      堆的有效大小
     */
    public static void downAdjust(int[] array, int parentIndex, int length) {
        // temp保存父節點值,用於最後的賦值
        int temp = array[parentIndex];
        int childIndex = 2 * parentIndex + 1;


        while (childIndex < length) {
            // 若是有右孩子,且右孩子小於左孩子的值,則定位到右孩子
            if (childIndex + 1 < length && array[childIndex + 1] < array[childIndex]) {
                childIndex++;
            }
            // 若是父節點小於任何一個孩子的值,直接跳出
            if (temp <= array[childIndex])
                break;
            //無需真正交換,單向賦值便可
            array[parentIndex] = array[childIndex];
            parentIndex = childIndex;
            childIndex = 2 * childIndex + 1;
        }

        array[parentIndex] = temp;
    }

    /**
     * 構建堆
     *
     * @param array 待調整的堆
     */

    public static void buildHeap(int[] array) {
        // 從最後一個非葉子節點開始,依次下沉調整
        int parent = array.length / 2 - 1;
        for (int i = parent; i >= 0; i--) {
            downAdjust(array, i, array.length);
        }
    }

    public static void main(String[] args) {
        int[] array = new int[]{1, 3, 2, 6, 5, 7, 8, 9, 10, 0};
        upAdjust(array);
        System.out.println(Arrays.toString(array));

        array = new int[]{7, 1, 3, 10, 5, 2, 8, 9, 6};
        buildHeap(array);
        System.out.println(Arrays.toString(array));

    }
    
[0, 1, 2, 6, 3, 7, 8, 9, 10, 5]
[1, 5, 2, 6, 7, 3, 8, 9, 10]
複製代碼

堆排序

堆排序算法的步驟:

一、把無序數組構建成二叉堆。

二、循環刪除堆頂元素,移到集合尾部,調節堆產生新的堆頂。

public static void heapSort(int[] array) {

        // 1.把無序數組構建成二叉堆。
        for (int i = (array.length - 2) / 2; i >= 0; i--) {
            downAdjust(array, i, array.length);
        }
        System.out.println(Arrays.toString(array));
        // 2.循環刪除堆頂元素,移到集合尾部,調節堆產生新的堆頂。
        for (int i = array.length - 1; i > 0; i--) {
            // 最後一個元素和第一元素進行交換
            int temp = array[i];
            array[i] = array[0];
            array[0] = temp;
            // 下沉調整最大堆
            downAdjust(array, 0, i);
        }
    }
複製代碼
相關文章
相關標籤/搜索