二叉堆
1.什麼是二叉堆?
二叉堆本質上是一種完全二叉樹,它分為兩個型別。
- 最大堆—最大堆的任何一個父節點的值,都大於或等於它左、右孩子節點的值。
- 最小堆—最小堆的任何一個父節點的值,都小於或等於它左、右孩子節點的值。
二叉堆的根節點叫做堆頂。
最大堆和最小堆的特點決定了:最大堆堆頂是整個堆中的最大元素;最小堆的堆頂是整個堆中的最小元素。
2.二叉堆的構建
二叉堆的構建需要依靠二叉堆的自我調整
2.1 二叉堆的自我調整
對於二叉堆,有如下幾種操作:
-
-
- 插入節點
- 刪除節點
- 構建二叉堆
-
這幾種操作都是基於堆的自我調整。所謂堆的自我調整,就是把一個不符合堆性質的完全二叉樹,調整成一個堆。
2.1.1 插入節點(以最小堆為例)
當二叉堆插入節點時,插入位置是完全二叉樹的最後一個位置。新節點與父節點比較大小,小於父節點,則新節點上浮,與父節點交換位置。依次迴圈比較,知道最小值到達堆頂位置,並且所有父節點都小於或等於它左、右孩子節點。
2.1.2 刪除節點
二叉堆刪除節點的過程與插入節點的過程正好相反,所刪除的是處於堆頂的節點。
需要注意的是,當刪除堆頂的節點後,為了繼續維持完全二叉樹的結構,我們把堆的最後一個節點臨時補到原本堆頂的位置,接下來,讓堆頂位置的節點與它的左右孩子節點進行比較,來滿足最小堆或者最大堆的特性。
2.1.3 構建二叉堆
構建二叉堆,也就是把一個無序的完全二叉樹調整為二叉堆,本質就是讓所有非葉子節點依次下沉。
首先,需要從最後一個非葉子節點開始,與它的左右孩子節點進行比較,並按照最大堆或者最小堆的特性依次完成節點的下沉。
2.2 二叉堆的程式碼實現
首先,我們要明確一點,二叉堆雖然是一個完全二叉樹,但是它的儲存方式並不是鏈式儲存,二是順序儲存。換句話說,二叉堆的所有節點都儲存在陣列中。
在陣列中,在沒有左右指標的情況下,如何定位一個父節點的左孩子和右孩子呢?
假設父節點的下標是parent,那麼它左孩子的下標就是2 * parent + 1;右孩子的下標就是2 * parent +2。
簡單的程式碼實現如下:
1 package com.algorithm.test; 2 3 import java.util.Arrays; 4 5 /** 6 * @Author Jack丶WeTa 7 * @Date 2020/7/30 11:13 8 * @Description 二叉堆的程式碼實現 9 */ 10 public class HeapTest { 11 12 /** 13 * "上浮"調整 14 * @param array 待調整的堆 15 */ 16 public static void upAdjust(int[] array){ 17 int childIndex = array.length - 1; 18 int parentIndex = (array.length - 1) / 2; 19 //temp儲存插入的葉子節點的值,用於最後的賦值 20 int temp = array[childIndex]; 21 while (childIndex > 0 && temp < array[parentIndex]) { 22 //無須真正交換,單向賦值即可 23 array[childIndex] = array[parentIndex]; 24 childIndex = parentIndex; 25 parentIndex = (parentIndex - 1) / 2; 26 } 27 array[childIndex] = temp; 28 } 29 30 /** 31 * "下稱調整" 32 * @param array 待調整的堆 33 * @param parentIndex 要“下稱”的父節點 34 * @param length 堆的有效大小 35 */ 36 public static void downAdjust(int[] array, int parentIndex, int length){ 37 //temp儲存父節點的值,用於最後賦值 38 int temp = array[parentIndex]; 39 int childIndex = 2 * parentIndex + 1; 40 while (childIndex < length) { 41 //如果有右孩子,且右孩子小於左孩子的值則定位到右孩子 42 if (childIndex + 1 < length && array[childIndex+1] < array[childIndex]) 43 childIndex++; 44 //如果父節點小於任何一個孩子的值,則直接跳出 45 if (temp < array[childIndex]) 46 break; 47 48 //無須真正交換,單向賦值即可 49 array[parentIndex] = array[childIndex]; 50 parentIndex = childIndex; 51 childIndex = 2 * childIndex + 1; 52 } 53 array[parentIndex] = temp; 54 } 55 56 /** 57 * 構建堆 58 * @param array 待調整的堆 59 */ 60 public static void buildHeap(int[] array){ 61 //從最後一個非葉子節點開始,依次做“下沉”調整 62 for (int i = (array.length - 2) / 2; i >= 0; i--){ 63 downAdjust(array, i, array.length); 64 } 65 } 66 67 public static void main(String[] args) { 68 int[] array = new int[]{1,3,2,6,5,7,8,9,10,0}; 69 upAdjust(array); 70 System.out.println(Arrays.toString(array)); 71 72 array = new int[]{7,1,3,10,5,2,8,9,6}; 73 buildHeap(array); 74 System.out.println(Arrays.toString(array)); 75 } 76 }
堆的插入操作是單一節點的“上浮”,堆的刪除操作是單一節點的“下沉”,這兩個操作的平均交換次數都是堆高度的一半,所以時間複雜度是0(logn),至於堆的構建,則是O(n)。