堆排序-python

復(fù)習(xí)之前學(xué)過的堆排序,發(fā)現(xiàn)掌握的不是特別牢固,又仔細(xì)閱讀了幾篇博文,整理出來這篇記錄。

1 堆排序介紹

1.1 與堆有關(guān)的概念

二叉樹:每個節(jié)點最多有兩個子節(jié)點的樹。
完全二叉樹:除了最后一層之外,每一層都被完全填充并且最后一層左對齊的二叉樹。
最大堆:每個父節(jié)點都大于孩子節(jié)點的完全二叉樹。
最小堆:每個父節(jié)點都小于孩子節(jié)點的完全二叉樹。

這里我們討論最大堆,在最大堆中,如果節(jié)點標(biāo)號從 1 開始,滿足如下條件:

  • 父節(jié)點標(biāo)號為 i ,左孩子節(jié)點標(biāo)號為 i*2, 右孩子節(jié)點標(biāo)號為 i*2 + 1。
  • 最后一個節(jié)點標(biāo)號為 j ,則它的父節(jié)點的標(biāo)號為 j // 2,也是最后一個有子節(jié)點。

1.2 堆排序

給定一個列表[90,50,80,16,30,60,70,10,2],在上面講到,節(jié)點標(biāo)號從1開始滿足某些性質(zhì)方便表示,為了和下標(biāo)索引對應(yīng)起來,在列表最前端添加一個占位元素0,此時列表變?yōu)?code>[0,90,50,80,16,30,60,70,10,2]。
堆排序主要步驟如下:

  • 首先我們將列表調(diào)整成一個最大堆。
  • 此時堆首的元素最大,將它與堆最有一個元素交換。
  • 將剩下的元素調(diào)整成一個最大堆。
  • 循環(huán)執(zhí)行,直到需要調(diào)整的堆中只有一個元素。

2堆排序步驟詳解

  • 將列表調(diào)整為一個最大堆,得到結(jié)果如下:


    初始的最大堆
  • 將最大的堆頂元素與最后一個元素交換,即902交換,得到如下結(jié)果:

    將90與2交換后的堆

  • 此時除了90剩下的元素構(gòu)成的堆已經(jīng)不是最大堆,調(diào)整剩下的元素,使它成為最大堆


    調(diào)整元素,使它成為最大堆
  • 調(diào)整結(jié)果如下,然后繼續(xù)重復(fù)上述步驟,直到堆中只有一個元素。


    剩下的元素變?yōu)樽畲蠖?/div>

3 代碼

整體的排序分為 2 塊:

  • 初始化最大堆
  • 交換堆頂與堆尾元素
    注:
    1.在初始化最大堆的過程中,從最后一個有子節(jié)點開始,也就是從最后一個極小堆開始
    2.在隊首加入占位元素,如果直接用python內(nèi)置的list,用.insert(0)方法時間復(fù)雜度為O(n),而用單鏈表時間復(fù)雜度僅為O(1)。文件頭需要加入from collections import deque
def heap_sort(l):
    # 加入占位元素
    L = deque(l)
    L.appendleft(0)
    
    Length = len(L) - 1 #最大索引下標(biāo)
    first_sort_count = Length // 2 # 最后一個有子節(jié)點索引下標(biāo)
    # 初始化最大堆, 從最后一個有子節(jié)點開始
    for i in range(first_sort_count):
        heap_adjust(L, first_sort_count - i, Length)
        
    # 開始排序
    for i in range(Length-1):
        swap_param(L, 1, Length-i)
        heap_adjust(L, 1, Length-i-1)
        
    return [L[i] for i in range(1, len(L))]
def heap_adjust(L, start, end):
    tmp = L[start]
    i = start
    j = i*2
    
    while j <= end:
        if (j<end) and (L[j]<L[j+1]):
            j += 1
        if tmp < L[j]:
            L[i] = L[j]
            i = j
            j = i*2
        else:
            break
    
    L[i] = tmp
def swap_param(L, i, j):
    L[i], L[j] = L[j], L[i]

4 時間和空間復(fù)雜度

空間復(fù)雜度:O(1)。就地排序,全程并沒有使用額外的空間。

時間復(fù)雜度:O(nlogn)。
分為初始化堆過程和每次選取最大數(shù)后重新建堆的過程。
1.初始化建堆過程時間:O(n)

首先要理解怎么計算這個堆化
假設(shè)高度為k,則從倒數(shù)第二層右邊的節(jié)點開始,這一層的節(jié)點都要執(zhí)行子節(jié)點比較然后交換(如果順序是對的就不用交換);倒數(shù)第三層呢,則會選擇其子節(jié)點進行比較和交換,如果沒交換就可以不用再執(zhí)行下去了。如果交換了,那么又要選擇一支子樹進行比較和交換;
那么第 i 層的時間計算為:s = 2^( i - 1 ) * ( k - i );其中 i 表示第幾層,2^( i - 1) 表示該層上有多少個元素,( k - i) 表示子樹上要比較的次數(shù),如果在最差的條件下,就是比較次數(shù)后還要交換;因為這個是常數(shù),所以提出來后可以忽略;

S = 2^(k-2) * 1 + 2^(k-3)2+.....+2(k-2) + 2^(0)*(k-1) ===> 因為葉子層不用交換,所以i從 k-1 開始到 1;
這個等式求解:等式左右乘上2,然后和原來的等式相減,就變成了:
S = 2^(k - 1) + 2^(k - 2) + 2^(k - 3) ..... + 2 - (k-1)
除最后一項外,就是一個等比數(shù)列了,直接用求和公式:S = {a1[ 1- (q^n) ] } / (1-q);

S = 2^k -k -1;又因為k為完全二叉樹的深度,所以 (2^k) <= n < (2^k -1 ),總之可以認(rèn)為:k = logn
綜上所述得到:S = n - longn -1,所以時間復(fù)雜度為:O(n)。

2.重構(gòu)最大堆過程
在取出堆頂點放到對應(yīng)位置并把原堆的最后一個節(jié)點填充到堆頂點之后,需要對堆進行重建,只需要對堆的頂點調(diào)用heap_adjust()函數(shù)。
  每次重建意味著有一個節(jié)點出堆,所以需要將堆的容量減一。heap_adjust()函數(shù)的時間復(fù)雜度k=log(n),k為堆的層數(shù)。所以在每次重建時,隨著堆的容量的減小,層數(shù)會下降,函數(shù)時間復(fù)雜度會變化。重建堆一共需要n-1次循環(huán),每次循環(huán)的比較次數(shù)為log(i),則相加為:log2+log3+…+log(n-1)+log(n)≈log(n!)??梢宰C明log(n!)和nlog(n)是同階函數(shù),所以時間復(fù)雜度為O(nlogn)。

最終的時間復(fù)雜度為O(nlogn)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 選擇排序 每次在n個記錄中選擇一個最小的需要比較n-1次,但是這樣的操作并沒有把每一趟的比較結(jié)果保存下來,在后一趟...
    wlj1107閱讀 1,771評論 0 2
  • 在C語言中,五種基本數(shù)據(jù)類型存儲空間長度的排列順序是: A)char B)char=int<=float C)ch...
    夏天再來閱讀 4,081評論 0 2
  • 1.插入排序—直接插入排序(Straight Insertion Sort) 基本思想: 將一個記錄插入到已排序好...
    依依玖玥閱讀 1,356評論 0 2
  • 旅行的第三天,早早的就是睡不著了。 今天是要離開的日子,有人說旅行越到終點的時候越會百感交集。一個美好的地方常常帶...
    羊的三腳貓閱讀 293評論 0 0
  • 禁煙簽名現(xiàn)場 合影留念 根據(jù)世界衛(wèi)生組織統(tǒng)計,目前大學(xué)生吸煙人數(shù)正逐年增長,為了讓更多的人了解吸煙的危害,讓同學(xué)們...
    蘭州理工大學(xué)管理員閱讀 263評論 0 0

友情鏈接更多精彩內(nèi)容