《程式設計珠璣》程式碼之路19:堆的概念和隱式堆實現
阿新 • • 發佈:2018-12-14
堆是用來表示元素集合的一種資料結構,它有兩個性質(大頂堆和小頂堆差異就一個符號,本文不詳細討論):
1:任何節點都小於或等於其子節點的值。
2:最多在最後一層和倒數第二層才有葉節點,而且儘可能靠左側分佈。
以上的性質,使得堆可以實現logn級別的插入和刪除操作,查詢最小值同樣也是logn。
那麼一個堆如何實現呢,單純用指標實現肯定不是一成不變的選擇,因為堆特殊的性質決定了堆是比較完全的樹,用左右指標的方式實現,儲存效率和訪問效率一定不是最高的。
我們觀察一下下面這個堆:
x[1]代表根節點,如果給每個節點這麼按層次編號的話,會發現這棵樹用陣列儲存並不會造成太大空間浪費,而且,如果從陣列從1計數(從0也可以)的話,左子樹的編號就是i*2,右子樹的編號就是i*2 + 1,也就是說我們可以很容易的用陣列把堆儲存成隱式堆。
程式碼定義如下:
int root = 1;
value(i) = x[i];
leftChild(i) = i *2;
rightChild(i) = i * 2 + 1;
parent(i) = i / 2;
numm(i) = (i < 1) || (i > n);
堆有兩個常見的操作,就是從最頂端和最低端改變一個數,改變之後除了這兩個位置,其他位置依然符合堆的定義,當改變頂端的狀態時,需要向下調整,當改變底端的狀態時需要向上調整,使得整棵樹恢復到堆的狀態。
向上調整的過程我們定義為siftup(n):n代表修改元素的下標:
程式碼如下:
int siftUp(int n){ int i = n; while (i > 1){ int pos = i / 2; if (x[pos] <= x[i]){ break; } swap(x[pos], x[i]); i = pos } return 0; }
向下調整的過程我們定義為siftdown(n),程式碼如下:
int siftDown(int n){
int i = n;
while (1){
int c = 2 * i;
if (c > n){
break;
}
if (c + 1 <= n){
if (x[c + 1] < x[c]){
c++;
}
}
if (x[i] <= x[c]){
break;
}
swap(x[i], x[c]);
i = c;
}
}