堆排序

選擇排序

選擇排序的基本思路是:每次都從原序列中順序查找出最小的元素,放入新的序列的下一個位置(在具體實現中,一般是還是放在原序列中,採用依次交換位置的方法)。這種最簡單實現的選擇排序時間複雜度爲O(n2)O(n^2)。有沒有效率更高的基於選擇的排序算法呢?堆排序就是一種,與選擇排序一樣,堆排序是每次從原序列中取出最大(或最小)的元素,不同的是堆排序使用了堆這一數據結構,每次取出最大(最小)元素僅需O(log(n)),效率有所提高。

堆排序

排序方法非常多,這裏的堆排序很好理解,就是指利用堆這種數據結構所設計的一種排序算法。具體排序思路如下:假定是升序排序,利用堆的性質,先由待排序數組構造最大堆,滿足堆性質後,因爲根節點是最大值,所以每次彈出根節點,即爲有序數組,但這樣描述不是十分嚴謹,比較嚴謹的描述如下:

  1. 構造最大堆。
  2. 交換數組中第一個元素(堆中根節點元素)和數組中最後一個元素,數組長度減一。
  3. 執行“下移”操作,將第一個元素“下移”到滿足堆性質。
  4. 不斷執行步驟2,直到最後僅剩一個元素。

時間複雜度分析:構造堆O(n),依次彈出根節點,共n次,每次的時間複雜度O(log(n)),所以有O(n+nlog(n))=O(nlog(n))。空間複雜度O(1)

具體實現

算法描述如下:

// a爲帶排序數組, count元素個數
procedure heapsort(a, count)
    heapify(a, count);   // 構造堆

    end = count - 1;
    while end > 0
        swap(a[end], a[0]);
        end = end - 1;
        siftDown(a, 0, end);

// 將數組堆化, a爲待排序數組, count元素個數
procedure heapify(a, count) 
    // 這裏元素在數組中的位置從0開始, iParent(count-1)指的是最後一個元素的父節點
    start = iParent(count-1)
    while start >= 0 do
        siftDown(a, start, count-1)
        start = start - 1   //下一個非葉子節點

// 堆根節點爲start的堆進行堆化(保證父節點大於等於子節點)
procedure siftDown(a, start, end)
    root = start
    while iLeftChild(root) <= end
        // 其實這塊主要是選左右子節點中最大的一個,與之交換,代碼實現有很多方法,這裏只是其中一種
        child = iLeftChild(root)
        swap = root 

        if a[swap] < a[child]
            swap = child
        if child+1 <= end and a[swap] < a[child+1]  // 如果右子節點存在且大於左子節點大於父節點,就設置swap爲右子節點
            swap = child + 1
        if swap = root
            return  // 已滿足堆性質,返回
        else
            swap(a[root], a[swap])
            root = swap

具體實現代碼見heapsort.cpp

與其他排序算法的比較

堆排序經常與快排進行比較,他們的平均時間複雜度都爲O(nlog(n)),但一般情況下,快排是較堆排序快一些的,分析時比較容易忽視的一點是局部性原理,這也是cache設計的依據,堆排序相比快排對“局部性”不友好,這個從siftDown的過程就可以看出來,其總是需要父子節點之間進行比較,當元素非常多時,父子節點在數組中的位置會相距非常大。


Reference:
Heapsort

發佈了166 篇原創文章 · 獲贊 160 · 訪問量 37萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章