堆排序算法C語言詳解

2021-02-07 編程大師

在學習堆排序之前,首先需要了解堆的含義:在含有 n 個元素的序列中,如果序列中的元素滿足下面其中一種關係時,此序列可以稱之為堆。


對於堆的定義也可以使用完全二叉樹來解釋,因為在完全二叉樹中第 i 個結點的左孩子恰好是第 2i 個結點,右孩子恰好是 2i+1 個結點。如果該序列可以被稱為堆,則使用該序列構建的完全二叉樹中,每個根結點的值都必須不小於(或者不大於)左右孩子結點的值。

以無序表{49,38,65,97,76,13,27,49}來講,其對應的堆用完全二叉樹來表示為:



圖 3 無序表對應的堆

提示:堆用完全二叉樹表示時,其表示方法不唯一,但是可以確定的是樹的根結點要麼是無序表中的最小值,要麼是最大值。

通過將無序錶轉化為堆,可以直接找到表中最大值或者最小值,然後將其提取出來,令剩餘的記錄再重建一個堆,取出次大值或者次小值,如此反覆執行就可以得到一個有序序列,此過程為堆排序。

堆排序過程的代碼實現需要解決兩個問題:

如何將得到的無序序列轉化為一個堆?

在輸出堆頂元素之後(完全二叉樹的樹根結點),如何調整剩餘元素構建一個新的堆?

首先先解決第 2 個問題。圖 3 所示為一個完全二叉樹,若去除堆頂元素,即刪除二叉樹的樹根結點,此時用二叉樹中最後一個結點 97 代替,如下圖所示:



 

此時由於結點 97 比左右孩子結點的值都大,破壞了堆的結構,所以需要進行調整:首先以 堆頂元素 97 同左右子樹比較,同值最小的結點交換位置,即 27 和 97 交換位置:



 

由於替代之後破壞了根結點右子樹的堆結構,所以需要進行和上述一樣的調整,即令 97 同 49 進行交換位置:



 

通過上述的調整,之前被破壞的堆結構又重新建立。從根結點到葉子結點的整個調整的過程,被稱為「篩選」。

解決第一個問題使用的就是不斷篩選的過程,如下圖所示,無序表{49,38,65,97,76,13,27,49}初步建立的完全二叉樹,如下圖所示:



 

在對上圖做篩選工作時,規律是從底層結點開始,一直篩選到根結點。對於具有 n 個結點的完全二叉樹,篩選工作開始的結點為第 ⌊n/2⌋個結點(此結點後序都是葉子結點,無需篩選)。

所以,對於有 9 個結點的完全二叉樹,篩選工作從第 4 個結點 97 開始,由於 97 > 49 ,所以需要相互交換,交換後如下圖所示:



 

然後再篩選第 3 個結點 65 ,由於 65 比左右孩子結點都大,則選擇一個最小的同 65 進行交換,交換後的結果為:



 

然後篩選第 2 個結點,由於其符合要求,所以不用篩選;最後篩選根結點 49 ,同 13 進行交換,交換後的結果為:



 

交換後,發現破壞了其右子樹堆的結構,所以還需要調整,最終調整後的結果為:


所以實現堆排序的完整代碼為:

#include <stdio.h>#include <stdlib.h>#define MAX 9typedef struct {    int key;}SqNote;typedef struct {    SqNote r[MAX];    int length;}SqList;void HeapAdjust(SqList * H,int s,int m){    SqNote rc=H->r[s];        for (int j=2*s; j<=m; j*=2) {                if (j+1<m && (H->r[j].key<H->r[j+1].key)) {            j++;        }                if (!(rc.key<H->r[j].key)) {            break;        }                H->r[s]=H->r[j];        s=j;    }    H->r[s]=rc;}void swap(SqNote *a,SqNote *b){    int key=a->key;    a->key=b->key;    b->key=key;}void HeapSort(SqList *H){        for (int i=H->length/2; i>0; i--) {                HeapAdjust(H, i, H->length);    }        for (int i=H->length; i>1; i--) {                swap(&(H->r[1]), &(H->r[i]));                HeapAdjust(H, 1, i-1);    }}int main() {    SqList * L=(SqList*)malloc(sizeof(SqList));    L->length=8;    L->r[1].key=49;    L->r[2].key=38;    L->r[3].key=65;    L->r[4].key=97;    L->r[5].key=76;    L->r[6].key=13;    L->r[7].key=27;    L->r[8].key=49;    HeapSort(L);    for (int i=1; i<=L->length; i++) {        printf("%d ",L->r[i].key);    }    return 0;}

運行結果為:

13 27 38 49 49 65 76 97

提示:代碼中為了體現構建堆和輸出堆頂元素後重建堆的過程,堆在構建過程中,採用的是堆的第二種關係,即父親結點的值比孩子結點的值大;重建堆的過程也是如此。

堆排序在最壞的情況下,其時間複雜度仍為 O(nlogn)。這是相對於快速排序的優點所在。同時堆排序相對於樹形選擇排序,其只需要一個用於記錄交換(rc)的輔助存儲空間,比樹形選擇排序的運行空間更小。

相關焦點

  • C語言八大排序算法,附動圖和詳細代碼解釋!
    (Heap Sort)算法思想:堆的概念堆:本質是一種數組對象。= [0 for i in range(length)] # 設置輸出序列,初始化為0 c = [0 for i in range(maxnumber+ 1)] # 設置技術序列,初始化為0 for j in numberlist: c[j] = c[j] + 1 for i in range(1, len(c)): c[i] = c[i] + c[
  • 八大經典排序算法詳解
    >」,找工作面試的時候,算法和數據結構也是絕對不可避免的,面試官可能一言不合就讓你手寫一個排序算法。我把最經典的八大排序算法原理和代碼也都整理出來了,內容如下,希望對大家能有所幫助。插入排序•基本思想:每步將一個待排序的紀錄,按其關鍵碼值的大小插入前面已經排序的文件中適當位置上,直到全部插入完為止。•算法適用於少量數據的排序,時間複雜度為O(n^2)。是穩定的排序方法。
  • C語言實現八大排序算法(一)
    本文主要介紹數據結構中常見的八大排序算法,冒泡排序、快速排序、直接插入排序、希爾排序、簡單選擇排序、堆排序、歸併排序和基數排序。
  • 第二篇,選擇排序算法:簡單選擇排序、堆排序
    此為第二篇,選擇排序算法:簡單選擇排序、堆排序。
  • 堆排序算法
    堆是一種數據結構,一種叫做完全二叉樹的數據結構。堆是具有以下性質的完全二叉樹:每個結點的值都大於或等於其左右孩子結點的值,稱為大頂堆;或者每個結點的值都小於或等於其左右孩子結點的值,稱為小頂堆。大頂堆:a[i] >=a[2i+1] && ar[i] >= a[2i+2] 小頂堆:a[i] <= a[2i+1] && a[i] <= a[2i+2] 堆排序的基本思想:
  • 詳解堆排序
    堆是一棵順序存儲的完全二叉樹。其中每個結點的關鍵字都不大於其孩子結點的關鍵字,這樣的堆稱為小根堆。其中每個結點的關鍵字都不小於其孩子結點的關鍵字,這樣的堆稱為大根堆。構造了初始堆後,我們來看一下完整的堆排序處理:還是針對前面提到的無序序列 { 1, 3, 4, 5, 2, 6, 9, 7, 8, 0 } 來加以說明。
  • Go語言實現常用排序算法
    比如:插入類排序有:直接插入排序和希爾排序選擇類排序有:直接選擇排序和堆排序交換類排序有:冒泡排序和快速排序它們的複雜度如下:穩定性概念定義快速排序,歸併排序和堆排序是比較高級的排序算法。目前被認為綜合最好的高級排序算法是快速排序,快速排序的平均用時最短,大多數的編程庫內置的排序算法都是它。
  • PHP實現耐心排序(patience sort)算法
    耐心排序(patience sort)是一種排序算法,靈感來源於紙牌遊戲patience,並以此命名。該算法的一個變體可以有效地計算給定數組中最長遞增子序列的長度。該算法的名字來源於一個簡化版的patience紙牌遊戲。這個遊戲以一副洗牌開始。
  • 【offerMe--數據結構】----排序算法
    (抓撲克牌思想)特點:元素越接近有序,直接插入排序算法的時間效率越高,算法的本質是減治算法;因此當數組接近有序或大概率有序的情況下,或是數組的數量比較小的時候採用插入排序。該算法可以認為是插入排序的一個變種,稱為二分查找排序。
  • C語言插入排序算法及代碼
    插入排序是排序算法的一種,它不改變原有的序列(數組),而是創建一個新的序列,在新序列上進行操作。這裡以從小到大排序為例進行講解。基本思想及舉例說明插入排序的基本思想是,將元素逐個添加到已經排序好的數組中去,同時要求,插入的元素必須在正確的位置,這樣原來排序好的數組是仍然有序的。
  • 快速排序(QSort,快排)算法及C語言實現
    上節介紹了如何使用起泡排序的思想對無序表中的記錄按照一定的規則進行排序,本節再介紹一種排序算法——快速排序算法(Quick Sort)。
  • ...選擇,插入,希爾,歸併,快排,堆排序,計數排序,桶排序,基數排序)
    一、前言傳統的計算機算法和數據結構領域,都是基於java或者c/c++等。沒用javascript實現過或是沒仔細看過相關算法的原理,導致寫起來浪費很多時間。對於一個前端來說,尤其是筆試面試的時候,算法方面考的其實不難(十大排序算法或是和十大排序算法同等難度的)。
  • 你所知道的十大排序算法的總結(冒泡,選擇,插入,希爾,歸併,快排,堆...
    一、前言傳統的計算機算法和數據結構領域,都是基於java或者c/c++等。沒用javascript實現過或是沒仔細看過相關算法的原理,導致寫起來浪費很多時間。對於一個前端來說,尤其是筆試面試的時候,算法方面考的其實不難(十大排序算法或是和十大排序算法同等難度的)。
  • 【第3篇 | 數據結構與算法】一舉消滅十大常見(常考)排序算法(原理+動圖+代碼+)
    其中,K是將子問題b和c的結果合併成問題a的結果所消耗的時間。由遞歸算法的複雜度分析可知,a問題分解為子問題b和c,那麼有對應時間關係 T(a) = T(b) + T(c) +K。不需要進行元素之間的比較操作,屬於一種「分配式排序」,是一種穩定的排序。堆排序法原理:堆排序的過程類似於堆的刪除操作。第一步,把堆頂元素和下標為n的堆尾元素交換位置;第二步,將前n-1個元素重新堆化;第三步,再把堆頂元素和下標為n-1位置的元素交換,依次迭代。直到最後剩下一個元素為止。
  • 十大排序算法,來這看看-基本思想+動畫演示+C語言實現
    基本思想希爾排序,也稱遞減增量排序算法,是插入排序的一種更高效的改進版本基本思想是先將整個待排序的記錄序列分割成為若干子序列分別進行直接插入排序     quick_sort(arr, index + 1, end);//基準值之右再排序  }}7.堆排序基本思想創建一個大頂堆(
  • JS家的排序算法 十大經典算法排序總結
    然而,在傳統的計算機算法和數據結構領域,大多數專業教材和書籍的默認語言都是Java或者C/C+ +。這給最近想惡補算法和數據結構知識的我造成了一定困擾,因為我想尋找一本以JavaScript為默認語言的算法書籍。當我了解到O』REILLY家的動物叢書系列裡有一本叫做《數據結構與算法JavaScript描述》時,便興奮的花了兩天時間把這本書從頭到尾讀了一遍。
  • C語言編程實例講解
    C語言希爾排序算法C語言冒泡排序算法C語言直接插入排序算法C語言快速排序算法C語言選擇排序算法>C語言歸併排序算法C語言二分查找算法,折半查找算法C語言分塊查找算法,索引順序查找算法C語言求n的階乘(n!)
  • 經典排序算法和Python詳解之(一)選擇排序和二元選擇排序
    本文源自微信公眾號【Python編程和深度學習】原文連結:經典排序算法和Python詳解之(一)選擇排序和二元選擇排序,歡迎掃碼關注鴨!掃它!掃它!掃它排序算法是《數據結構與算法》中最基本的算法之一。排序就是對於一個列表,按照某關鍵字遞增或遞減的順序進行操作。
  • R語言實現幾種經典排序算法
    希爾排序        希爾排序是插入排序的一種,也稱縮小增量排序,是直接插入排序算法的一種更高效的改進版本。希爾排序是非穩定排序算法。希爾排序是把記錄按下標的一定增量分組,對每組使用直接插入排序算法排序;隨著增量逐漸減少,每組包含的關鍵詞越來越多,當增量減至1時,整個文件恰被分成一組,算法便終止。
  • JavaScript實現九種排序算法
    1.插入排序最普通的排序算法, 從數組下標1開始每增1項排序一次,越往後遍歷次數越多;原理圖:代碼:// 插入排序 從下標1開始每增1項排序一次,越往後遍歷次數越多很常見很容易理解的排序算法, 排序思路:遍歷數組,每次遍歷就將最大(或最小)值推至最前。