心中有堆

博客書寫不易,您的點贊收藏是我前進的動力,千萬別忘記點贊、 收**藏 ^ _ ^ !

  1. 心中有堆 https://blog.csdn.net/luo_boke/article/details/106928990
  2. 心中有樹——基礎 https://blog.csdn.net/luo_boke/article/details/106980011
  3. 心中有棧 https://blog.csdn.net/luo_boke/article/details/106982563

堆是一顆完全二叉樹,是一種經過排序的樹形數據結構,它滿足如下性質:

  1. 堆序性:任一結點值均小於(或大於)它的所有後代結點值,最小值結點(或最大值結點)在堆的根上。
  2. 結構性:堆總是一棵完全二叉樹,即除了最底層,其他層的結點都被元素填滿,且最底層儘可能地從左到右填入。

二叉樹又是啥?請查看我的另一篇博文《心中有樹——基礎》

小根堆與大根堆
小根堆:結點值小於後代結點值的堆,也叫最小堆,圖左
大根堆:結點值大於後代結點值的堆,也叫最大堆,圖右
在這裏插入圖片描述

二叉堆
二叉堆是一種特殊的堆,即每個結點的子結點不超過2個。堆排序就是使用的二叉堆。

堆的存儲
一般使用線性數據結構(如數組)存儲堆:
在這裏插入圖片描述

  • 根結點存儲在第0個位置
  • 結點i的左孩子存儲在2*i+1的位置
  • 結點i的右孩子存儲在2*i+2的位置

堆構建過程

1)根據子結點推父結點:(n-1)/2
2)根據父結點推子結點:左子結點(2n+1),右子結點(2n+2)
索引由0開始計數

我們以9、12、5、24、0、1、99、3、10、7 這10個數來構建大根堆如下,

  1. 首先我們將現在的無序序列看成一個堆結構,一個沒有規則的二叉樹,將序列裏的值按照從上往下,從左到右依次填充到二叉樹中。
    在這裏插入圖片描述
  2. 從最後一個葉子7遍歷父結點,如果父<左右子結點最大值,則父結點值與最大子結點交換值。發現7和10都小於35,切換至99和3的父結點,發現99>24,交換24與99的位置。
    在這裏插入圖片描述
  3. 繼續比較0和1的父結點,因0<5,1<5,往下走比較99和35的父結點,最大子結點99>12,交換位置。交換後,此時需要對12這個父結點進行排序,發現24>12,此時需要交換12和24的位置。
    在這裏插入圖片描述
    在這裏插入圖片描述
  4. 繼續對父結點9進行遍歷操作,需要和99換位置,同理9需要最大的子結點35換位置。
    在這裏插入圖片描述
  5. 最終獲得了大堆根
    在這裏插入圖片描述
    構建代碼

    /***
     * 構建大根堆
     * @param array  數據源
     */
    private void buildHeap(int[] array) {
        //從右向左,從下到上依次遍歷父結點,建立大根堆,時間複雜度:O(n*log2n)
        for (int i = (array.length - 1 - 1) / 2; i >= 0; i--) {
            adjust(array, i, array.length - 1);
        }
    }
    
    /**
     * 將指定堆構建成大堆根函數
     * 邏輯
     * 1. 如果起始索引無子結點,則跳出該方法
     * 2. 如果只有一個左子結點,進行大小比較並置換值
     * 3. 如果有兩個子結點,選擇最大值與父結點比較,然後置換其位置。
     * 如果子結點大於父結點,置換完成後,遞歸進行同樣操作,其子結點索引即是函數的start值
     *
     * @param array 源數組
     * @param start 起始索引
     * @param end   結尾索引
     */
    public void adjust(int[] array, int start, int end) {
        // 左子結點的位置
        int leftIndex = 2 * start + 1;
        if (leftIndex == end) {
            //只有一個左結點,進行比較並置換值
            if (array[leftIndex] > array[start]) {
                int temp = array[leftIndex];
                array[leftIndex] = array[start];
                array[start] = temp;
            }
        } else if (leftIndex < end) {
            //有兩個子結點
            int temp = array[leftIndex];
            int tempIndex = leftIndex;
            if (array[leftIndex + 1] > array[leftIndex]) {
                temp = array[leftIndex + 1];
                tempIndex = leftIndex + 1;
            }
            if (temp > array[start]) {
                array[tempIndex] = array[start];
                array[start] = temp;
            }
            adjust(array, tempIndex, end);
        }
    }

堆排序過程

上面我們將大根堆構建好了,現在我們對堆進行排序。其構建思想是:

  1. 根據堆的特點進行編寫,先將一組擁有n個元素的數據構建成大根堆或者小根堆(我按照大根堆進行介紹,小根堆是一樣的思想)。
  2. 再將根結點上的數和堆最後一位數據進行互換,此時,第n位的數就是整個序列中最大的數。
  3. 然後再將前n-1爲元素進行構建形成大根堆,再將根結點與第n-1位數據進行互換,得到第二大數據,此時倒數兩個數據無疑是有序的。
  4. 然後將前n-2個數據構建成大根堆,依次循環直到剩下一位元素,則說明第一位後面的數字都是有序的,並且比第一位數大,此時排序完成。
  • 1)將99和7替換,對排除99的堆進行重新構建大根堆
    在這裏插入圖片描述
  • 2)替換35和9的位置,對剩下的8個數進行重新構建大堆根
    在這裏插入圖片描述
  • 3)24與3交換位置,對剩餘的7個數重新構建大根堆
    在這裏插入圖片描述
  • 4)後續過程同理,最終得到經過排序完成的堆 0、1、3、5、7、9、10、12、24、35、99
    在這裏插入圖片描述

排序代碼

    /**
     * 堆排序
     *
     * @param array 源數組
     */
    public void heapSort(int[] array) {
        buildHeap(array);
        int tmp;
        //要與root結點置換位置元素的索引
        int end = array.length - 1;
        //n個結點只用構建排序n-1次,最後只有1個元素不用在排序
        for (int i = array.length - 1; i > 0; i--) {
            tmp = array[0];
            array[0] = array[end];
            array[end] = tmp;

            end--;
            //頭尾置換後,將堆重新構建爲大堆根,置換尾部大元素不參加構建
            //因爲除了root結點,其他都是由大到小有序的,所以再次構建大根堆時,不用在進行adjust()前的那個循環
            adjust(array, 0, end);
        }
    }

堆添加元素

添加元素時,新元素被添加到數組末尾,但是添加元素後,堆的性質可能會被破壞,需要向上調整堆結果。如給大堆根堆添加元素100。
在這裏插入圖片描述
此時此時堆的結構被破壞,需要從下往上進行調整。因100大於0,5,99,則新的大堆根爲
在這裏插入圖片描述
元素添加代碼

    /**
     * 在 array 是大堆根的前提下添加元素然後重構大堆根
     *
     * @param array 大堆根數組
     * @param value 添加的元素值
     */
    private void addHeap(int[] array, int value) {
        int[] arr = new int[array.length + 1];
        System.arraycopy(array, 0, arr, 0, array.length);
        arr[arr.length - 1] = value;
        int currentIndex = arr.length - 1;
        int parentIndex = (arr.length - 1) / 2;
        while (parentIndex >= 0) {
            if (value > arr[parentIndex]) {
                int temp = arr[parentIndex];
                arr[parentIndex] = value;
                arr[currentIndex] = temp;

                //如果最後一個元素的父結點還有父結點需要繼續進行對比
                currentIndex = parentIndex;
                parentIndex = (currentIndex - 1) / 2;
            } else {
                break;
            }
        }
    }

堆刪除元素

堆刪除元素都是從結點刪除,然後以這個結點爲root結點的數組的最後一個元素移動到根結點的位置,並向下調整堆結構,直至重新符合堆序性。如我們刪除結點35,將7移到35的位置
在這裏插入圖片描述
將7與其子結點逐個比較,直至符合大根堆規則
在這裏插入圖片描述

刪除元素代碼

/**
     * 在 array 是大堆根的前提下刪除元素然後重構大堆根
     *
     * @param array 大堆根數組
     * @param deleteIndex 刪除元素的索引
     */
    private int[] deleteHeap(int[] array, int deleteIndex) {
        array[deleteIndex] = array[array.length - 1];
        int[] arr = new int[array.length - 1];
        System.arraycopy(array, 0, arr, 0, array.length - 1);
        int lefeIndex = 2 * deleteIndex + 1;
        while (lefeIndex >= arr.length - 1) {
            int maxIndex = lefeIndex;
            if (arr.length - 1 > lefeIndex) {
                if (arr[lefeIndex + 1] > arr[lefeIndex]) {
                    maxIndex = lefeIndex + 1;
                }
            }

            if (arr[maxIndex] > arr[deleteIndex]) {
                int temp = arr[maxIndex];
                arr[maxIndex] = arr[deleteIndex];
                arr[deleteIndex] = temp;
                lefeIndex = 2 * maxIndex + 1;
            } else {
                break;
            }
        }
        return arr;
    }

博客書寫不易,您的點贊收藏是我前進的動力,千萬別忘記點贊、 收**藏 ^ _ ^ !

相關鏈接

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