數據結構:堆排序
走進堆排序
什么是堆
堆(英語:Heap)是計算機科學中的一種特別的樹狀數據結構。堆實質是一顆完全二叉樹。它就長下面這樣:

正是由於他在形式上是一個完全二叉樹,我們也將其可以用數組來存儲。其中Kn的子元素的下標是是K(n*2)和K(n*2+1)。

但是堆是一種特殊完全二叉樹,它的元素遵循兩種規律,每一個節點的值要么大於其所有子節點,要么小於其所有子節點,類似下面這樣:

根據節點與其子節點的關系,可以分為大頂堆(右)和小頂堆(左),從中不難發現,大頂堆從上往下依次鍵值減小,小頂堆從上向下鍵值增大。其實在這里,我們就可以發現堆的一大應用,即因為其結構的特殊性,處在第一個位置上的元素一定是最大或者最小的。
什么是堆排序
☐ 對一組待排序記錄的關鍵字,首先把它們按堆的定義建成小(大)頂堆
☐ 然后輸出堆頂的最小(大)關鍵字所代表的記錄,再對剩余的關鍵字建堆,以便得到次小(大)的關鍵字
☐ 如此反復進行,直到全部關鍵字排成有序序列為止。
說白了,堆排序就是不斷取走堆中最大元素或者最小元素(即第一個元素),然后對剩下元素進行建堆,再重復的一個過程。
堆的兩條重要性質:
1.在一個二叉堆中,位置為K的節點的父節點的位置為|_K/2_|,而它的兩個子節點位置為2K和2K+1
2.一顆大小為N的完全二叉樹的高度為|_LgN_|
圖示堆排序
堆排序實質是對一組關鍵字進行建堆的過程,這一過程可稱為堆的有序化。我們此處將的是大頂堆,小頂堆的道理是相同的。
插入新的元素進行有序化
如下圖所示,我們的目標是大頂堆,然而新插入的元素值為9,大於其父元素,所以我們需要進行有序化:

我們將子元素設為X(圖中值為9),我們需要交換它和它的父節點(值為6)來修復堆。但是可能交換后X還是很大(大於值為8.5的元素),所以我們需要X一次次的它的祖先節點進行比較,直到找打它最合適的位置。根據二叉堆的性質,我們不難發現只要記住位置為K的節點的父節點為 |_K/2_|,一切都很簡單了。

這就是一種上浮操作,即新插入的元素進行上浮,就要需要一次次的它的祖先節點進行比較,直到找打它最合適的位置。
上浮操作核心代碼如下:
private void swim(int k) {
while (k > 1 && less(k/2,k)) {
exch(k/2, k);
k = k/2;
}
}
刪除堆頂元素后進行有序化
在堆排序中,我們是如何處理刪除堆頂元素的呢?我們首先將堆頂元素與序列末端元素進行交換,然后刪除末端元素。這是堆頂元素肯定不是堆中最大的元素,所以他需要找到他合適的位置。

為值為6的元素找到其合適位置,它需要和它的子節點中較大的節點進行交換來修復堆,但是可能交換后X還是很小,所以我們需要X一次次的它的子節點進行比較並交換,直到找打它最合適的位置。

這是一種下沉操作,即被交換后的元素,需要一次次的它的子節點進行比較並交換,直到找打它最合適的位置。
下沉操作核心代碼如下:
private void sink(int k) {
while (2 * k <= N) {
int j = 2 * k;
if (j < N && less(j, j + 1)) {
j++;
}
if (!less(k, j)) {
break;
}
exch(k, j);
k = j;
}
}
到這里位置,我們已經學會了在堆中插入一個新元素和刪除堆頂元素的操作,這已然是堆排序的核心內容了。
Java版本實現代碼
class MaxPQ<Key extends Comparable<Key>> {
private Key[] pq;
private int N = 0;
public MaxPQ(int maxN) {
pq = (Key[]) new Comparable[maxN + 1];
}
public static void main(String[] args) {
MaxPQ<Integer> maxPQ = new MaxPQ<Integer>(10);
for(int i = 0; i < 10; i++)
{
maxPQ.insert((int)(Math.random() * 10 + 1));
}
while(!maxPQ.isEmpty())
{
System.out.println(maxPQ.delMax());
}
}
public int size() {
return N;
}
public boolean isEmpty() {
return N == 0;
}
public void insert(Key v) {
pq[++N] = v;
swim(N);
}
public Key delMax() {
Key max = pq[1];
exch(1,N--);
pq[N + 1] = null;
sink(1);
return max;
}
private boolean less(int i, int j) {
return pq[i].compareTo(pq[j]) < 0;
}
private void exch(int i, int j) {
Key temp = pq[i];
pq[i] = pq[j];
pq[j] = temp;
}
private void sink(int k) {
while (2 * k <= N) {
int j = 2 * k;
if (j < N && less(j, j + 1)) {
j++;
}
if (!less(k, j)) {
break;
}
exch(k, j);
k = j;
}
}
private void swim(int k) {
while (k > 1 && less(k/2,k)) {
exch(k/2, k);
k = k/2;
}
}
}
