1. 程式人生 > 實用技巧 >二叉堆

二叉堆

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