堆排序
HeapSort,這是相當常用的一種排序算法。原因是它的時間複雜度相當穩定,且相當高效。無論是最好情況還是最壞情況,時間複雜度都能較穩定地保持在O(nlogn)。
之所以這麼高效,是因爲它建立在一種特殊的數據結構上,即,堆。
什麼是堆呢?其實堆就是一個完全二叉樹。
因爲是一個完全二叉樹,所以可以方便的用順序存儲的方式存下來。再根據完全二叉樹的特殊性質,可以很完美的實現堆的adjust。
關鍵的性質:
對於一棵有 n 個結點的完全二叉樹,其節點按層序編號,每層從左到右,則對於任一結點 i 有:
- 如果 i 等於1,則節點 i 是二叉樹的根,無雙親。如果 i 大於1,則該節點的雙親是結點 i / 2。
- 如果 2 * i 大於n,則該節點無左孩子,否則其左孩子是結點 2 * i 。
- 如果 2 * i + 1 大於n,則該節點無右孩子,否則其右孩子是結點 2 * i + 1 。
堆排序的關鍵在於adjust,即保持這顆完全二叉樹是一個大頂堆(小頂堆)。
- 大頂堆:完全二叉樹中,任一結點都小於其雙親結點。
- 小頂堆:完全二叉樹中,任一結點都大於其雙親結點。
我們每次取下一個堆頂元素,那麼只要每次操作完後,通過一次adjust,都能保證這顆完全二叉樹還是一個大頂堆(小頂堆),那麼我們取出的也就自然是有序序列了。
待排序數據依然存放於順序表中。
數據存放沒有從0開始,而是選擇從1開始。
代碼參考於《大話數據結構》。
初始設定
#include<stdio.h>
#define MAXSIZE 20 //順序表最大容量
#define N 10 //表中數據個數
順序表結構體
typedef struct
{
int data[MAXSIZE + 1];
int len; //已存儲元素個數
}Sqlist;
輸出順序表
void Show(Sqlist L)
{
int i;
for (i = 1; i < L.len; ++i)
{
printf("%d,", L.data[i]);
}
printf("%d\n", L.data[i]);
}
輸入函數
void Input(Sqlist* lp)
{
int d[N] = { 9, 1, 5, 8, 3, 0, 7, 4, 6, 2 };
for (int i = 0; i < N; i++)
lp->data[i + 1] = d[i];
lp->len = N;
}
swap函數
void Swap(Sqlist* lp,int a, int b)
{ //交換順序表中兩元素的數值
int t = lp->data[a];
lp->data[a] = lp->data[b];
lp->data[b] = t;
}
堆排序
void HeapAdjust(Sqlist* lp, int s, int m)
{
int j, temp;
temp = lp->data[s]; //暫存
for (j = 2 * s; j <= m; j *= 2)
{ //沿較大的孩子結點向下篩選
if (j < m && lp->data[j] < lp->data[j + 1])
++j; //將j標在較大的孩子上
if (temp >= lp->data[j])
break; //說明此時該子樹雙親已經最大
lp->data[s] = lp->data[j]; //較大孩子上移
s = j;
}
lp->data[s] = temp; //插入
}
void HeapSort(Sqlist* lp)
{
int i;
for (i = lp->len / 2; i > 0; --i)
{ //不必考慮葉子節點,故一開始生成堆只需處理一半結點
HeapAdjust(lp, i, lp->len);
}
for (i = lp->len; i > 1; --i)
{ //每次將堆頂和最後一個元素交換,堆中元素個數減一
Swap(lp, 1, i);
HeapAdjust(lp, 1, i - 1);
}
}
系列鏈接
上一個排序算法:
④希爾排序
下一個排序算法:
⑥歸併排序