排序算法:堆排序算法实现及分析
堆排序介紹
堆排序(Heap Sort)就來利用堆(假設利用大頂堆)進行排序的方法。它的基本思想是,將待排序的序列構成一個大頂堆。此時,整個序列的最大值就是堆頂的根結點。將它移走(其實就是將其與堆數組的末尾元素交換,此時末尾元素就是最大值),然后將剩余的n-1個序列重新構造成一個堆,這樣就會得到n個元素中的次小值。如何反復執行,便能得到一個有序序列了。
定義看懂沒有?沒看懂沒關系,下面看圖解。
堆排序圖解
我們先來說說堆,我們這來看大頂堆,說大頂堆前,我先看 一個數組如何模擬成樹的。請看下圖,將數組元素從下標0開始,看做完全二叉樹結點,請以層序遍歷的角度看待它。
我先來構造大頂堆,在構造堆之前,我們先看下堆的定義,堆是具有下列性質的完全二叉樹:每個結點的值都大于或等于其左右孩子結點的值,稱為大頂堆;或者每個結點的值都小于或等于其左右孩子結點的值,稱為小頂堆。我們從后往前將每個結點構成一個大頂堆不就得了?想想不是呢?因為最下層的結點已經滿足堆條件,然后調整上層結點,使其滿足大頂堆條件,那么整個完全二叉樹不就成了大頂堆了么。我們應該從后面的那個結點開始呢?當然是從非葉子結點 9 開始呀,因為葉子結點沒有孩子結點唄。將結點9看做一棵樹將其調整問堆,然后將3位根結點的樹調整為堆,然后將2為根結點的樹調整為堆........,整棵完全二叉樹就調整好了,成為了一個大頂堆。請看下圖的代碼和說明。
將堆的初始化看懂了,就已經成功了一大半了。還有一小部分就是 將最值(下標為0)放到序列的尾部,然后將剩下的元素繼續進行堆調整,堆頂(下標為0)的元素 又是次最值,將其放到尾部,這樣一直循環 直到將每個元素放到堆頂,則堆排序完畢。
堆排序代碼
下面請堆排序代碼,然后再進行解說。
//堆調整 大堆頂,將最大值放在根結點 void BigHeadAdjust(int *arr,int index,int length) {int lchild = 2 * index + 1;int rchild = 2 * index + 2;int max = index;if (lchild<length&&arr[lchild]>arr[max]){max = lchild;}if (rchild<length&&arr[rchild]>arr[max]){max = rchild;}if (max != index){Swap(&arr[max], &arr[index]);BigHeadAdjust(arr, max, length);}return; }//堆排序,采用大頂堆 升序 void HeapSort_Up(int *arr, int length) {//初始化堆,將每個非葉子結點倒敘進行大頂堆調整。//循環完畢 初始大頂堆(每個根結點都比它孩子結點值大)形成for (int i = length / 2 - 1; i >= 0; i--){BigHeadAdjust(arr, i, length);}printf("大堆頂初始化順序:");PrintArr(arr, length);//將堆頂值放到數組尾部,然后又進行大堆頂調整,一次堆調整最值就到堆頂了。 for (int i = length - 1; i >= 0; i--){Swap(&arr[0], &arr[i]);BigHeadAdjust(arr, 0, i);}return; }細心的朋友,會發現初始化化堆 是從非葉子結點從后往前進行調整為堆,而后序的堆調整 都是從下標為0處開始調整為堆。因為起初的完全二叉樹是完全無效的,所有只能從后往前調整。在初始化完堆之后,盡管將最值移動到尾部,打亂了堆,因為原來的堆結構已經基本形成,只需要遞歸調用BigHeadAdjust即可。
堆排序復雜度分析
堆排序,它的運行時間主要是消耗在構建堆和在重建堆時的反復篩選上。在構建堆的過程,因為我們是從完全二叉樹最下層的非葉子結點開始構建的,將它與其孩子結點進行比較和有必要的互換,對于每個非葉子結點來說,其實最多2次比較和互換,故初始化堆的時間復雜度為O(n)。在正式排序的時候,第i次取堆頂記錄和重建堆需要O(logi)的時間(完全二叉樹的某個結點到根結點的距離為log2i+1),并且需要取n-1次堆頂記錄,因此重建堆的時間復雜度為O(nlogn)。所以總的來說,堆排序的時間復雜度為O(nlogn)。由于堆排序對元素記錄的排序狀態不敏感,因此它無論最好,最壞,和平均時間復雜度均為O(nlogn)。
完整代碼
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <stdlib.h> #include <time.h> #include <sys/timeb.h> #define MAXSIZE 1000000 //交換值 void Swap(int* a, int* b) {int temp = *a;*a = *b;*b = temp; } //打印數組元素 void PrintArr(int* arr, int length) {for (int i = 0; i < length; i++){printf("%d ", arr[i]);}printf("\n");return; } long GetSysTime() {struct timeb tb;ftime(&tb);return tb.time * 1000 + tb.millitm; }//堆調整 大堆頂,將最大值放在根結點 void BigHeadAdjust(int *arr,int index,int length) {int lchild = 2 * index + 1;int rchild = 2 * index + 2;int max = index;if (lchild<length&&arr[lchild]>arr[max]){max = lchild;}if (rchild<length&&arr[rchild]>arr[max]){max = rchild;}if (max != index){Swap(&arr[max], &arr[index]);BigHeadAdjust(arr, max, length);}return; }//堆排序,采用大頂堆 升序 void HeapSort_Up(int *arr, int length) {//初始化堆,將每個非葉子結點倒敘進行大頂堆調整。//循環完畢 初始大頂堆(每個根結點都比它孩子結點值大)形成for (int i = length / 2 - 1; i >= 0; i--){BigHeadAdjust(arr, i, length);}//printf("大堆頂初始化順序:");//PrintArr(arr, length);//將堆頂值放到數組尾部,然后又進行大堆頂調整,一次堆調整最值就到堆頂了。 for (int i = length - 1; i >= 0; i--){Swap(&arr[0], &arr[i]);BigHeadAdjust(arr, 0, i);}return; }//堆調整 小堆頂,將最小值放在根結點 void SmallHeadAdjust(int *arr, int index, int length) {int lchild = 2 * index + 1;int rchild = 2 * index + 2;int min = index;if (lchild<length&&arr[lchild]<arr[min]){min = lchild;}if (rchild<length&&arr[rchild]<arr[min]){min = rchild;}if (min != index){Swap(&arr[min], &arr[index]);SmallHeadAdjust(arr, min, length);}return; }//堆排序,采用小頂堆 降序 void HeapSort_Down(int *arr, int length) {for (int i = length / 2 - 1; i >= 0; i--){SmallHeadAdjust(arr, i, length);}for (int i = length - 1; i >= 0; i--){Swap(&arr[0], &arr[i]);SmallHeadAdjust(arr, 0, i);}return; } //希爾排序 升序 //根據插入排序的原理,將原來的一個大組,采用間隔的形式分成很多小組,分別進行插入排序 //每一輪結束后 繼續分成更小的組進行 插入排序,直到分成的小組長度為1,說明插入排序完畢 void ShellSort_Up(int* arr, int length) {int increase = length;int i, j, k, temp;do{increase = increase / 3 + 1;//每個小組的長度//每個小組的第0個元素for (i = 0; i < increase; i++){//對每個小組進行插入排序,因為是間隔的形式分組,每個小組下一個元素為 j+=incresefor (j = i + increase; j < length; j += increase){temp = arr[j];//待插入元素for (k = j - increase; k >= 0 && temp < arr[k]; k -= increase){arr[k + increase] = arr[k];}arr[k + increase] = temp;}}} while (increase>1); }int main(int argc, char *argv[]) {srand((size_t)time(NULL));//設置隨機種子int arr[10] = { 6,8,2,3,9,7,4,1,5,10 };int *arr2 = (int*)malloc(sizeof(int)*MAXSIZE);int *arr3 = (int*)malloc(sizeof(int)*MAXSIZE);//給每個元素設置一個隨機值for (int i = 0; i < MAXSIZE; i++){int num = rand() % MAXSIZE;arr2[i] = num;arr3[i] = num;}//printf("排序前:\n");//PrintArr(arr, 10);//printf("堆排序升序:\n");//HeapSort_Up(arr, 10);//PrintArr(arr, 10);//printf("堆排序降序:\n");//HeapSort_Down(arr, 10);//PrintArr(arr, 10);long start1 = GetSysTime();ShellSort_Up(arr2, MAXSIZE);long end1 = GetSysTime();printf("%d個元素 希爾排序耗費%d毫秒\n",MAXSIZE,end1-start1);long start2 = GetSysTime();HeapSort_Up(arr3, MAXSIZE);long end2 = GetSysTime();printf("%d個元素 堆排序耗費%d毫秒\n", MAXSIZE, end2 - start2);return 0; }運行結果檢測
正確性驗證
和希爾排序比較
排序100萬個數據,比較下他們的效率。希爾排序居然比堆排序高,運行了好幾遍都是這個結果。。。
總結
以上是生活随笔為你收集整理的排序算法:堆排序算法实现及分析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: C/C++面试题—机器人的运动范围【回溯
- 下一篇: 算法分析基本概念