上一篇寫了數據結構之二叉搜索樹、AVL自平衡樹,此次來寫堆。html
好久之前排序算法的時間複雜度一直是O(n^2), 當時學術界充斥着「排序算法不可能突破O(n^2)」的聲音,直到1959年,由D.L.Shell提出了一種排序算法,希爾排序(Shell Sort),纔打破了這種不可能的聲音,把排序算法的時間複雜度提高到了O(n^3/2)!算法
當科學家們知道這種"不可能"被突破以後,又相繼有了更快的排序算法,「不可能超越O(n^2)」完全成爲了歷史。數組
在1964年,沒錯,是55年前!堆排序這種奇思妙想的,十分精彩的,排序算法誕生了!時間複雜度爲O(nlogn),遠甩O(n^2)數據結構
由Robert W. Floyd(羅伯特·弗洛伊德)和J.W.J. Williams(威廉姆斯)共同發明了著名的堆排序,同時也發明了「堆」這樣的數據結構, Floyd在1978年得到了圖靈獎!真是個狼人!!(比很人還要多一點)性能
有時候瞭解下歷史,也是十分有趣的!雖然你可能會以爲並沒什麼卵用~code
以前第一次聽到堆這個詞的時候,感受像是一堆什麼東西,徹底跟樹連想不到一塊兒,後來才知道,原來堆也是一顆二叉樹,並且是徹底二叉樹htm
堆的性質:blog
堆中某個節點的值老是不大於或不小於其父節點的值;
堆老是一棵徹底二叉樹。排序
咱們能夠把堆,存放在一個數組中,根據索引來獲取節點,那麼如何經過索引表示父子關係呢?
堆是一顆徹底二叉樹,因此知足以下條件索引
假如當前的節點索引爲:k
父節點索引:(k-1) / 2
左孩子節點:2 * k + 1
右孩子節點:2 * k + 2
根據這個規律,咱們就能夠用索引來計算出父子節點的位置了。這樣就能把堆存放在數組中使用,會更加節省內存。
堆排序算法就是造成一個堆後,假如是大頂堆,堆頂確定是最大的元素,那咱們每次都把堆頂的最大元素拿走,而後把堆末尾的元素放到堆頂來,可是這個元素不必定是當前最大的,因此還要對這個元素在堆裏進行比較,把最大的元素放到堆頂,再取出來。如此咱們每次取出的都是剩餘元素中最大的元素,就能獲得一組從大到小有序的元素。下面咱們來用大頂堆對一組數據進行堆排序計算。
數據爲:[50, 10, 90, 30, 70, 40, 80, 60, 20]
算法分爲兩個部分
1.如何將一組無序的數據構建出一個初始的大頂堆?
2.在拿走堆頂元素以後,如何計算出新的堆頂元素?
首先咱們要實現一個操做:若是一個節點的子節點比它更大,就交換位置,若是子節點還有子節點,就要繼續比下去,直到末尾。這個操做咱們稱爲:HeapOne
public void HeapOne(List<int> list, int len, int s) { int temp, j; temp = list[s];//先把指定要下沉節點的值取出來 for (j = (2 * s)+1; j < len; j = (j*2)+1) { if (j < (len - 1) && list[j] < list[j + 1])//看看左右兩個子節點誰更大,就取誰 ++j; if (temp >= list[j])//子節點比父節點小,就無論 break; list[s] = list[j];//先把子節點的值給父節點 s = j;//繼續從這個子節點往下比較下去 } list[s] = temp; }
實現這個操做以後,就能夠開始咱們的第一個部分了,造成初始大頂堆。
從最後一個非葉子節點開始,對該節點進行HeapOne,一直從下往上,直到把全部的父節點都HeapOne了一遍,一個初始的大頂堆就造成了。
public void HeapSort(List<int> list) { int i; for (i = (list.Count - 1) / 2; i >= 0; i--)//第一部分,造成一個初始大頂堆 { HeapOne(list, list.Count, i); } for (i = list.Count -1; i > 0; i--)//每拿走一個元素,都從新計算新堆 { int temp = list[0]; list[0] = list[i]; list[i] = temp; HeapOne(list, i, 0); } }
算法第二部分
一直重複這個操做後,直到最後一個堆頂被取出,放到數組末尾,堆的長度也就爲0了,咱們的數組也就造成了一組從大到小的數列。
如此,堆排序就完成了
堆排序性能比較穩定,時間複雜度包含初始堆+排序時重建堆爲:O(nlogn)。
在遊戲開發中也會常用到堆
百度百科-堆排序 《大話數據結構》-程傑