在順序存儲結構中,堆排序是一種很是不錯的高級選擇排序算法,普通狀況和最差狀況下均可以將時間複雜度控制在O(n * logn)。算法
堆排序能夠用在順序存儲結構,是由於徹底二叉樹的一種獨特性質。而這裏還要先提一下滿二叉樹。函數
啥叫滿二叉樹?滿二叉樹是這樣一種二叉樹,它的每一層都是「滿」的,設根部爲第0層,則每一層都有2^n個節點。全部節點的度數要麼是2,要麼是0(葉子)。指針
那徹底二叉樹呢?咱們首先作出以下規定,即對二叉樹中的節點,按從根部到葉子、每層從左到右遞增的編號;若是某棵樹,其全部節點的編號都與一顆具備相同層數的滿二叉樹對應節點的編號一一對應,則稱這棵樹是徹底二叉樹。code
顯而易見,徹底二叉樹不算最下面一層,必然是一顆滿二叉樹————而且最下面一層必定是從左到右連續完好口的。排序
由於徹底二叉樹和滿二叉樹的編號是一一對應的,且各層的節點個數也是很是明確的,由於咱們就能夠將徹底二叉樹(毫無疑問滿二叉樹必然是徹底二叉樹)保存爲順序存儲結構。這是由於任意節點知道其偏移量以後,理論上均可以計算出其父、其子的偏移量。基礎
那麼咱們就計算一下吧。二叉樹
設有節點,其位於第n層(從根到葉,根層編號爲0),第k個(從左到右,最左邊編號爲0);所以n和k也能夠理解爲其以前的節點層數/節點個數。設其偏移量函數爲P(n,k)時間
那麼咱們知道在以前有n層,每層i都是滿的,有2^i個節點。co
P(n,k)=2^0+2^1+...+2^(n-1)+k
等比序列啊,易得,P(n,k)=2^n+k-1。
好,那麼該節點的孩子的編號的,層數確定是n+1,該節點以前的節點都有兩個孩子,因此孩子的行編號必然是2 * k和2 * k + 1。 也就是P(n+1, 2 * k)和P(n+1, 2 * k + 1)
P(n+1, 2 * k)=2^(n+1)+2*k-1=2 * (2^n+k-1)+1=2 * P(n,k)+1 P(n+1, 2 * k + 1)=2^(n+1)+2*k=2 * (2^n+k-1)+2=2 * P(n,k)+2
因而咱們知道一個節點,若是其偏移量爲d,則其子節點的偏移量爲2 * d + 1和2 * d + 2。
反過來推算,很容易知道其父節點的偏移量爲 (d-1)/2。
如上所述,這就表示,咱們能夠用順序存儲表示一課徹底二叉樹,並能夠很方便的計算出任意節點的父節點和子節點。
在此基礎上,咱們就能夠進行堆排序了。堆排序很簡單,就是首先向單元素堆不斷追加葉子節點,同時調整以保持堆特性,待到將全部節點(在順序存儲中就是各個元素了)都加入堆後,根據堆的性質在堆的根部必然是極值,而後將極值與末端的葉子節點交換,丟掉該葉子,而且從根部調整堆以保持對特性,而後不斷重複這個過程,直到堆變成單元素堆,結束。
我爲何要提堆呢?咱們能夠看到堆排序的後半部分算法中,就是一個很明確的「多個值中選取極值,取出該極值,更新根、而後修復堆」的過程。
這個過程徹底能夠套用到多路歸併排序中,無非是堆排序把末端節點丟了,而多路歸併無丟棄末端節點(即堆的長度未改變)。
可是這裏就有個問題——對堆進行修復時,每層調整都要對三個值(父節點、兩個子節點)進行至少兩次比較。
那麼爲了減小比較的消耗,人們就發展出勝者樹來——勝者樹讓全部參與排序的序列提出的值都做爲葉子,而後在其上面補充節點成爲徹底二叉樹,父節點會保存兩個子節點比較時的勝者。
這樣就讓每層只需比較一次(和兄弟比較便可)。
但是仍是有人不滿意,因而又弄出了敗者樹,敗者樹和勝者樹的結構基本一致,只是父節點保存的不是勝者而是敗者,而且額外有個指針保存當前的勝者(修正完畢後天然就是最後勝者了)。
敗者樹比勝者樹好在哪?
勝者樹的話,從葉子到根的過程當中,每一層都要和兄弟進行比較,而後把勝者付賦給父節點。
而敗者樹,從葉子到根的過程當中,每一層只需和父節點進行比較(該葉子更新前能夠勝出,全部其常勝——因此其各級父節點保管的必然是與其競爭並失敗的兄弟節點),而後敗者賦給父節點,勝者繼續向上走。
其實差距不太大,節省了一次尋址。
通常一個有n個待排序列的話,構造敗者樹或者勝者樹是,樹的大小爲2 * n - 1。敗者樹、勝者樹與徹底二叉樹堆同樣都要先進行調整,此時應從後向前調整。有興趣能夠本身算算。