1. 程式人生 > >排序演算法之堆排序(關鍵詞:資料結構/演算法/排序演算法/堆排序)

排序演算法之堆排序(關鍵詞:資料結構/演算法/排序演算法/堆排序)

假定:有 1 個亂序的數列 nums ,其中有 n 個數。
要求:排好序之後是 從小到大 的順序。

堆排序演算法

原理

  1. 先將原始的堆,調整為最大堆:
    從倒數第 1 個有子結點的結點(下標為 index = n//2 - 1)開始,將以結點 index 為根結點的子堆調整為最大堆;
    index 範圍是 n//2 - 1(含) 到 0(含);
    最後,原始的堆調整為最大堆。

  2. 將最大堆調整為(從小到大排列)有序的最小堆:
    a. 將最大堆的堆頂第 0 項與第 n-1 項交換位置,這樣,第 1 大值排在堆的尾部;
    b. 這樣,除開第 n-1 項的剩餘項組成的子堆,不是最大堆也不是最小堆,將這個子堆調整為最大堆;
    c. 重複上述過程 a、b;
    最終得到 1 個從小到大排列的最小堆。

關鍵程式碼的我的理解:

filter_down 函式:
特別要說明其中的 if-else。如果 根結點 的值 rootVal 較大,則不將其往下過濾(break);如果 rootVal 比子結點小,則將較大值往上移動,為 rootVal 騰出空間,最終將 rootVal 下濾。
值得注意的是,filter_down 函式本身,只是將堆中的最大項調整到頂部,較小項調整到尾部,並不會將 子堆 完全 調整為 最大堆,(結合測試用例中的 nums6、nums7 ,畫圖輔助理解。)
只有在 heap_sort 中,從後往前呼叫 filter_down 函式(第 1 個 for 迴圈),這樣的方式,才能得到完整的最大堆。

程式碼

# coding:utf-8

from swap import swap


"""
將二叉樹中的,以 nums[p] 為根的子堆,
調整為最大堆。
(下濾 根節點 nums[p])

p 是根節點所在的下標;
n 是當前堆一共有多少個元素。
"""
def filter_down(nums, p, n):
        ''' 取出根節點存放的值 '''
        rootVal = nums[p]

        parentIdx = p
        while 2*parentIdx+1 <= n-1:
                ''' kidIdx 暫時指向左兒子 '''
                kidIdx = 2*parentIdx+1

		''' 左兒子 kidIdx 不等於 n-1,即左兒子不是最後 1 個結點,
		也就是說,還有右兒子 '''
                if kidIdx != n-1 and nums[kidIdx] < nums[kidIdx+1]:
                        kidIdx += 1
                        ''' kidIdx 指向較大的子結點 '''

                ''' rootVal 找到了合適的位置 '''
                if rootVal >= nums[kidIdx]:
                        break
                ''' 如果 rootVal 比子結點小,
                則下濾 rootVal。
                (將較大值往上移動)'''
                else:
                        nums[parentIdx] = nums[kidIdx]

                ''' 將父節點的指標往下移動 '''
                parentIdx = kidIdx

        nums[parentIdx] = rootVal


def heap_sort(nums):
        n = len(nums)

        ''' 建立最大堆 '''
        for index in range(n//2-1, -1, -1):
                filter_down(nums, index, n)

        ''' 刪除最大堆的頂部的最大項
        將最大堆的最大項與最後 1 項交換位置,
        然後將除最後 1 項的剩餘部分,調整為最大堆;
        重複上面的操作。
        '''
        for index in range(n-1, 0, -1):
                swap(nums, 0, index)
                filter_down(nums, 0, index)


def test():
        nums0 = [1,2,3]
        nums1 = [1,3,2]
        nums2 = [2,1,3]
        nums3 = [2,3,1]
        nums4 = [3,1,2]
        nums5 = [3,2,1]
        nums6 = [5,1,6,3,4,8]
        nums7 = [6,1,8,3,4,5]
        for nums in (nums0, nums1, nums2, nums3, nums4, nums5, nums6, nums7):
                filter_down(nums, 0, len(nums))
        assert nums0 == [3,2,1]
        assert nums1 == [3,1,2]
        assert nums2 == [3,1,2]
        assert nums3 == [3,2,1]
        assert nums4 == [3,1,2]
        assert nums5 == [3,2,1]
        assert nums6 == [6,1,8,3,4,5]
        assert nums7 == [8,1,6,3,4,5]

        nums = [7,4,5,3,8,9]
        heap_sort(nums)
        assert nums == [3,4,5,7,8,9]
        print('Pass!')

演算法複雜度

時間複雜度:
最壞情況下 ;
最好情況下 ;
平均情況 。

空間複雜度:

穩定性

參考文獻

  1. 《資料結構(第 2 版)》 - 浙江大學 - P148——P151、P265——P267;
  2. 《資料結構與演算法 Python 語言描述》 - 裘宗燕 - 北京大學 - ;
  3. https://github.com/henry199101/sort/blob/master/heap_sort.py。