堆排序是由1991年的計算機先驅獎獲得者、斯坦福大學計算機科學系教授羅伯特.弗洛伊德(Robert W.Floyd)和威廉姆斯(J.Williams)在1964年共同發明了的一種排序算法( Heap Sort );
堆排序(Heapsort)是指利用堆積樹(堆)這種數據結構所設計的一種排序算法,它是選擇排序的一種。可以利用數組的特點快速定位指定索引的元素。堆分為大根堆和小根堆,是完全二叉樹。大根堆的要求是每個節點的值都不大於其父節點的值,即A[PARENT[i]] >= A[i]。在數組的非降序排序中,需要使用的就是大根堆,因為根據大根堆的要求可知,最大的值一定在堆頂
算法分析
其實這種算法看起來挺復雜,但是如果真正理解了就會感覺非常簡單的;
基本思想:把待排序的元素按照大小在二叉樹位置上排列,排序好的元素要滿足:父節點的元素要大於等於其子節點;這個過程叫做堆化過程,如果根節點存放的是最大的數,則叫做大根堆;如果是最小的數,自然就叫做小根堆了。根據這個特性(大根堆根最大,小根堆根最小),就可以把根節點拿出來,然后再堆化下,再把根節點拿出來,,,,循環到最后一個節點,就排序好了。
基本步驟:
其實整個排序主要核心就是堆化過程,堆化過程一般是用父節點和他的孩子節點進行比較,取最大的孩子節點和其進行交換;但是要注意這應該是個逆序的,先排序好子樹的順序,然后再一步步往上,到排序根節點上。然后又相反(因為根節點也可能是很小的)的,從根節點往子樹上排序。最后才能把所有元素排序好;具體的操作可以看代碼,也可以看看下面的圖示:
實現代碼:
理解代碼:i節點的孩子節點為 2i +1和 2i+2 ;i節點的 父節點為:(i-1)/2;最后一個非葉子節點:n/2 - 1;下面的代碼是實現的大根堆,把元素從小到大依次排序;
#include<stdio.h> #define LEN 12 //打印數組 void print_array(int *array, int length) { int index = 0; printf("array:\n"); for(; index < length; index++){ printf(" %d,", *(array+index)); } printf("\n\n"); } //堆化函數 void _heapSort(int *array, int i, int length) { int child, tmp; //這個是改變了哪個節點,就從該節點開始對以該節點為根節點的子樹進行排序 for (; 2*i + 1 < length; i = child){//依次到它的子樹的子樹。。。。 child = 2*i + 1; if ((child +1 < length) && (array[child+1] > array[child])) child++;//選個最大的孩子節點 if (array[i] < array[child]){//最大子節點和父節點進行交互 tmp = array[i]; array[i] = array[child]; array[child] = tmp; }else break; } } void heapSort(int *array, int length) { int i, tmp; if (length <= 1) return;//如果元素小於1,則退出 //這一步是先把元素都堆化好,后面的話 哪個節點修改過,就從哪個節點開始對以它為根節點的子樹進行堆化 for (i = length/2 - 1; i >= 0; i--) _heapSort(array, i, length);//從第一個非葉子節點開始排序,一直到根節點 // 先抽取到根節點,然后再對元素進行堆化,然后又抽取根節點,再對元素進行堆化。。。。依次循環 for (i = 0; i < length; i++ ){ tmp = array[0]; array[0] = array[length-i-1]; array[length -i-1] = tmp; _heapSort(array, 0, length-1-i);//堆化子樹 } } int main(void) { int array[LEN] = {2, 1, 4, 0, 12, 520, 2, 9, 5, 3, 13, 14}; print_array(array, LEN); heapSort(array, LEN); print_array(array, LEN); return 0; }
時間復雜度
堆排序的時間復雜度,主要在初始化堆過程和每次選取最大數后重新建堆的過程;
初始化建堆過程時間:O(n)
推算過程:
首先要理解怎么計算這個堆化過程所消耗的時間,可以直接畫圖去理解;
假設高度為k,則從倒數第二層右邊的節點開始,這一層的節點都要執行子節點比較然后交換(如果順序是對的就不用交換);倒數第三層呢,則會選擇其子節點進行比較和交換,如果沒交換就可以不用再執行下去了。如果交換了,那么又要選擇一支子樹進行比較和交換;
那么總的時間計算為:s = 2^( i - 1 ) * ( k - i );其中 i 表示第幾層,2^( i - 1) 表示該層上有多少個元素,( k - i) 表示子樹上要比較的次數,如果在最差的條件下,就是比較次數后還要交換;因為這個是常數,所以提出來后可以忽略;
S = 2^(k-2) * 1 + 2^(k-3)*2.....+2*(k-2)+2^(0)*(k-1) ===> 因為葉子層不用交換,所以i從 k-1 開始到 1;
這個等式求解,我想高中已經會了:等式左右乘上2,然后和原來的等式相減,就變成了:
S = 2^(k - 1) + 2^(k - 2) + 2^(k - 3) ..... + 2 - (k-1)
除最后一項外,就是一個等比數列了,直接用求和公式:S = { a1[ 1- (q^n) ] } / (1-q);
S = 2^k -k -1;又因為k為完全二叉樹的深度,所以 (2^k) <= n < (2^k -1 ),總之可以認為:k = logn (實際計算得到應該是 log(n+1) < k <= logn );
綜上所述得到:S = n - longn -1,所以時間復雜度為:O(n)
更改堆元素后重建堆時間:O(nlogn)
推算過程:
1、循環 n -1 次,每次都是從根節點往下循環查找,所以每一次時間是 logn,總時間:logn(n-1) = nlogn - logn ;
綜上所述:堆排序的時間復雜度為:O(nlogn)
空間復雜度:
轉載請注明作者和原文出處,原文地址:http://blog.csdn.net/yuzhihui_no1/article/details/44258297
若有不正確之處,望大家指正,共同學習!謝謝!!!