堆排序概述及代碼實現


一、堆排序概述

當你看到這里請去原作者那里點個贊, 原文鏈接

1.堆是一種數據結構 

可以將堆看作一棵完全二叉樹,這棵二叉樹滿足,任何一個非葉節點的值都不大於(或不小於)其左右孩子節點的值。 
這里寫圖片描述

2. 堆的存儲 

一般用數組來表示堆,若根節點存在於序號0處,i結點的父結點下表就為(i-1)/2,i結點的左右子結點下標分別為2i+1和2i+2

3. 堆排序思想 

利用大頂堆(小頂堆)堆頂記錄的是最大關鍵字(最小關鍵字)這一特性,使得每次從無序中選擇最大記錄(最小記錄)變得簡單。

4. 堆排序的實現 

堆排序需要解決兩個問題:

  • 如何由一個無序序列建成一個堆
  • 如何在輸出堆頂元素之后,調整剩余元素成為一個新的堆?

針對第二個問題:一般在輸出堆頂元素之后,視為將這個元素排除,然后用表中最后一個元素填補它的位置,自上向下進行調整:首先將堆頂元素和它的左右子樹的根結點進行比較,把最小的元素交換到堆頂;然后順着被破壞的路徑一路調整下去,直至葉子結點,就得到新的堆。

Step 1: 構造初始堆

初始化堆時是對所有的非葉子結點進行篩選 
最后一個非終端元素的下標是[n/2]向下取整,所以篩選只需要從第[n/2]向下取整個元素開始,從后往前進行調整。

Step 2:進行堆排序

堆排序是一種選擇排序。建立的初始堆為初始的無序區。

排序開始,首先輸出堆頂元素(因為它是最值),將堆頂元素和最后一個元素交換,這樣,第n個位置(即最后一個位置)作為有序區,前n-1個位置仍是無序區,對無序區進行調整,得到堆之后,再交換堆頂和最后一個元素,這樣有序區長度變為2。。。

不斷進行此操作,將剩下的元素重新調整為堆,然后輸出堆頂元素到有序區。每次交換都導致無序區-1,有序區+1。不斷重復此過程直到有序區長度增長為n-1,排序完成。

5. 堆排序實例 

1)首先,建立初始的堆結構圖 
這里寫圖片描述 
2)然后,交換堆頂的元素和最后一個元素,此時最后一個位置作為有序區(有序區顯示為黃色),然后進行其他無序區的堆調整,重新得到大頂堆后,交換堆頂和倒數第二個元素的位置…… 
這里寫圖片描述 
3)重復此過程 
這里寫圖片描述 
4)最后,有序區擴展完成即排序完成 
這里寫圖片描述

由排序過程可見,若想得到升序,則建立大頂堆,若想得到降序,則建立小頂堆。

6. 堆排序分析 

穩定性: 
時間復雜度:O(nlogn) 
空間復雜度:

堆排序方法對記錄數較少的文件並不值得提倡,但對n較大的文件還是很有效的。因為其運行時間主要耗費在建初始堆和調整建新堆時進行的反復“篩選”上。

二: Python代碼

簡潔版:

def sift_down(arr, start, end):
    root = start
    while True:
        # 從root開始對最大堆調整
        child = 2 * root + 1
        if child > end:
            break

        # 找出兩個child中交大的一個
        if child + 1 <= end and arr[child] < arr[child + 1]:
            child += 1

        if arr[root] < arr[child]:
            # 最大堆小於較大的child, 交換順序
            arr[root], arr[child] = arr[child], arr[root]

            # 正在調整的節點設置為root
            root = child
        else:
            # 無需調整的時候, 退出
            break


def heap_sort(arr):
    # 從最后一個有子節點的孩子來調整最大堆
    first = len(arr) // 2 - 1
    for start in range(first, -1, -1):
        sift_down(arr, start, len(arr) - 1)

    # 將最大的放到堆的最后一個, 堆-1, 繼續調整排序
    for end in range(len(arr) -1, 0, -1):
        arr[0], arr[end] = arr[end], arr[0]
        sift_down(arr, 0, end - 1)
View Code

 

注釋版:

def sift_down(arr, start, end):
    root = start
    while True:
        # 從root開始對最大堆調整
        child = 2 * root + 1
        # child為root的左孩子
        if child > end:
            # 超出序列的范圍
            break

        # 找出兩個child中較大的一個
        if child + 1 <= end and arr[child] < arr[child + 1]:
            # 如果右孩子存在, 而且左孩子小於右孩子
            child += 1
            # 將child指向右孩子

        if arr[root] < arr[child]:
            # 如果root 小於 它的較大child
            arr[root], arr[child] = arr[child], arr[root]
            # root 與 child, 交換位置

            root = child
            # 正在調整的節點設置為root
        else:
            # 無需調整的時候, 退出
            break


def heap_sort(arr):

    # 從最后一個有子節點的節點 來 調整最大堆
    first = len(arr) // 2 - 1
    # first: 最后一個有子節點的節點的下標
    for start in range(first, -1, -1):
        sift_down(arr, start, len(arr) - 1)

    # 將堆內的首節點存儲到有序區
    for end in range(len(arr) -1, 0, -1):

        arr[0], arr[end] = arr[end], arr[0]
        sift_down(arr, 0, end - 1)

        """
            有序區: 從尾節點的位置, 根據循環的次數, 依次向前移動, 直到堆為空, 即: 有序區的長度為堆的原始長度
            
            1. for end in range(len(arr)-1, 0, -1)
                len(arr)-1: arr內的最后一個節點的下標
                end: 從arr的最后一個節點開始遍歷, 即: 將arr倒序遍歷
            
            2. arr[0], arr[end] = arr[end], arr[0]
                
                將arr內的首節點, 存儲到有序區
                1. 
                    arr[0]: 
                    root節點, 堆頂, arr內的第一個元素,
                    如果arr是大頂堆, 則是arr內的最大元素,
                    如果arr是小頂堆, 則是arr內的最小元素
                
                2. 
                    arr[end]:
                    arr內的尾節點, 有序區的首節點 
                    根據循環的次數, 位置依次向前移動
            
        """



if __name__ == "__main__":
    l = list(i for i in range(0, 1000))
    print("洗牌之前的列表:" + str(l))
    random.shuffle(l)
    print("洗牌之后的列表:" + str(l))
    heap_sort(l)
    print(l)
View Code

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM