1 堆
1.1 簡介
n個關鍵字序列Kl,K2,…,Kn稱爲(Heap),當且僅當該序列滿足如下性質(簡稱爲堆性質):
(1)ki<=k(2i)且ki<=k(2i+1)(1≤i≤ n),當然,這是小根堆,大根堆則換成>=號。//k(i)相當於二叉樹的非葉結點,K(2i)則是左孩子,k(2i+1)是右孩子
若將此序列所存儲的向量R[1..n]看做是一棵完全二叉樹的存儲結構,則堆實質上是滿足如下性質的完全二叉樹:
樹中任一非葉結點的關鍵字均不大於(或不小於)其左右孩子(若存在)結點的關鍵字。
1.2 堆的高度
堆可以被看成是一棵樹,結點在堆中的高度可以被定義爲從本結點到葉子結點的最長簡單下降路徑上邊的數目;定義堆的高度爲樹根的高度。我們將看到,堆結構上的一些基本操作的運行時間至多是與樹的高度成正比,爲O(lgn)。
1.3 堆排序
堆排序利用了大根堆(或小根堆)堆頂記錄的關鍵字最大(或最小)這一特徵,使得在當前無序區中選取最大(或最小)關鍵字的記錄變得簡單。
(1)用大根堆排序的基本思想
① 先將初始文件R[1..n]建成一個大根堆,此堆爲初始的無序區
② 再將關鍵字最大的記錄R[1](即堆頂)和無序區的最後一個記錄R[n]交換,由此得到新的無序區R[1..n-1]和有序區R[n],且滿足R[1..n-1].keys≤R[n].key
③由於交換後新的根R[1]可能違反堆性質,故應將當前無序區R[1..n-1]調整爲堆。然後再次將R[1..n-1]中關鍵字最大的記錄R[1]和該區間的最後一個記錄R[n-1]交換,由此得到新的無序區R[1..n-2]和有序區R[n-1..n],且仍滿足關係R[1..n-2].keys≤R[n-1..n].keys,同樣要將R[1..n-2]調整爲堆。
……
直到無序區只有一個元素爲止。
(2)大根堆排序算法的基本操作:
① 初始化操作:將R[1..n]構造爲初始堆;
② 每一趟排序的基本操作:將當前無序區的堆頂記錄R[1]和該區間的最後一個記錄交換,然後將新的無序區調整爲堆(亦稱重建堆)。
注意:
①只需做n-1趟排序,選出較大的n-1個關鍵字即可以使得文件遞增有序。
②用小根堆排序與利用大根堆類似,只不過其排序結果是遞減有序的。堆排序和直接選擇排序相反:在任何時刻堆排序中無序區總是在有序區之前,且有序區是在原向量的尾部由後往
1.4 算法分析
堆排序的時間,主要由建立初始堆和反覆重建堆這兩部分的時間開銷構成,它們均是通過調用Heapify實現的。
堆排序的最壞時間複雜度爲O(nlogn)。堆序的平均性能較接近於最壞性能。
由於建初始堆所需的比較次數較多,所以堆排序不適宜於記錄數較少的文件。
堆排序是就地排序,輔助空間爲O(1),
它是不穩定的排序方法。
1.5 算法實現
要將初始文件R[l..n]調整爲一個大根堆,就必須將它所對應的完全二叉樹中以每一結點爲根的子樹都調整爲堆。
顯然只有一個結點的樹是堆,而在完全二叉樹中,所有序號大於n/2的結點都是葉子,因此以這些結點爲根的子樹均已是堆。這樣,我們只需依次將以序號爲n/2,…,1的結點作爲根的子樹都調整爲堆即可。
#include <iostream>
using namespace std;
const int MAX = 100;
typedef struct SQLIST
{
int r[MAX];
int length;
}SqList;
typedef SqList HeapType;
void HeapAdjust(HeapType &H, int s, int m)
{
//已知H.r[s..m]記錄的關鍵字除H.r[s]之外均滿足堆的定義,本函數調整H.r[s]
//的關鍵字,使H.r[s..m]成爲一個小頂堆(對其中記錄的關鍵字而言)
int rc = H.r[s];
for (int j = 2 * s; j <= m; j *= 2)//沿着值較大的孩子節點向下篩選
{
if (j < m && H.r[j] < H.r[j+1])
{
++ j;//j爲值較大的記錄的下標
}
if (rc >= H.r[j])
{
break;//rc應插入在位置s上
}
H.r[s] = H.r[j];
s = j;
}
H.r[s] = rc;//插入
}
void HeapSort(HeapType &H)
{
//對順序表H進行堆排序
for (int i = H.length / 2; i > 0; -- i)//把H.r[1..H.length]建成堆,這裏是爲了讓每個節點都是堆,因爲大於n/2的點都是葉子節點,已是堆
{
HeapAdjust(H,i,H.length);
}
for (int i = H.length; i > 1; -- i)
{
int tmp = H.r[1];//將堆頂記錄和當前未經排序子序列H.r[1..i]中最後一個記錄相互交換
H.r[1] = H.r[i];
H.r[i] = tmp;
HeapAdjust(H, 1, i -1);//將H.r[1..i-1]重新調整爲堆
}
}
int main()
{
HeapType H;
H.r[1] = 4;
H.r[2] = 3;
H.r[3] = 5;
H.r[4] = 1;
H.r[5] = 6;
H.r[6] = 2;
H.length = 6;
HeapSort(H);
for (int i = 1; i <= H.length; ++ i)
{
cout << H.r[i] << " " ;
}
cout << endl;
return 0;
}