背景
Heap 可以用來實現優先級隊列,也可以用來做堆排序,本文簡單的做個介紹。
Heap
規則
- 是一個完全二叉樹,隱含的意思是:他是平衡的、使用數組進行存儲也是連續的。
- 給定的任意節點,該節點小於等於其父親節點,大於他們的孩子節點。
基礎知識
對於一個完全二叉樹,如果將其存儲到數組中,給定父節點的索引為:x,則:
- left child's index is:2*x + 1。
- right child's index is:2*x + 2。
- root's index is:0.
說明:上面的公式很容易自己推到出來,有興趣的朋友可以推到一下,這樣就不用記住這個特性了。
圖示

存儲到數組的順序為:先存儲第一層,然后是第二層,直到第 N 層。
操作
添加和刪除后還必須保證 Heap 滿足規則。
添加
添加前

添加 6

先將 6 添加到完全樹的下一個節點,然后沿着祖先路徑,將其插入到合適的節點(不一定是根節點)。
代碼
1 public void Insert(T item) 2 { 3 if (this.IsFull()) 4 { 5 throw new InvalidOperationException("容量已滿,不能插入!"); 6 } 7 8 _items[_length++] = item; 9 this.MoveUp(_length - 1); 10 }
結果

刪除最大值
接着上面的例子執行刪除
先將刪除根節點(6),再將完全樹最后的節點(2)直接移動到根節點。

接着將 2 向下插入到合適的節點,比如:5 > 4 && 5 > 2,因此結果是:

代碼
1 public T Remove() 2 { 3 if (this.IsEmpty()) 4 { 5 throw new InvalidOperationException("容量已空,不能刪除!"); 6 } 7 8 var result = _items[0]; 9 _items[0] = _items[--_length]; 10 11 this.MoveDown(0); 12 13 return result; 14 }
完整代碼
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 7 namespace DataStuctureStudy.Heaps 8 { 9 class HeapTest 10 { 11 public static void Test() 12 { 13 var heap = new Heap<int>(10); 14 heap.Insert(1); 15 heap.Insert(2); 16 heap.Insert(3); 17 heap.Insert(4); 18 heap.Insert(5); 19 heap.Insert(6); 20 heap.Display(); 21 heap.Remove(); 22 heap.Display(); 23 } 24 25 class Heap<T> 26 where T : IComparable<T> 27 { 28 private T[] _items; 29 private int _length; 30 31 public Heap(int size) 32 { 33 _items = new T[size]; 34 } 35 36 public void Display() 37 { 38 Console.WriteLine("數組表示"); 39 Console.Write("["); 40 for (var i = 0; i < _items.Length; i++) 41 { 42 if (i < _length) 43 { 44 Console.Write(_items[i]); 45 } 46 else 47 { 48 Console.Write('-'); 49 } 50 } 51 Console.WriteLine("]"); 52 Console.WriteLine(); 53 54 Console.WriteLine("樹形表示"); 55 var row = 0; 56 var column = 0; 57 var level = (int)Math.Ceiling(Math.Log(_length + 1, 2)); 58 var width = (int)Math.Pow(2, level); 59 for (var i = 0; i < _length; i++) 60 { 61 this.Display(_items[i], width, row, column); 62 63 if ((i + 1) == Math.Pow(2, row + 1) - 1) 64 { 65 row++; 66 column = 0; 67 Console.WriteLine(); 68 } 69 else 70 { 71 column++; 72 if (i == _length - 1) 73 { 74 Console.WriteLine(); 75 } 76 } 77 } 78 79 Console.WriteLine(); 80 } 81 82 private void Display(T item, int width, int row, int column) 83 { 84 var step = (int)((width * 3) / Math.Pow(2, row)); 85 var itemLength = item.ToString().Length; 86 Console.Write(item.ToString().PadLeft((step + itemLength) / 2).PadRight(step)); 87 } 88 89 public void Insert(T item) 90 { 91 if (this.IsFull()) 92 { 93 throw new InvalidOperationException("容量已滿,不能插入!"); 94 } 95 96 _items[_length++] = item; 97 this.MoveUp(_length - 1); 98 } 99 100 private void MoveUp(int index) 101 { 102 var bottom = _items[index]; 103 var current = index; 104 105 while (current > 0) 106 { 107 var parent = (current - 1) / 2; 108 if (_items[parent].CompareTo(bottom) > 0) 109 { 110 break; 111 } 112 113 _items[current] = _items[parent]; 114 current = parent; 115 } 116 117 _items[current] = bottom; 118 } 119 120 public T Remove() 121 { 122 if (this.IsEmpty()) 123 { 124 throw new InvalidOperationException("容量已空,不能刪除!"); 125 } 126 127 var result = _items[0]; 128 _items[0] = _items[--_length]; 129 130 this.MoveDown(0); 131 132 return result; 133 } 134 135 private void MoveDown(int index) 136 { 137 var top = _items[index]; 138 var current = index; 139 140 while (current < _length) 141 { 142 var large = 0; 143 var left = 2 * current + 1; 144 var right = left + 1; 145 146 if (left < _length && right < _length) 147 { 148 if (_items[left].CompareTo(_items[right]) >= 0) 149 { 150 large = left; 151 } 152 else 153 { 154 large = right; 155 } 156 } 157 else if (left < _length) 158 { 159 large = left; 160 } 161 else 162 { 163 break; 164 } 165 166 if (_items[large].CompareTo(top) <= 0) 167 { 168 break; 169 } 170 171 _items[current] = _items[large]; 172 current = large; 173 } 174 175 _items[current] = top; 176 } 177 178 public bool IsFull() 179 { 180 return _length == _items.Length; 181 } 182 183 public bool IsEmpty() 184 { 185 return _length == 0; 186 } 187 } 188 } 189 }
備注
下篇簡單的介紹一下堆排序。
