最小堆


一、 滿二叉樹

一個深度為k,節點個數為2^k-1的二叉樹為滿二叉樹,即一棵樹深度為k,沒有空位。

 

二、完全二叉樹

一棵深度為k有n個節點的二叉樹,對樹中節點按從上至下、從左至右的順序進行編號,如果編號為i(1<=i<=n)的節點與滿二叉樹中編號為i的節點的二叉樹中位置相同,則這棵樹為完全二叉樹。滿二叉樹是特殊的完全二叉樹。

 

三、完全二叉樹與滿二叉樹性質

四、最小堆

最小堆是一種經過排序的完全二叉樹,其中任意非終端節點數值均不大於其左子節點和右子節點的值。

如果一棵二叉樹滿足最小堆的要求,那么,堆頂(根節點)也就是整個序列的最小元素。如果從廣度優先的方式從根節點開始遍歷,可以構成序列。反過來,可以推演出序列構成二叉樹的公式為:

  •  對於序列下標為i(下標從0開始)的元素,左孩子的下標為left(i)=i*2+1,右孩子下標為right(i)=left(i)+1。

五、二叉樹調整為最小堆的方法

1)      倒序遍歷數列,因為下標在size/2之后的節點都是葉子結點,所以可以從size/2-1位置開始倒序遍歷,減少比較次數。

2)      對二叉樹中的元素挨個進行沉降處理,沉降過程為:把遍歷到的節點與左右子節點中的最小值比對,如果比最小值要大,那么和孩子節點交換數據,反之則不作處理,繼續倒序遍歷。

3)      沉降后的節點,再次沉降,直到葉子節點。

 

六、最小堆代碼實現

 1 package io.guangsoft;
 2 
 3 public class MinPriorityQueue <T extends Comparable> {
 4     //存儲堆中元素
 5     private T[] items;
 6     //記錄堆中元素個數
 7     private int total;
 8     public MinPriorityQueue(int capacity) {
 9         items = (T[])new Comparable[capacity + 1];
10         this.total = 0;
11     }
12     //獲取隊列中元素個數
13     public int size() {
14         return total;
15     }
16     //判斷隊列是否為空
17     public boolean isEmpty() {
18         return total == 0;
19     }
20     //判斷堆中索引i處的元素是否小於索引j處的元素
21     private boolean less(int i, int j) {
22         return items[i].compareTo(items[j]) < 0;
23     }
24     //交換堆中i索引和j索引處的值
25     private void swap(int i, int j) {
26         T tmp = items[i];
27         items[i] = items[j];
28         items[j] = tmp;
29     }
30     //往堆中插入一個元素
31     public void insert(T t) {
32         items[++total] = t;
33         swim(total);
34     }
35     //刪除堆中最小元素,並返回這個最小元素
36     public T delMin() {
37         T min = items[1];
38         swap(1, total);
39         total--;
40         sink(1);
41         return min;
42     }
43     //使用上浮算法,使索引k處的元素能在堆中處於一個正確的位置
44     private void swim(int k) {
45         //通過循環比較當前節點和其父節點的大小
46         while(k > 1) {
47             if(less(k, k / 2)) {
48                 swap(k, k / 2);
49             }
50             k /= 2;
51         }
52     }
53     //使用下沉算法,使索引k處的元素能在堆中處於一個正確的位置
54     private void sink(int k) {
55         //通過循環比較當前節點和其子節點中較小值
56         while(2 * k <= total) {
57             //找到子節點中的較小值
58             int min;
59             if(2 * k + 1 <= total) {
60                 if(less(2 * k, 2 * k + 1)) {
61                     min = 2 * k;
62                 } else {
63                     min = 2 * k + 1;
64                 }
65             } else {
66                 min = 2 * k;
67             }
68             //判斷當前節點和較小值的大小
69             if(less(k, min)) {
70                 break;
71             }
72             swap(k, min);
73             k = min;
74         }
75     }
76     //主類
77     public static void main(String args[]) {
78         //創建最小優先隊列
79         MinPriorityQueue<Integer> queue = new MinPriorityQueue<>(8);
80         //往隊列中存數據
81         queue.insert(1);
82         queue.insert(6);
83         queue.insert(2);
84         queue.insert(5);
85         queue.insert(7);
86         queue.insert(3);
87         queue.insert(8);
88         queue.insert(4);
89         //通過循環獲取最小優先隊列中的元素
90         while(!queue.isEmpty()) {
91             Integer min = queue.delMin();
92             System.out.print(min + ",");
93         }
94     }
95 }

七、 PriorityQueue

  PriorityQueue是一個基於優先級的無界隊列,優先級隊列的元素按照其自然順序進行排序或者根據構造隊列時提供的Comparator進行排序。其存儲結構為數組,隨着不斷向優先級隊列添加元素,其容量會自動擴容。

  • heapify方法負責把序列轉化為最小堆,即所謂的建堆。
  • siftDown(k,x)方法用於沉降,根據comparator是否為null決定比較方法。沉降方法把不滿足最小堆條件的父節點一路沉到最底部。siftDown的時間復雜度不會超出O(log2n)。
  • offer方法用於把數據入隊,priorityQueue不允許存放null數據,與ArrayList留有擴容余量不同,當數組長度達到極限時才執行擴容。新添加的節點初始位置是在整個隊列的末位,二叉樹中,它一定是葉子節點,當它比父節點小時,需要進行上浮。
  • grow(minCapacity)方法用於擴容,如果舊容量小於64,則擴容一倍+2,否則擴容1.5倍。擴容后校驗容量,容量最大支持最大整型值。
  • poll方法用來檢索並移除此隊列的頭,最小堆雖然不能保證數列的順序,但其堆頂元素始終是最小元素,而priorityQueue也只要求出隊的對象優先級最高,當出隊即堆頂元素移除后,其左孩子會變為堆頂,需要重新調整堆結構。而移除最小堆的任意葉子節點,最小堆性質不變。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM