堆排序时间复杂度
注:本文轉(zhuǎn)載于兩篇博文,感謝博主
轉(zhuǎn)載于:
https://blog.csdn.net/yuzhihui_no1/article/details/44258297
https://blog.csdn.net/qq_34228570/article/details/80024306
?堆排序是由1991年的計(jì)算機(jī)先驅(qū)獎(jiǎng)獲得者、斯坦福大學(xué)計(jì)算機(jī)科學(xué)系教授羅伯特.弗洛伊德(Robert W.Floyd)和威廉姆斯(J.Williams)在1964年共同發(fā)明了的一種排序算法( Heap Sort );
????????堆排序(Heapsort)是指利用堆積樹(堆)這種數(shù)據(jù)結(jié)構(gòu)所設(shè)計(jì)的一種排序算法,它是選擇排序的一種??梢岳脭?shù)組的特點(diǎn)快速定位指定索引的元素。堆分為大根堆和小根堆,是完全二叉樹。大根堆的要求是每個(gè)節(jié)點(diǎn)的值都不大于其父節(jié)點(diǎn)的值,即A[PARENT[i]] >= A[i]。在數(shù)組的非降序排序中,需要使用的就是大根堆,因?yàn)楦鶕?jù)大根堆的要求可知,最大的值一定在堆頂。
算法分析
????????其實(shí)這種算法看起來挺復(fù)雜,但是如果真正理解了就會(huì)感覺非常簡單的;
????????基本思想:把待排序的元素按照大小在二叉樹位置上排列,排序好的元素要滿足:父節(jié)點(diǎn)的元素要大于等于其子節(jié)點(diǎn);這個(gè)過程叫做堆化過程,如果根節(jié)點(diǎn)存放的是最大的數(shù),則叫做大根堆;如果是最小的數(shù),自然就叫做小根堆了。根據(jù)這個(gè)特性(大根堆根最大,小根堆根最小),就可以把根節(jié)點(diǎn)拿出來,然后再堆化下,再把根節(jié)點(diǎn)拿出來,,,,循環(huán)到最后一個(gè)節(jié)點(diǎn),就排序好了。
????????基本步驟:
????????其實(shí)整個(gè)排序主要核心就是堆化過程,堆化過程一般是用父節(jié)點(diǎn)和他的孩子節(jié)點(diǎn)進(jìn)行比較,取最大的孩子節(jié)點(diǎn)和其進(jìn)行交換;但是要注意這應(yīng)該是個(gè)逆序的,先排序好子樹的順序,然后再一步步往上,到排序根節(jié)點(diǎn)上。然后又相反(因?yàn)楦?jié)點(diǎn)也可能是很小的)的,從根節(jié)點(diǎn)往子樹上排序。最后才能把所有元素排序好;具體的操作可以看代碼,也可以看看下面的圖示:
????????
實(shí)現(xiàn)代碼
? ? ? ??理解代碼:i節(jié)點(diǎn)的孩子節(jié)點(diǎn)為 2i +1和 2i+2 ;i節(jié)點(diǎn)的 父節(jié)點(diǎn)為:(i-1)/2;最后一個(gè)非葉子節(jié)點(diǎn):n/2 - 1;下面的代碼是實(shí)現(xiàn)的大根堆,把元素從小到大依次排序;
#include<stdio.h>#define LEN 12//打印數(shù)組void print_array(int *array, int length){int index = 0;printf("array:\n");for(; index < length; index++){printf(" %d,", *(array+index));}printf("\n\n");}//堆化函數(shù)void _heapSort(int *array, int i, int length){int child, tmp;//這個(gè)是改變了哪個(gè)節(jié)點(diǎn),就從該節(jié)點(diǎn)開始對(duì)以該節(jié)點(diǎn)為根節(jié)點(diǎn)的子樹進(jìn)行排序for (; 2*i + 1 < length; i = child){//依次到它的子樹的子樹。。。。child = 2*i + 1;if ((child +1 < length) && (array[child+1] > array[child])) child++;//選個(gè)最大的孩子節(jié)點(diǎn)if (array[i] < array[child]){//最大子節(jié)點(diǎn)和父節(jié)點(diǎn)進(jìn)行交互tmp = array[i];array[i] = array[child];array[child] = tmp;}else break;} }void BuildMaxHeap(int *array, int length){//初始化堆int i;for (i = length/2 - 1; i >= 0; i--) _heapSort(array, i, length);//從第一個(gè)非葉子節(jié)點(diǎn)開始排序,一直到根節(jié)點(diǎn)}void heapSort(int *array, int length){int i, tmp;if (length <= 1) return;//如果元素小于1,則退出//這一步是先把元素都堆化好,后面的話 哪個(gè)節(jié)點(diǎn)修改過,就從哪個(gè)節(jié)點(diǎn)開始對(duì)以它為根節(jié)點(diǎn)的子樹進(jìn)行堆化//初始化堆 BuildMaxHeap(array, length);// 先抽取到根節(jié)點(diǎn),然后再對(duì)元素進(jìn)行堆化,然后又抽取根節(jié)點(diǎn),再對(duì)元素進(jìn)行堆化。。。。依次循環(huán)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;}時(shí)間復(fù)雜度
? ? ? 初始化堆:O(n)
????????堆排序的時(shí)間復(fù)雜度,主要在初始化堆過程和每次選取最大數(shù)后重新建堆的過程;
??????????初始化建堆過程時(shí)間:O(n)
????????推算過程:
????????首先要理解怎么計(jì)算這個(gè)堆化過程所消耗的時(shí)間,可以直接畫圖去理解;
????????假設(shè)高度為k,則從倒數(shù)第二層右邊的節(jié)點(diǎn)開始,這一層的節(jié)點(diǎn)都要執(zhí)行子節(jié)點(diǎn)比較然后交換(如果順序是對(duì)的就不用交換);倒數(shù)第三層呢,則會(huì)選擇其子節(jié)點(diǎn)進(jìn)行比較和交換,如果沒交換就可以不用再執(zhí)行下去了。如果交換了,那么又要選擇一支子樹進(jìn)行比較和交換;
????????那么總的時(shí)間計(jì)算為:s = 2^( i - 1 ) ?* ?( k - i );其中 i 表示第幾層,2^( i - 1) 表示該層上有多少個(gè)元素,( k - i) 表示子樹上要比較的次數(shù),如果在最差的條件下,就是比較次數(shù)后還要交換;因?yàn)檫@個(gè)是常數(shù),所以提出來后可以忽略;
????????S = 2^(k-2) * 1 + 2^(k-3)*2.....+2*(k-2)+2^(0)*(k-1) ?===> 因?yàn)槿~子層不用交換,所以i從 k-1 開始到 1;
????????這個(gè)等式求解,我想高中已經(jīng)會(huì)了:等式左右乘上2,然后和原來的等式相減,就變成了:
????????S = 2^(k - 1) + 2^(k - 2) + 2^(k - 3) ..... + 2 - (k-1)? (注:說明時(shí)間復(fù)雜度與樹的高度有關(guān))
????????除最后一項(xiàng)外,就是一個(gè)等比數(shù)列了,直接用求和公式:S = { ?a1[ 1- ?(q^n) ] } ?/ (1-q);
????????S = 2^k -k -1;又因?yàn)?span style="color:#f33b45;">k為完全二叉樹的深度,所以 (2^k) <= ?n < (2^k ?-1 ),總之:可以認(rèn)為k = logn (實(shí)際計(jì)算得到應(yīng)該是 log(n+1) < k <= logn );
? ? ? ??綜上所述得到:S = n - logn -1,所以時(shí)間復(fù)雜度為:O(n)
? ? ? ?
? ? 排序重建堆:
?
? ? ? ? 在取出堆頂點(diǎn)放到對(duì)應(yīng)位置并把原堆的最后一個(gè)節(jié)點(diǎn)填充到堆頂點(diǎn)之后,需要對(duì)堆進(jìn)行重建,只需要對(duì)堆的頂點(diǎn)調(diào)用_heapSort()函數(shù)。?
每次重建意味著有一個(gè)節(jié)點(diǎn)出堆,所以需要將堆的容量減一。_heapSort()函數(shù)的時(shí)間復(fù)雜度k=log(n),k為堆的層數(shù)。所以在每次重建時(shí),隨著堆的容量的減小,層數(shù)會(huì)下降,函數(shù)時(shí)間復(fù)雜度會(huì)變化。重建堆一共需要n-1次循環(huán),每次循環(huán)的比較次數(shù)為log(i),則相加為:log2+log3+…+log(n-1)+log(n)≈log(n!)??梢宰C明log(n!)和nlog(n)是同階函數(shù):?
∵(n/2)n/2≤n!≤nn,∵(n/2)n/2≤n!≤nn,?
∴n/4log(n)=n/2log(n1/2)≤n/2log(n/2)≤log(n!)≤nlog(n)∴n/4log?(n)=n/2log?(n1/2)≤n/2log?(n/2)≤log?(n!)≤nlog?(n)?
所以時(shí)間復(fù)雜度為O(nlogn)
? ? ? ?故堆排序(heapSort())時(shí)間復(fù)雜度為:O(nlogn)
? ??
?
總結(jié)
- 上一篇: python实现链表的删除_Python
- 下一篇: halcon 旋转_HALCON高级篇: