二叉堆及堆排序

moment發表於2019-01-21

什麼是二叉堆

二叉堆是一種特殊的堆,二叉堆是完全二元樹(二叉樹)或者是近似完全二元樹(二叉樹)。

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

最大堆:父結點的鍵值總是大於或等於任何一個子節點的鍵值;

最小堆:父結點的鍵值總是小於或等於任何一個子節點的鍵值。

對於二叉堆,如下有幾種操作

插入節點

二叉堆的節點插入,插入位置是完全二叉樹的最後一個位置。比如我們插入一個新節點,值是 0。

MacDown logo

這時候,我們讓節點0的它的父節點5做比較,如果0小於5,則讓新節點“上浮”,和父節點交換位置。

MacDown logo

繼續用節點0和父節點3做比較,如果0小於3,則讓新節點繼續“上浮”。

MacDown logo

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

MacDown logo

刪除節點

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

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]複製程式碼

堆排序

堆排序演算法的步驟:

1、把無序陣列構建成二叉堆。

2、迴圈刪除堆頂元素,移到集合尾部,調節堆產生新的堆頂。

 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);

}
}複製程式碼

來源:https://juejin.im/post/5c4566ffe51d4551d04534d7#comment

相關文章