1. 程式人生 > >《程式設計珠璣》程式碼之路19:堆的概念和隱式堆實現

《程式設計珠璣》程式碼之路19:堆的概念和隱式堆實現

堆是用來表示元素集合的一種資料結構,它有兩個性質(大頂堆和小頂堆差異就一個符號,本文不詳細討論):

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;
	}

}