數據結構之二叉堆、堆排序

前言

上一篇寫了數據結構之二叉搜索樹、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);
        }
    }

算法第二部分

  1. 咱們把堆頂的元素取出,放到一個臨時變量裏存着。
  2. 而後把堆的最末尾元素取出來,放到堆頂。
  3. 把堆的長度-1(由於已經取出以前的堆頂元素了)
  4. 接着對剛剛這個從末尾放到堆頂的元素,進行HeapOne操做,讓他跟子節點比較,把最大的元素交換到堆頂來,再次造成最大堆。

一直重複這個操做後,直到最後一個堆頂被取出,放到數組末尾,堆的長度也就爲0了,咱們的數組也就造成了一組從大到小的數列。

如此,堆排序就完成了

總結

堆排序性能比較穩定,時間複雜度包含初始堆+排序時重建堆爲:O(nlogn)。
在遊戲開發中也會常用到堆

  1. 好比Top K問題,從n個數據中,找出最大的前100個。
  2. 用堆來實現優先加載隊列。
  3. A星尋路算法中,能夠用最小堆來對尋路的開放列表維護順序,把f值最小的放在堆頂,每次取出堆頂後,再HeapOne一次就行了。比每次都對開放列表進行排序的性能高的多。

參考

百度百科-堆排序 《大話數據結構》-程傑

相關文章
相關標籤/搜索