原文:
上篇部落格主要講了氣泡排序、插入排序、希爾排序以及選擇排序。本篇部落格就來講一下堆排序(heap sort)。看到堆排序這個名字我們就應該知道這種排序方式的特點,就是利用堆來講我們的序列進行排序。「堆」其實就是一種有著特定結構的完全二叉樹,下方將會詳細的介紹一下堆。本篇部落格講的就是堆排序,首先我們先對大頂堆,小丁堆進行介紹,然後構建堆,最後利用堆的特性對我們的資料序列進行排序。
下方我們依然是先給出相應內容的示意圖,然後給出相應的**實現,最後就是測試用例了。還是那句話,廢話少說,進入今天部落格的主題。
一、堆
在本篇部落格的第一部分,我們先聊一下什麼什麼是「堆」。在資料結構中的堆其實就是一顆「完全二叉樹」,不過此完全二叉樹有著一些特殊的規則,根據這些特殊的規則又可以將「堆」分為「大頂堆」和「小頂堆」。大頂堆的特點是該「完全二叉樹」的根節點比其左右節點都要大,而小頂堆與其相反,在「小頂堆」中根節點要比左右子節點的值都要小。下方詳細的介紹了「大頂堆」和「小頂堆」。
1、大頂堆
下方這示意圖就是大頂堆的規則示意圖,其根節點比起左右子節點都大。如果將「堆」的節點按照層次進行編號的話,假設根節點的編號為i(i > 0)的話,那麼該根節點的左孩子的編號就為2i, 其右孩子的編號就為2i + 1。那麼根據大頂堆的特點,我們很容易就得出k(i) >= k(2i)和k(i) >= k(2i + 1)。根據此特點我們又很容易得出在大頂堆中的根節點是完全二叉樹中最大的那個節點。
根據上述特點,下方是我們構建的「大頂堆」,如下所示。在大頂堆中,如果我們隊大頂堆進行層次遍歷的話,層次遍歷序列的第乙個值肯定是所有序列中最大的那個值。
2、小頂堆
與大頂堆相反,小頂堆則是左右孩子都比根節點大的完全二叉樹。與大頂堆規則類似,在小頂堆中k(i)<=k(2i), k(i) <=k(2i+1)(i > 0)。
根據上述特點,我們很容易的就給出了小頂堆的結構如下所示。如果我們隊小頂堆進行層次遍歷的話,層次遍歷序列的第乙個值肯定是所有序列中最小的那個值。
二、大頂堆的構建
接下來我們要對[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]轉換成大頂堆的層次遍歷的序列。首先我們先將上述序列從左往右存入完全二叉樹中,如下所示。換一種方法來說,上述要排序的序列,也就是下方完全二叉樹層次遍歷的結果。
2、「大頂堆」的轉換
大頂堆的構建是從下往上進行調整的,確切的說是從區域性到整體的來進行大頂堆的建立。在構建大頂堆的過程中,我們先從最小的子樹開始調整,然後慢慢的往外擴充。下方是整個過程的示意圖,下方會給出詳細的介紹。
3.**實現
上述步驟如果理解後,在再給出相應的**實現並不困難。雖然上面是使用的完全二叉樹進行表示的,但是我們在真正進行堆排序的時候並不會用到上述的完全二叉樹的結構。僅僅用到了大頂堆層次遍歷的序列。所以我們只需要將需要排序的陣列根據k(i) >= k(2i)和k(i) >= k(2i + 1)這個規則把該序列轉換成大頂堆層次遍歷的序列即可。下方就是相應的**實現。
下方截圖中的兩個函式就是構建大頂堆層次遍歷序列的函式。heapcreate()函式就負責將傳入的陣列轉換成大頂堆層次遍歷的結構。heapadjast()方法就負責對子樹進行調整。具體**如下所示:
三、堆排序的實現
上面我們將無序的序列轉換成了「大頂堆」的層次遍歷的結果。接下來我們就要利用大頂堆來進行排序了。本部分將會給出堆排序的詳細示意圖,然後再根據這些示意圖給出相應的**實現和執行結果。詳細內容如下所示:
1、堆排示意圖
下方是對「大頂堆」進行的排序,排序後,我們的大頂堆會變成小頂堆,而這個「小頂堆」的層次遍歷就是有序的。下方這個示意圖就是堆排完整的過程。其實下方的步驟可以總結為下方的兩步:
下方這些示意圖其實就是上述兩個步驟的不斷迴圈,具體如下所示。
(這裡注意:調整的時候,父節點是和兩個子節點中資料較大的互換,而不是簡單和左邊的互換)
2、調整大頂堆的**實現
因為將大頂堆第乙個值與最後乙個值交換後,大頂堆的規則將會被打破,將不再是大頂堆。需要我們從上往下進行調整,上述示意圖的方框中的第二部分就是調整的過程。調整後,將會又成為乙個新的大頂堆。下方就是調整的具體**實現,如下所示。
下方**的核心就是將新的根節點與子節點進行比較,若根節點比子節點中較大的那個節點要小,就要將兩者進行交換。重複這個過程,直到成為大頂堆為止。具體做法如下所示。下方這段**就是上面我們建立大頂堆的那段**,我們在堆排序的過程中,依然是呼叫下方的方法來進行大頂堆的調整。
3.堆排序的**實現
「大頂堆」的建立以及調整上面我們已經給出了相應的**實現。在上述**的基礎上,給出堆排序的**並不困難,下方就是堆排序的具體**實現。
在下方**中,首先我們將需要排序的序列呼叫heapcreate()方法將其轉換成「大頂堆」的層次遍歷的序列。然後將大頂堆的根節點與尾結點進行交換,交換後將大頂堆的長度減一,然後將縮減後的堆呼叫heapadjust()進行調整,使其再次成為乙個「大頂堆」。使用while不斷的迴圈交換和調整這個過程,知道「大頂堆」中的元素個數為零。具體**如下所示:
4、輸出結果
接下來我們就來看看上述**的執行結果,下方截圖中就是相應的執行結果。從下方結果中我們也能清楚的看到,堆排序其實就是不斷交換和調整的過程。
堆排序 堆排序優化 索引堆排序
堆排序 堆排序優化 索引堆排序 注 堆排序 索引堆排序 都是不穩定的排序。注 索引最大堆排序有誤!有沒有大神可以指點一二?1 堆 所有元素 都從索引0開始 父親結點索引 i 左孩子結點索引 2i 1 右孩子結點索引 2i 2 左後乙個非葉子結點索引 n 1 2 用於構建堆,從最後乙個非葉子結點索引開...
堆排序 堆排序優化 索引堆排序
堆排序 堆排序優化 索引堆排序 注 堆排序 索引堆排序 都是不穩定的排序。注 索引最大堆排序有誤!有沒有大神可以指點一二?1 堆 所有元素 都從索引0開始 父親結點索引 i 左孩子結點索引 2i 1 右孩子結點索引 2i 2 左後乙個非葉子結點索引 n 1 2 用於構建堆,從最後乙個非葉子結點索引開...
堆排序 模擬堆排序
838.堆排序 輸入乙個長度為n的整數數列,從小到大輸出前m小的數。輸入格式 第一行包含整數n和m。第二行包含n個整數,表示整數數列。輸出格式 共一行,包含m個整數,表示整數數列中前m小的數。資料範圍 1 m n 1051 m n 105,1 數列中元素 1091 數列中元素 109 輸入樣例 5 ...