一.概述
1.堆排序是一種時間複雜度爲O(nlogn)的選擇排序,它的中心思想是先建立一個比較有序列的堆,然後逐步將堆中的根節點與最後一個數據交換,最終得到有序的數據;
2.堆排序對原始數據不敏感,無論原始數據的有序程度如何,堆排序的時間複雜度都是O(nlogn);
3.堆排序是一種不穩定的排序方法,且適合待排數據比較多的情況;
二.概念
完全二叉樹:完全二叉樹說白了就是所有的結點按照 根結點—>左子樹—>右子樹 的順序進行排列,不允許跳躍,擁有(2^n - 1)個節點的n層完全二叉樹是滿二叉樹;
用到的完全二叉樹的性質:序號爲i的節點,其左子樹的序號爲2i,右子樹的序號爲2i+1;
堆:堆是一個完全二叉樹,且具有如下性質:每個結點的值都大於或者等於它們的左子樹和右子樹的值(大頂堆);每個結點的值都小於或者等於它們的左子樹和右子樹得值(小頂堆);
三.堆排序過程
1.用無序的數組(共n個數據)通過堆調整函數,建立一個大頂堆或者小頂堆(本文以大頂堆爲例);
2.將堆頂的根(最大值)與數組末尾的元素進行交換,然後將剩下n-1個數據重新利用堆調整函數構建一個新的大頂堆,再把堆頂的跟與數組倒數第二個元素進行交換,如此循環n-1次就得到了有序序列;
堆調整函數流程圖如下:
堆排序函數流程圖:
//堆調整函數,H[]爲存儲數據的數組
//s爲根結點的起始位置,m爲排序數據的長度
void HeapAdjust (int H[],int s,int m)
{
int temp = 0; //定義臨時變量用於之後的數值交換
int j = 0;
temp = H[s]; //先把根結點的數據存儲在臨時變量中
for(j = 2*s;j <= m;j *= 2) //2*s代表該結點的左子樹序號,該循環的意思是搜查該結點的所有子樹
{
if(j < m && H[j] < H[j + 1]) //選出根結點中左子樹、右子樹比較大的那個
{
++j;
}
if(H[j] < temp) //如果根結點的子樹們沒有比根結點大的,就跳出循環,不用交換
{
break;
}else{
H[s] = H[j]; //如果根結點的子樹們有比根結點大的,則把最大的那個賦值給根結點
}
s = j; //把被交換的子樹結點序號賦給根結點
}
H[j] = temp; //把臨時變量中儲存的根節點的值賦值給交換數據的子結點,完成數據交換
}
//堆排序
void HeadSort (int H[] ,int length) //H[]爲待排序數組 length爲該數組的長度
{
int i;
for(i = length / 2 - 1; i > 0; i--) //從序號爲length/2的開始的結點有子樹,後面的結點都是葉子結點不需要遍歷
{
HeapAdjust(H, i, length); //構建大頂堆
}
for(i = length -1; i > 0; i--)
{
swap(H ,0 ,i); //將堆頂的元素(最大)與數組中最後一個未排序的元素交換
HeapAdjust(H, 0, i - 1); //將剩餘元素重新構建大頂堆
}
}
//交換函數,用於交換數組中下標爲i與下標爲j的值
void swap (int H[], int i, int j)
{
int temp = H[i];
H[i] = H[j];
H[j] = temp;
}
四.堆排序時間複雜度的計算
堆排序的運行時間主要分爲兩部分:初始大頂堆的構建、交換數據之後重複建堆
大頂堆的構建其實是完全二叉樹的所有有子樹的結點與左右子樹的值得比較,每個結點最多比較兩次,所以時間複雜度爲O(n);
在交換數據之後重建堆,因爲完全二叉樹的某個結點到根結點的距離爲 logi + 1,所以第i次取出堆頂數據重建堆需要O(logi)的時間,一共需要取n -1次,所以時間複雜度爲O(nlogn)。