堆排序說明

這是一篇對堆排序說得比較好的文章,轉載了。html

原文:https://www.cnblogs.com/ludashi/p/6043006.htmlgit

 

上篇博客主要講了冒泡排序、插入排序、希爾排序以及選擇排序。本篇博客就來說一下堆排序(Heap Sort)。看到堆排序這個名字咱們就應該知道這種排序方式的特色,就是利用堆來說咱們的序列進行排序。「堆」其實就是一種有着特定結構的徹底二叉樹,下方將會詳細的介紹一下堆。本篇博客講的就是堆排序,首先咱們先對大頂堆,小丁堆進行介紹,而後構建堆,最後利用堆的特性對咱們的數據序列進行排序。github

下方咱們依然是先給出相應內容的示意圖,而後給出相應的代碼實現,最後就是測試用例了。仍是那句話,廢話少說,進入今天博客的主題。數組

 

1、堆數據結構

在本篇博客的第一部分,咱們先聊一下什麼什麼是「堆」。在數據結構中的堆其實就是一顆「徹底二叉樹」,不過此徹底二叉樹有着一些特殊的規則,根據這些特殊的規則又能夠將「堆」分爲「大頂堆」和「小頂堆」。大頂堆的特色是該「徹底二叉樹」的根節點比其左右節點都要大,而小頂堆與其相反,在「小頂堆」中根節點要比左右子節點的值都要小。下方詳細的介紹了「大頂堆」和「小頂堆」。函數

 一、大頂堆測試

下方這示意圖就是大頂堆的規則示意圖,其根節點比起左右子節點都大。若是將「堆」的節點按照層次進行編號的話,假設根節點的編號爲i(i > 0)的話,那麼該根節點的左孩子的編號就爲2i, 其右孩子的編號就爲2i + 1。那麼根據大頂堆的特色,咱們很容易就得出k(i) >= k(2i)和k(i) >= k(2i + 1)。根據此特色咱們又很容易得出在大頂堆中的根節點是徹底二叉樹中最大的那個節點。3d

  

根據上述特色,下方是咱們構建的「大頂堆」,以下所示。在大頂堆中,若是咱們隊大頂堆進行層次遍歷的話,層次遍歷序列的第一個值確定是全部序列中最大的那個值。htm

  

二、小頂堆blog

與大頂堆相反,小頂堆則是左右孩子都比根節點大的徹底二叉樹。與大頂堆規則相似,在小頂堆中k(i)<=k(2i), k(i) <=k(2i+1)(i > 0)。

  

根據上述特色,咱們很容易的就給出了小頂堆的結構以下所示。若是咱們隊小頂堆進行層次遍歷的話,層次遍歷序列的第一個值確定是全部序列中最小的那個值。

  

 

 

2、大頂堆的構建

接下來咱們要對[62, 88, 58, 47, 62, 35, 73, 51, 99, 37, 93]進行堆排序,在排序以前,咱們須要將該序列構建成大頂堆。更確切的說是根據k(i) >= k(2i)和k(i) >= k(2i + 1)這個規則把該序列轉換成大頂堆層次遍歷的序列。進一步說,假如大頂堆層次遍歷的序列爲list, 若是下標是從1開始的話,那麼確定有list[i] > list[2i], list[i]>list[2i + 1](i > 0)這個規則。咱們就能夠經過這個規則將[62, 88, 58, 47, 62, 35, 73, 51, 99, 37, 93]此序列轉換成大頂堆的層次遍歷的序列。下方咱們會詳細的給出方案。

1.大頂堆構建的示意圖

接下來咱們將經過示意圖的方式來聊一下如何將[62, 88, 58, 47, 62, 35, 73, 51, 99, 37, 93]轉換成大頂堆的層次遍歷的序列。首先咱們先將上述序列從左往右存入徹底二叉樹中,以下所示。換一種方法來講,上述要排序的序列,也就是下方徹底二叉樹層次遍歷的結果。

  

二、「大頂堆」的轉換 

大頂堆的構建是從下往上進行調整的,確切的說是從局部到總體的來進行大頂堆的建立。在構建大頂堆的過程當中,咱們先從最小的子樹開始調整,而後慢慢的往外擴充。下方是整個過程的示意圖,下方會給出詳細的介紹。

  • (1)、位於「徹底二叉樹」最下方最小的子樹是以62爲根節點的子樹,咱們先對此子樹進行調整,將其調整成大頂堆。咱們先比較62的兩個子節點,比較後咱們知道93是子節點中較大的那個。而後62再和93進行比較,咱們發現93>62,將62與93交換。該子樹的大頂堆構建完畢。
  • (2)、以一樣的方式咱們對以47爲根節點的子樹和以58爲根節點的子樹進行調整,將其調整爲大頂堆。具體步驟以下方(2)、(3)所示。
  • (3)、子樹的範圍繼續擴大,接下來咱們要調整根節點爲88的子樹。88的左右子樹都是大頂堆,可是88爲根節點的子樹不是大頂堆,咱們須要從下方的子樹中找到88應該在的位置,使其成爲大頂堆。88與其較大的子節點99比較,由於99>88將其進行交換。交換完畢後,88的子節點爲51和47。88>51,不須要交換,此刻該子樹的大頂堆構建完畢。
  • (4)、同上一步,咱們對整棵樹進行調整,最終大頂堆構建完畢。

  

 

3.代碼實現

上述步驟若是理解後,在再給出相應的代碼實現並不困難。雖然上面是使用的徹底二叉樹進行表示的,可是咱們在真正進行堆排序的時候並不會用到上述的徹底二叉樹的結構。僅僅用到了大頂堆層次遍歷的序列。因此咱們只須要將須要排序的數組根據k(i) >= k(2i)和k(i) >= k(2i + 1)這個規則把該序列轉換成大頂堆層次遍歷的序列便可。下方就是相應的代碼實現。

下方截圖中的兩個函數就是構建大頂堆層次遍歷序列的函數。heapCreate()函數就負責將傳入的數組轉換成大頂堆層次遍歷的結構。heapAdjast()方法就負責對子樹進行調整。具體代碼以下所示:

  

 

 

3、堆排序的實現

 上面咱們將無序的序列轉換成了「大頂堆」的層次遍歷的結果。接下來咱們就要利用大頂堆來進行排序了。本部分將會給出堆排序的詳細示意圖,而後再根據這些示意圖給出相應的代碼實現和運行結果。詳細內容以下所示:

一、堆排示意圖

下方是對「大頂堆」進行的排序,排序後,咱們的大頂堆會變成小頂堆,而這個「小頂堆」的層次遍歷就是有序的。下方這個示意圖就是堆排完整的過程。其實下方的步驟能夠總結爲下方的兩步:

  • 將大頂堆的第一個值(整個序列中最大的那個值)與大頂堆最後一個值進行交換。
  • 交換後,最後一個值爲整個序列中最大值,將此值從大頂堆中剔除。而後將剩餘的元素再次進行調整,將其調整爲大頂堆。

下方這些示意圖其實就是上述兩個步驟的不斷循環,具體以下所示。

(這裏注意:調整的時候,父節點是和兩個子節點中數據較大的互換,而不是簡單和左邊的互換)

  

  

二、調整大頂堆的代碼實現

由於將大頂堆第一個值與最後一個值交換後,大頂堆的規則將會被打破,將再也不是大頂堆。須要咱們從上往下進行調整,上述示意圖的方框中的第二部分就是調整的過程。調整後,將會又成爲一個新的大頂堆。下方就是調整的具體代碼實現,以下所示。

下方代碼的核心就是將新的根節點與子節點進行比較,若根節點比子節點中較大的那個節點要小,就要將二者進行交換。重複這個過程,直到成爲大頂堆爲止。具體作法以下所示。下方這段代碼就是上面咱們建立大頂堆的那段代碼,咱們在堆排序的過程當中,依然是調用下方的方法來進行大頂堆的調整。

  

 

3.堆排序的代碼實現

「大頂堆」的建立以及調整上面咱們已經給出了相應的代碼實現。在上述代碼的基礎上,給出堆排序的代碼並不困難,下方就是堆排序的具體代碼實現。

在下方代碼中,首先咱們將須要排序的序列調用heapCreate()方法將其轉換成「大頂堆」的層次遍歷的序列。而後將大頂堆的根節點與尾結點進行交換,交換後將大頂堆的長度減一,而後將縮減後的堆調用heapAdjust()進行調整,使其再次成爲一個「大頂堆」。使用while不斷的循環交換和調整這個過程,知道「大頂堆」中的元素個數爲零。具體代碼以下所示:

  

 

四、輸出結果

接下來咱們就來看看上述代碼的運行結果,下方截圖中就是相應的運行結果。從下方結果中咱們也能清楚的看到,堆排序其實就是不斷交換和調整的過程。

  

 

本篇博客對堆排序的介紹就先到這兒,下篇博客咱們將會介紹「歸併排序」以及「快速排序」的詳細內容。本篇博客的相關代碼依然會在github上進行分享,下方是github分享地址,以下所示:

github代碼分享地址:https://github.com/lizelu/DataStruct-Swift/tree/master/AllKindsOfSort 

相關文章
相關標籤/搜索