Python排序--堆排序

堆排序Heap Sort

  • 堆是一個完全二叉樹
  • 每個非葉子結點都要大於或者等於其左右結點的值稱爲大頂堆
  • 每個非葉子節點都小於或者等於其左右結點的值稱爲小頂堆
  • 根結點一定是大頂堆中最大的值,一定是小頂堆中最小的值

大頂堆

  • 完全二叉樹的每個非葉子結點都要大於或者等於其左右孩子結點的值稱爲大頂堆
  • 根結點一定是大頂堆中的最大值
    這裏寫圖片描述

小頂堆

  • 每個非葉子節點都小於或者等於其左右結點的值稱爲小頂堆
  • 根結點一定是小頂堆中的最小值
    這裏寫圖片描述

堆排序實現原理

堆排序原理

  1. 構建完全二叉樹
    1. 待排序數字爲 6,5,3,1,8,7,2,4
    2. 構建一個完全二叉樹存放數據,並根據性質5對元素編號,放入順序的數據結構中
    3. 構建一個列表爲[0,6,5,3,1,8,7,2,4]
  2. 構建大頂堆-排序核心算法
    1. 度數爲2的結點,如果它的左右孩子結點的最大值比它大,將這個最大值和該結點交換
    2. 度數爲1的結點,如果它的左孩子大於它,則交換
    3. 如果結點被交換到新的位置,還需要和其孩子結點重複上面的過程
    4. 從完全二叉樹的最後一個結點的雙親結點開始,即最後一層的最右邊葉子結點的父節點開始
    5. 接點數爲n,則起始結點的編號爲n//2(性質5)
    6. 從起始結點向坐找其同層結點,到頭後再從上一層的最右邊開始繼續向左逐個查找,直至根結點
    7. 確保每個結點的都比其左右孩子的值大
  3. 排序
    1. 將大頂堆根結點的最大值和最後一個葉子結點交換,最後一個葉子結點成爲最大值,將這個葉子結點排除在待排序結點之外
    2. 從根結點開始(交換後的新的根結點),重新調整爲大頂堆後,重複上一步

代碼實現

這裏寫圖片描述
生成列表

這裏寫圖片描述
heap_main 函數實現了單次比較交換
這裏寫圖片描述

loop_heap 函數實現了重複交換排序,直至形成大頂堆
這裏寫圖片描述
swop 函數每次都將大頂堆的堆頂和最後一個結點交換,然後排除最後一個元素
在繼續調整形成大頂堆
不斷的重複,直至剩餘一個爲排序的元素

總結

  • 利用堆性質的一種選擇排序,在堆頂選出最大值或者最小值
  • 時間複雜度
    • 堆排序的時間複雜度爲O(nlogn)
    • 由於堆排序對原始數據的排序狀態並不敏感,因此它無論原始排序狀態的好壞,時間複雜度均爲O(nlogn)
  • 空間複雜度
    • 只使用了一個交換空間,複雜度爲O(1)
  • 穩定性
    • 不穩定的排序算法

擴展

打印列表形式,不容易發現進行交換的改變
所以通過一個函數將數據格式化輸出成二叉樹的形式
這裏寫圖片描述

完整代碼

origin = [0,6,5,3,1,8,7,2,4]    #爲了和編號對應,添加元素 0 在首位
total = len(origin) - 1 #初始待排序元素個數
import math


def print_tree(arr, unit_width=2):
    length = len(arr)
    depth = math.ceil(math.log2(length))

    index = 1

    width = 2 ** depth - 1
    for i in range(depth):
        for j in range(2 ** i):
            print('{:^{}}'.format(arr[index], width * unit_width), end=' '*unit_width)
            index += 1
            if index >= length:
                break
        width = width // 2
        print()

print('排序前')
print('________________________________')
print_tree(origin)


def heap_main(n,i,arr):
    '''

    :param n: 待比較數的個數
    :param i: 當前節點的下標
    :param arr: 待排序數據
    :return: None
    '''
    while 2 * i <= n:
        #   孩子結點判斷 2i 爲左孩子,2i+1 爲右孩子
        lkid_index = 2 * i
        max_kid_index = lkid_index
        if n > lkid_index and arr[lkid_index + 1] > arr[lkid_index]:
            # n > 2i 說明有有孩子,判斷左右孩子誰最大
            max_kid_index = lkid_index + 1

        if arr[max_kid_index] > arr[i]:
            # 最大孩子和父節點比較
            arr[i], arr[max_kid_index] = arr[max_kid_index], arr[i]
            i = max_kid_index   #被交換後,需要判斷是否還需要進行調整

        else:
            break


def loop_heap(total, arr):
    for i in range(total//2, 0, -1):
        # 找出所有的結點,從最後的結點開始,直到根結點
        heap_main(total, i, arr)
    return arr


def swop(total, arr):
    while total > 1: #重複交換和排序,直至待排序的元素剩餘一個
        arr[1], arr[total] = arr[total], arr[1]
        # 堆頂和最後一個結點交換
        total -= 1
        heap_main(total,1,arr)
        # 對破壞都的隊進行重新排序
    return arr


loop_heap(total, origin)
swop(total, origin)
print('排序後')
print('________________________________')
print_tree(origin)

這裏寫圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章