1. 程式人生 > 實用技巧 >堆排序

堆排序

堆排序

(二叉)堆是一種具有特殊性質的二叉樹。要麼所有結點都大於它的左右孩子結點,要麼所有結點都小於它的左右孩子結點。前者被稱為大根堆,後者被稱為小根堆。如圖:

從上到下,左到右編號序號後,我們可以用一個數組來表示這種結構(箭頭指向的是孩子結點),即:

如果從0開始編號的話,可以發現,如果一個結點的下標為[i],則它的左孩子和右孩子的下標分別為:[2i+1][2i+2]

把該陣列記為 A大根堆的性質可以歸納為:

\[A[i]\geq max(A[2i+1],A[2i+2]) \]

容易得出,小根堆的性質是:

\[A[i]\leq min(A[2i+1],A[2i+2]) \]

維護堆的性質

現在考慮這樣的一種情況。比如說,我把剛剛的那個大根堆的根16換成4,這就違背了大根堆的性質,它就不是一個大根堆了,像這樣:

而我們要讓它(以4為根結點的樹)保持大根堆的性質,所以,我們要讓這個4這個節點,在堆裡面逐級下降。具體來講是,比較當前結點和它的孩子結點的值,若孩子結點的值比它要大,則和最大的孩子結點做交換,否則不交換。交換之後,由於比較小的那個結點往下走了,所以可能會導致下面的子樹違背了大根堆的性質,所以要對子樹遞迴的進行這個操作,直到那個以指定的結點為根的子樹滿足大根堆的性質。

顯然,我們很容易看到,經過調整之後,這個樹依然是一個大根堆。但實際上,並不是所有情況都是如此的,因為這裡替換的是大根堆堆頂的一個元素,並且從它開始進行調整。如果原本就不是一個大根堆,那經過一次調整後的結果就不一定是大根堆

把這過程寫成一個函式就是:

//維護最大堆的性質
void max_heapify(int *A, int i, int size) {
	int L = i * 2 + 1;  //左孩子下標(預設下標從0開始)
	int R = i * 2 + 2; //右孩子下標
	int largest = i;   //記住i和i的左右孩子三者中最大的那個的下標
	if (L < size && A[L] > A[largest]) {
		largest = L;
	}
	if (R < size && A[R] > A[largest]) {
		largest = R;
	}
	//如果違背了最大堆的性質,則交換
	if (largest != i) {
		swap(A[largest], A[i]);
		//遞迴進行調整
		max_heapify(A, largest, size);
	}
}

建堆

我們怎麼從把原本雜亂無章的資料建成一個堆呢?

上面可以看到,如果某個結點為根的子樹都滿足大根堆的性質的話,那麼從這個根開始調整,就可以讓整棵樹都滿足大根堆的性質。那我們就可以從最後一個非葉子結點(因為葉子結點本身就是一個堆)開始調整,自底向上,從右往左地把一棵樹構建成一個大根堆。像這樣:

至此,一個大根堆就建成了!

把這個過程寫成函式就是:

//建立大根堆
void build_max_heap(int *A, int size) {
	for (int i = (size - 1) / 2; i >= 0; i--) {
		max_heapify(A, i, size);
	}
}

堆排序

對一批無規則的資料,我們要先對他進行建堆的操作。

然後,我們可以每次都取大根堆堆頂的元素出來,把它和最後一個元素交換,然後調整堆。一直重複這樣的操作,就可以把進行排序操作。

這個排序的過程像這樣。

重複以上操作,我們就可以得到一個排好序的陣列

這個過程寫成函式:

void heap_sort(int *A, int size) {
	build_max_heap(A, size); //先建堆
	for (int i = size - 1; i > 0; i--) {
		swap(A[0], A[i]);   //交換堆頂和堆尾的元素
		max_heapify(A, 0, i);  //調整堆
	}
}

全部程式碼

#include<iostream>
#include<cstdlib>
#include<ctime>
using namespace std;

void swap(int &a, int &b) {
	int t = a;
	a = b;
	b = t;
}

//維護最大堆的性質
void max_heapify(int *A, int i, int size) {
	int L = i * 2 + 1;  //左孩子下標(預設下標從0開始)
	int R = i * 2 + 2; //右孩子下標
	int largest = i;   //記住i和i的左右孩子三者中最大的那個的下標
	if (L < size && A[L] > A[largest]) {
		largest = L;
	}
	if (R < size && A[R] > A[largest]) {
		largest = R;
	}
	//如果違背了最大堆的性質,則交換
	if (largest != i) {
		swap(A[largest], A[i]);
		//遞迴進行調整
		max_heapify(A, largest, size);
	}
}

//建立大頂堆
void build_max_heap(int *A, int size) {
	for (int i = (size - 1) / 2; i >= 0; i--) {
		max_heapify(A, i, size);
	}
}

void heap_sort(int *A, int size) {
	build_max_heap(A, size); //先建堆
	for (int i = size - 1; i > 0; i--) {
		swap(A[0], A[i]);   //交換堆頂和堆尾的元素
		max_heapify(A, 0, i);  //調整堆
	}
}


int main() {
	srand(time(NULL));
	int a[10];
	for (int i = 0; i < 10; i++) {
		a[i] = rand() % 10;
		cout << a[i] << " ";
	}
	cout << endl;
	heap_sort(a, 10);
	for (int i = 0; i < 10; i++) {
		cout << a[i] << " ";
	}
	cout << endl;
	system("pause");
}