堆排、python實現堆排


一、堆-完全二叉樹
  • 堆排序是利用堆這種數據結構而設計的一種排序算法,堆排序是一種選擇排序,它的最壞,最好,平均時間復雜度均為O(nlogn),是不穩定排序

  • 堆排序中的堆有大頂堆、小頂堆兩種。他們都是完全二叉樹

  • 將該堆按照排序放入列表

1. 大頂堆:
所有的父節點的值都比孩子節點大,葉子節點值最小。root 根節點是第一個節點值最大

2. 小頂堆:
和大頂堆相反,所有父節點值,都小於子節點值,root 根節點是 第一個節點值最小

 

二、堆排序
  • 基本思路:將待排序序列構造成一個大頂堆,此時,整個序列的最大值就是堆頂的根節點。將其與末尾元素進行交換,此時末尾就為最大值。可稱為有序區,然后將剩余n-1個元素重新構造成一個堆,估且稱為堆區(未排序)。這樣會得到n個元素的次小值。重復執行,有序區從:1--->n堆區:n-->0,便能得到一個有序序列了
     
2.1 構造大頂堆
  • 在構造有序堆時,開始時只需要掃描一半的元素(所有父節點)(length/2-1 --> 0)
    因為只有他們才有子節點:3-->2 -->1 -->0

1. 從最后一個父節點開始,將父節點、他所有的子節點中的最大值交換到父節點。父節點:3

2. 將倒數第二個父節點同理交換,父節點:2

3. 父節點:1

4. 根節點:0

5. 注意很重要:務必注意-承接第3步。
假設根節點值為:10, 當他和兩個子節點70, 80,

 
父節點和兩子節點中的大的(80)交換后位於父節點2:原來80的位置。

 
可是他還有子節點,且子節點中的值比根節點大,那就還需要以他為父節點構造一次,與子節點6 值為20交換一次

同理在其他所有父節點的構造中都需要判斷調整

  • 忽略第五步。構造好的的大頂堆如下:

 

2.2 開始排序
  • 基本思路:將待排序序列構造成一個大頂堆,此時,整個序列的最大值就是堆頂的根節點。將其與末尾元素進行交換,此時末尾就為最大值。可稱為有序區,然后將剩余n-1個元素重新構造成一個堆,估且稱為堆區(未排序)。這樣會得到n個元素的次小值。重復執行,有序區從:1--->n堆區:n-->0,便能得到一個有序序列了

  • 每次將堆頂(根節點)最的的元素和堆尾列表最后一個元素交換,80 和40交換
    即上面說的堆區(未排序):n-->0最大元素(根節點),和有序區從:1--->n,最后一個元素交換

  • 按照上面原理繼續排序,70, 30 交換。然后調整堆

  • 堆頂元素60尾元素20交換后-->調整堆

  • 最后結果

三、Python代碼實現
 
  • 現在排序這么一個序列:list_ = [4, 7, 0, 9, 1, 5, 3, 3, 2, 6]
"""
堆排序  heap_sort

                     4
                   /   \
                 7      0
               /  \    / \
             9    1   5   3
           / \   /
         3   2  6

list_ = [4, 7, 0, 9, 1, 5, 3, 3, 2, 6]
"""

 

3.2 代碼實現
 
def swap(data, root, last):
    data[root], data[last] = data[last], data[root]

#調整父節點 與孩子大小, 制作大頂堆
def adjust_heap(data, par_node, high):

    new_par_node = par_node
    j = 2*par_node +1   #取根節點的左孩子, 如果只有一個孩子 high就是左孩子,如果有兩個孩子 high 就是右孩子

    while j <= high: #如果 j = high 說明沒有右孩子,high就是左孩子
        if j < high and data[j] < data[j+1]: #如果這兒不判斷 j < high 可能超出索引
            # 一個根節點下,如果有兩個孩子,將 j  指向值大的那個孩子
            j += 1
        if data[j] > data[new_par_node]: #如果子節點值大於父節點,就互相交換
            data[new_par_node], data[j] = data[j], data[new_par_node]
            new_par_node = j #將當前節點,作為父節點,查找他的子樹
            j = j * 2 + 1

        else:
            # 因為調整是從上到下,所以下面的所有子樹肯定是排序好了的,
            #如果調整的父節點依然比下面最大的子節點大,就直接打斷循環,堆已經調整好了的
            break


# 索引計算: 0 -->1 --->....
#    父節點 i   左子節點:2i +1  右子節點:2i +2  注意:當用長度表示最后一個葉子節點時 記得 -1
#    即 2i + 1 = length - 1 或者 2i + 2 = length - 1
#    2i+1 + 1 = length 或 2i+2 + 1 = length
#    2(i+1)=length 或 2(i+1)+1 = length
#    設j = i+1  則左子節點(偶數):2j = length 和 右子節點(基數):2j+1 = length
#    2j//2 = j == (2j+1)//2 這兩個的整除是一樣的,所以使用length//2 = j 然后 i + 1 = j
#    i = j-1  = length//2 -1  #注意左子節點:2i+1 //2 =i  而右子節點:(2i+2)//2 = i+1 

# 從第一個非葉子節點(即最后一個父節點)開始,即 list_.length//2 -1(len(list_)//2 - 1)

# 開始循環到 root 索引為:0 的第一個根節點, 將所有的根-葉子 調整好,成為一個 大頂堆
def heap_sort(lst):
    """
    根據列表長度,找到最后一個非葉子節點,開始循化到 root 根節點,制作 大頂堆
    :param lst: 將列表傳入
    :return:
    """
    length = len(lst)
    last = length -1  #最后一個元素的 索引
    last_par_node = length//2 -1

    while last_par_node >= 0:

        adjust_heap(lst, last_par_node, length-1)
        last_par_node -= 1  #每調整好一個節點,從后往前移動一個節點

    # return lst

    while last > 0:
        #swap(lst, 0, last)
        lst[0], lst[last] = lst[last],lst[0]
        # 調整堆讓 adjust 處理,最后已經排好序的數,就不處理了
        adjust_heap(lst, 0, last-1)
        last -= 1

    return lst #將列表返回



if __name__ == '__main__':
    list_ = [4, 7, 0, 9, 1, 5, 3, 3, 2, 6]
    heap_sort(list_)
    print(list_)


#最后結果為:
[0, 1, 2, 3, 3, 4, 5, 6, 7, 9]


免責聲明!

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



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