資料結構之堆
定義
堆是一種特別的樹狀結構,我們首先來看看維基百科的上定義。
堆 (英語:Heap)是電腦科學 中的一種特別的樹狀資料結構 。若是滿足以下特性,即可稱為堆:“給定堆中任意節點 P 和 C,若 P 是 C 的母節點,那麼 P 的值會小於等於(或大於等於) C 的值”。若母節點的值恆小於等於 子節點的值,此堆稱為最小堆 (min heap);反之,若母節點的值恆大於等於 子節點的值,此堆稱為最大堆 (max heap)。在堆中最頂端的那一個節點,稱作根節點 (root node),根節點本身沒有母節點 (parent node)。
總結來說,堆是一個完全二叉樹,最多隻有兩個子節點,並且必須保證根節點是最大的值或者最小的值,所以對於一個堆而言,根節點是最大的值或者是最小的值。
儲存
因為堆是一棵完全二叉樹,所以使用陣列可以高效的儲存資料。對於使用陣列儲存的方式,有兩個性質非常關鍵,對於一個非根節點的節點i
來說,如果它的下標為k
,那麼它的父節點的下標為(k -1)/2
,子節點的下標為2 *k +1
和2 *k + 2
基本操作
- 堆化
- 插入
- 刪除
堆化
對於任意一個無序陣列而言,實現堆化的步驟如下,我們以構建一個最大堆為例:
- 找到第一個非葉子節點,對這個節點的左子樹和右子樹與節點比較,將大的元素放置到父節點的位置,直到父節點已經是最大值或者改節點已經是葉子節點。
- 依次對所有的非葉子節點進行第一步的操作
private Heap(int[] data) { int last_p = data.length - 1; //i是第一個非葉子節點 for (int i = (last_p - 1) / 2; i >= 0; i--) { heapify(data, i, last_p); } this.data = data; } private void heapify(int[] data, int start, int end) { int value = data[start]; int current_p = start; //左孩子 int left_p = 2 * current_p + 1; while (left_p <= end) { //右節點大於左節點 if (left_p < end && data[left_p] < data[left_p + 1]) { //移動位置到右節點 left_p++; } //當前的父節點已經是最大值 if (data[left_p] < value) { break; } else {//子節點上移到父節點的位置 data[current_p] = data[left_p]; current_p = left_p; left_p = current_p * 2 + 1; } } data[current_p] = value; }
插入
插入的演算法如下:.將新增的節點放在陣列的最末端,也就是陣列的最後一個位置。然後計算出父節點的位置,讓當前節點與父節點比較,如果父節點比較小,交換位置。重複上述步驟,直到父節點大於當前節點或者當前節點是根節點。
public int insert(int value) { checkSize(); int position = data.length - 1; data[position] = value; int current_p = position; int parent_p = (position - 1) / 2; while (current_p > 0) { if (data[parent_p] > value) { break; } else { data[current_p] = data[parent_p]; current_p = parent_p; parent_p = (current_p - 1) / 2; } } data[current_p] = value; return position; }
刪除
刪除的演算法如下:將陣列最後一個節點與當前需要刪除的節點替換,刪除最後一個節點,對於替換的節點來說,相當於進行了依次插入操作,不過這次是從上往下的插入。演算法與remove相同,也是比較最大的值進行替換,直到不滿足條件為止。
刪除根節點 public int remove() { int root = data[0]; int last = data[data.length - 1]; data[0] = last; data = Arrays.copyOf(data, data.length - 1); if (data.length == 0) { return root; } //從頂點開始調整 int current_p = 0; int l = current_p * 2 + 1; int value = data[current_p]; while (l < data.length) { if (l < data.length - 1 && data[l] < data[l + 1]) { l++;//右孩子大 } if (data[l] <= value) { break; } else { data[current_p] = data[l]; current_p = l; l = current_p * 2 + 1; } } data[current_p] = value; return root; }
應用
堆應用比較多的一個用處就是堆排序,對於一個數組進行堆化之後,第一個陣列是最大的值,然後交換第一個數和最後一個數,這樣最大的數就落在了最後一個數組的位置。縮小陣列,重複之前的步驟,最後就得到了一個排序好的陣列。
int[] data = new int[] {20, 40, 80, 33, 111, 47, 21, 90, -1}; HeapSort hs = new HeapSort(); hs.heap_array(data, data.length - 1); for (int i = 0; i < data.length - 1; i++) { int tmp = data[0]; data[0] = data[data.length - 1 - i]; data[data.length - 1 - i] = tmp; hs.heap_array(data, data.length - 2 - i); } System.out.println(Arrays.toString(data));