如何構建一個大根堆

數組可以看成是一個完全二叉樹,大根堆是一個完全二叉樹

在這裏插入圖片描述

構造大根堆

例子1:[O(N)---->從下到上]

因爲堆是對父節點-左/右孩子節點之間的約束,所以從最後一個非葉子節點開始調整。

在這裏插入圖片描述
注意每次交換後,都要對下一層的子堆進行遞歸調整,因爲交換後有可能破壞已調整子堆的結構。
堆排序

例子2:[O(N)---->從下到上]

1、假設給定無序序列結構如下
在這裏插入圖片描述
2.此時我們從最後一個非葉子結點開始(葉結點自然不用調整,第一個非葉子結點 arr.length/2-1=5/2-1=1,也就是下面的6結點),從左至右,從下至上進行調整。【9下沉之後,9變成了葉子節點,因此不會對子葉產生影響】

在這裏插入圖片描述
4.找到第二個非葉節點4 【3/2 - 1 = 0】,由於[4,9,8]中9元素最大,4和9交換。【4下沉之後,變動了的子樹必須重新調整】
在這裏插入圖片描述
這時,交換導致了子根[4,5,6]結構混亂,繼續調整,[4,5,6]中6最大,交換4和6。
在這裏插入圖片描述
此時,我們就將一個無需序列構造成了一個大頂堆。

圖解排序算法(三)之堆排序

第3個例子【從上到下 】

從一個空的二叉樹開始:
在這裏插入圖片描述
從頭開始遍歷數組:

for(int i = 0; i < arr.length; i++){
	選出arr[i],插入二叉樹
}

第一次循環:當前需要插入的索引是0,令cur = 0,它的父節點parent = (cur - 1) / 2 = 0 。

  • arr[父節點索引] == arr[當前節點索引] ,因此進入下一輪循環
    在這裏插入圖片描述
    第二次循環:當前需要插入的數據索引是1【需要插入的二叉樹的位置索引也是1】,令cur = 1,它的父節點索引是 parent = (cur - 1) / 2 = 0,
  • arr[cur] 與arr[parent]比較,因爲 當前節點值 > 父節點值,因爲需要最大值登頂,因此進入內循環:
    • 交換 當前節點和父節點的值
    • 將當前索引指向父節點,重新計算父節點
    • 因爲cur = 0, par = 0,兩者相等,因此跳出內循環

進入下一輪循環
在這裏插入圖片描述
在這裏插入圖片描述
第三次循環:需要插入的索引爲2,令cur = 2,其父節點 par = (2 - 1)/2 = 0,

  • 當前索引值73與父節點值25比較,當前索引比較大,進入內循環:
    • 交換當前節點與父節點的值
    • 當前索引指向父節點索引,重新計算父節點的索引
    • 因爲cur = 0, par = 0,因此跳出循環

在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述

第4次循環,需要插入的索引爲3,令cur =3,其父節點 par = (3 - 1)/2 = 1,

  • 當前索引值98與父節點值10比較,當前索引比較大,進入內循環:
    • 交換當前節點與父節點
    • 令cur = 夫節點 = 1,重新計算父節點 par = 0
      在這裏插入圖片描述
      在這裏插入圖片描述

判斷是否還是在內循環中:

  • 當前cur = 1, par = 0,因爲當前節點98比父節點73大,進入內層循環
    • 交換98和73的位置
    • 令cur = 父節點的索引 = 0, 重新計算par = (cur -1) / 2 = 0
      在這裏插入圖片描述
      在這裏插入圖片描述
      在這裏插入圖片描述
      第4次循環,需要插入的索引爲4,令cur =4,其父節點 par = (4- 1)/2 = 1,
      在這裏插入圖片描述
      在這裏插入圖片描述
      在這裏插入圖片描述
      在這裏插入圖片描述
      在這裏插入圖片描述
      在這裏插入圖片描述
      在這裏插入圖片描述

第5次循環,需要插入的索引爲5,令cur =5,其父節點 par = (5- 1)/2 = 2,
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
**** 一直循環,直到當前數組循環完畢

總結: 上面的過程可以看成是向數組中不斷插入一個元素【向大根堆中插入一個元素】,總結上面經驗得知:

  • 元素的插入索引是固定的【一定都是size】,但是插入的位置是不一定的。
    • 如果插入的數據小於等於他的父索引【(size - 1)/2】,什麼也不幹
    • 如果插入的數據大於它的父索引【(size - 1)/2】,那麼就讓這個數據不斷上浮,直到找到了應該的位置就表示插入成功了

節點上浮:當我們在向最大堆中插入一個節點時,我們必須滿足完全二叉樹的標準,那麼被插入節點的位置是固定的,而且要滿足父節點關鍵值不小於子節點關鍵值,那麼我們就需要去移動父結點和子結點的相互位置關係。

在這裏插入圖片描述

堆排序動畫

堆排序

例子1:

在這裏插入圖片描述
進行調整後,堆頂元素(array[0])爲最大值,將最大值與堆尾部元素(array[count-1])交換,並將count值減去1,則此時得到新的無序數組array[count],此時的堆被破壞;

在這裏插入圖片描述
對應到數組元素爲:
在這裏插入圖片描述
調整堆:與建堆過程類似,堆頂元素被一個比較小的值代替,所以從堆頂元素開始調整,在堆頂、堆頂的左孩子、堆頂的右孩子中找出最大的與堆頂進行交換,被交換的元素再次與他下一層的左右孩子進行比較(遞歸)調整。

在這裏插入圖片描述
然後一直重複,直到count = 0

在這裏插入圖片描述

例子2:

在這裏插入圖片描述

如果要排序:我們一般從最上面的那個元素開始。

從0開始遍歷數組:

令cur = 0, 並且cur 與 size - 1的元素交換位置,
在這裏插入圖片描述

在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述

代碼實現

大根堆進行從大到小的排序

public class TreeNode {
    // 從最後一個非葉子節點開始調整,調整爲一個大根堆
    private static void BuildMaxHeap(int[]arr){
        if (arr == null || arr.length < 2){
            return;
        }

        for (int i = arr.length/2 - 1; i > -1; i--){
            adjustMaxHeap(arr, arr.length, i);
        }


        for (int i = arr.length; i > 0; i--){
            swap(arr, i - 1, 0);
            adjustMaxHeap(arr, i - 1, 0);
        }
    }

    private static void adjustMaxHeap(int[]arr, int size, int i){
        int left = 2 * i + 1;
        int right = 2 * i + 2;
        int max = i;

        if (left < size && arr[left] > arr[max]){
            max = left;
        }

        if (right < size && arr[right] > arr[max]){
            max = right;
        }

        if (max != i){
            swap(arr, i, max);
            adjustMaxHeap(arr, size, max);
        }
    }

    private static void swap(int []arr, int i, int j){
        int t = arr[i];
        arr[i] = arr[j];
        arr[j] = t;
    }

    public static void main(String[] args) {
        int[] arr = new int[]{23,19,81,79,89,83,17,48,55,26};
        BuildMaxHeap(arr);

        System.out.println(Arrays.toString(arr));
    }
}

大根堆中可能用到的行爲


    // 從最好一個非葉子節點開始調整
    // 下移過程
    /**
     * 將父節點爲aar[i]的子樹調整爲最大堆
     * @param arr 堆數組
     * @param size 堆數組長度
     * @param index 節點索引
     */
    private static void AdjustHeap(int[] arr, int size, int index){
        if (arr == null || index < 0){
            return;
        }
        int left = 2 * index + 1;
        int right = 2 * index + 2;
        int max = index;
        if (left < size && arr[left] > arr[index]){
            max = left;
        }
        if (right < size && arr[right] > arr[index]){
            max = right;
        }

        if (max != index){
            Swap(arr, max, index);
            AdjustHeap(arr, size, max);
        }
    }


    /**
     * 根據輸入的數組構建一個最大堆
     * @param arr 堆數組
     * @param size 堆數組長度
     * @return 堆數組長度
     */
    private static int BuildMaxHeap(int arr[], int size) {
        //對每一個非葉節點開始向下進行最大堆調整
        for (int i = size / 2 - 1; i >= 0; i--)
        {
            AdjustHeap(arr, size, i);
        }
        return size;
    }

    /**
     * 向指定的最大堆中插入節點:首先在堆的最後添加一個節點,然後沿着堆樹上升,直到堆樹再次調整爲最大堆
     * @param arr
     * @param size
     * @param data
     * @return
     */
    int MaxHeapInsert(int arr[], int size,int data)
    {
        int index=size;
        while (index>0 && data>arr[(index-1)/2])
        {
            arr[index]=arr[(index-1)/2];
            index=(index-1)/2;
        }
        arr[index]=data;
        return (size+1);
    }

    /**
     * 最大堆堆頂節點的刪除:將堆樹中待刪除的節點A與堆樹中的最後一個節點B互換,然後調整節點B到合適的位置,最後從堆樹中刪除排在堆數組最後的節點A。
     * @param arr 最大堆數組
     * @param size 堆數組長度
     * @return 刪除後的堆數組長度
     */
    private static int MaxHeapDelete(int arr[], int size)
    {
        if(size<=0)return -1;
        Swap(arr, 0, size - 1);
        AdjustHeap(arr,size-1,0);//調整堆爲最大堆
        return size-1;
    }

    private static  void Swap(int arr[], int i, int j)
    {
        int temp=arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }


    public static void main(String[] args) {
        int[] arr = new int[]{23,19,81,79,89,83,17,48,55,26,16,1,46,95,10};
        BuildMaxHeap(arr, arr.length); //[95, 89, 81, 79, 26, 83, 23, 48, 55, 19, 16, 1, 46, 17, 10]

        System.out.println(Arrays.toString(arr));
    }

參考

代碼寫的很清晰

寫得非常好,但是還沒有看完

堆排序動畫

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