堆排序
堆排序
堆
(二叉)堆是一種具有特殊性質的二叉樹。要麼所有結點都大於它的左右孩子結點,要麼所有結點都小於它的左右孩子結點。前者被稱為大根堆,後者被稱為小根堆。如圖:
從上到下,左到右編號序號後,我們可以用一個數組來表示這種結構(箭頭指向的是孩子結點),即:
如果從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");
}