堆的一個經典的實現是徹底二叉樹(complete binary tree),這樣實現的堆稱爲二叉堆(binary heap)。數組
這裏來講明一下滿二叉樹的概念與徹底二叉樹的概念以及完滿二叉樹概念。數據結構
堆的特性:ide
必須是徹底二叉樹函數
任一結點的值是其子樹全部結點的最大值或最小值動畫
最大值時,稱爲「最大堆」,也稱大頂堆;spa
最小值時,稱爲「最小堆」,也稱小頂堆。3d
只要謹記堆的定義特性,實現起來實際上是很容易的。code
特性1. 維持徹底二叉樹 blog
特性2. 子類數字老是大於父類數字 排序
1public class MinHeap <E extends Comparable<E>> { 2 private Array<E> data; 3 4 public MinHeap(int capacity){ 5 data = new Array<>(capacity); 6 } 7 8 public MinHeap(){ 9 data = new Array<>(); 10 } 11 12 // 返回堆中的元素個數 13 public int size(){ 14 return data.getSize(); 15 } 16 17 // 返回一個布爾值, 表示堆中是否爲空 18 public boolean isEmpty(){ 19 return data.isEmpty(); 20 } 21 22 // 返回徹底二叉樹的數組表示中,一個索引所表示的元素的父親節點的索引 23 private int parent(int index){ 24 return (index - 1) / 2; 25 } 26 27 // 返回徹底二叉樹的數組表示中,一個索引所表示的元素的左孩子節點的索引 28 private int leftChild(int index){ 29 return index * 2 + 1; 30 } 31 32 // 返回徹底二叉樹的數組表示中,一個索引所表示的元素的右孩子節點的索引 33 private int rightChild(int index){ 34 return index * 2 + 2; 35 } 36}
最小堆的插入(ADD)
假設現有元素 5 須要插入,爲了維持徹底二叉樹的特性,新插入的元素必定是放在結點 6 的右子樹;同時爲了知足任一結點的值要小於左右子樹的值這 一特性,新插入的元素要和其父結點做比較,若是比父結點小,就要把父結點拉下來頂替當前結點的位置,本身則依次不斷向上尋找,找到比本身大的父結點就拉下來,直到沒有符合條件的值爲止。
動畫講解
在這裏先將元素 5 插入到末尾,即放在結點 6 的右子樹。
而後與父類比較, 6 > 5 ,父類數字大於子類數字,子類與父類交換。
重複此操做,直到不發生替換。
Show me the code:
添加一個輔助函數,用來交換傳入的索引兩個位置的元素值
1/** 2 * 交換傳入的索引兩個位置的元素值 3 * 4 * @param i 5 * @param j 6 */ 7 public void swap(int i, int j) { 8 if (i < 0 || i >= size || j < 0 || j >= size) 9 throw new IllegalArgumentException("Index is illegal."); 10 11 E temp = data[i]; 12 data[i] = data[j]; 13 data[j] = temp; 14 }
數組中添加交換兩元素位置的方法,注意下面代碼中註釋的描述特性位置。
1 /** 2 * 堆中添加元素方法。 3 * 4 * @param e 5 */ 6 public void add(E e) { 7 //特性1:新插入的元素首先放在數組最後,保持徹底二叉樹的特性 8 data.addLast(e); 9 siftUp(data.getSize() - 1); 10 } 11 12 /** 13 * index 爲i位置元素上浮。 14 * 15 * @param i 16 */ 17 private void siftUp(int i) { 18 //特性2:比較插入值和其父結點的大小關係,小於父結點則用父結點替換當前值,index位置上升爲父結點 19 // 當上浮元素大於父親,繼續上浮。而且不能上浮到0之上 20 // 直到i 等於 0 或 比 父親節點小了。 21 while (i > 0 && data.get(i).compareTo(data.get(parent(i))) > 0) { 22 // 數組Array中添加方法swap 23 data.swap(i, parent(i)); 24 i = parent(i); // 這句話讓i來到新的位置,使得循環能夠查看新的位置是否還要大。 25 } 26 }
最小堆的刪除(DELETE)
核心點:將最後一個元素填充到堆頂,而後不斷的下沉這個元素。
假設要從節點 1 ,也能夠稱爲取出節點 1 ,爲了維持徹底二叉樹的特性 ,咱們將最後一個元素 6 去替代這個 1 ;而後比較 1 和其子樹的大小關係,若是比左右子樹大(若是存在的話),就要從左右子樹中找一個較小的值替換它,而它能本身就要跑到對應子樹的位置,再次循環這種操做,直到沒有子樹比它小。
經過這樣的操做,堆依然是堆,總結一下:
找到要刪除的節點(取出的節點)在數組中的位置
用數組中最後一個元素替代這個位置的元素
當前位置和其左右子樹比較,保證符合最小堆的節點間規則
刪除最後一個元素
Show me the code:
1 public E findMin() { 2 return data.get(0); 3 } 4 5 public E extractMin() { 6 7 E ret = findMin(); 8 9 data.swap(0, data.getSize() - 1); // 0位置元素和最後一個元素互換。 10 data.removeLast(); // 刪除此時的最後一個元素(最小值) 11 siftDown(0); // 對於0處進行siftDown操做 12 13 return ret; 14 } 15 16 /** 17 * k位置元素下移 18 * 19 * @param k 20 */ 21 private void siftDown(int k) { 22 23 while(leftChild(k) < data.getSize()){ 24 int j = leftChild(k); // 在此輪循環中,data[k]和data[j]交換位置 25 if( j + 1 < data.getSize() && 26 data.get(j + 1).compareTo(data.get(j)) < 0 ) 27 j ++; 28 // data[j] 是 leftChild 和 rightChild 中的最小值 29 30 if(data.get(k).compareTo(data.get(j)) >= 0 ) 31 break; 32 33 data.swap(k, j); 34 k = j; 35 } 36 }
對於有 n 個節點的堆來講,其高度 d = log2n + 1
。 根爲第 0 層,則第 i 層結點個數爲 2i,
考慮一個元素在堆中向下移動的距離。
大約一半的結點深度爲 d-1 ,不移動(葉)。
四分之一的結點深度爲 d-2 ,而它們至多能向下移動一層。
樹中每向上一層,結點的數目爲前一層的一半,而子樹高度加一
堆有logn
層深,因此插入刪除的平均時間和最差時間都是O(logN)
普通隊列是一種先進先出的數據結構,先放進隊列的元素取值時優先被取出來。而優先隊列是一種具備最高優先級元素先出的數據結構,好比每次取值都取最大的元素。
優先隊列支持下面的操做:
a. 找出優先級最高的元素(最大或最小元素);
b. 刪除一個具備最高優先級的元素;
c. 添加一個元素到集合中。
1public class PriorityQueue<E extends Comparable<E>> implements Queue<E> { 2 3 private MaxHeap<E> maxHeap; 4 5 public PriorityQueue(){ 6 maxHeap = new MaxHeap<>(); 7 } 8 9 @Override 10 public int getSize(){ 11 return maxHeap.size(); 12 } 13 14 @Override 15 public boolean isEmpty(){ 16 return maxHeap.isEmpty(); 17 } 18 19 @Override 20 public E getFront(){ 21 return maxHeap.findMax(); 22 } 23 24 @Override 25 public void enqueue(E e){ 26 maxHeap.add(e); 27 } 28 29 @Override 30 public E dequeue(){ 31 return maxHeap.extractMax(); 32 } 33}
理解了優先隊列,堆排序的邏輯十分簡單。