1. 程式人生 > >其它 >堆排序python實現及時間複雜度分析

堆排序python實現及時間複雜度分析


技術標籤:資料結構與演算法資料結構演算法堆排序

一、前言

堆:一種特殊的完全二叉樹結構。

堆排序python實現及時間複雜度分析

大根堆:一棵完全二叉樹,滿足任一節點都比其孩子節點大;

小根堆:一棵完全二叉樹,滿足任一節點都比其他孩子節點小。

二、堆排序

堆排序過程:

  1. 建立大根堆
  2. 將堆頂元素與最後一個元素進行交換
  3. 將交換後的堆,重新調整為大根堆
  4. 重複2,3步驟,直到全部順序排序

其中,建立大根堆和重新調整為大根堆的主體方法是相同的。其實現方法如下:

def adjust(lis,low,high):
    '''
    :param lis:除了根節點,其左右子樹都要滿足大根堆性質
    :param low: 最開始根節點的位置
    :param high:最後一個元素位置
    :return:
    '''
    i=low
    tmp=lis[i]
    j=2*i+1
    while j+1<=high:
        if lis[j]<lis[j+1]:
            j=j+1
        if lis[i]<lis[j]:
            lis[i],lis[j]=lis[j],lis[i]
            i = j
            j = 2 * i + 1
        else:
            break
    if j==high:
        if lis[j]>lis[i]:
            lis[j],lis[i]=lis[i],lis[j]
            i=j
    lis[i]=tmp
    return lis

建立大根堆的過程,其實就是從最後一個非葉子節點到根節點依次呼叫上述方法,具體實現方法如下:

# 建堆
def build_stack(lis):
    #從下往上調整每個子二叉樹,則最後就能得到一個大頂堆(或小頂堆)
    n=len(lis)
    for i in range ((n-2)//2,-1,-1):#最後一個非葉子節點的索引為(n-2)//2
        adjust(lis,i,n-1)
    return lis

最後排序過程,就是依次交換,再呼叫調整方法

#排序
def sort(lis):
    n=len(lis)
    for i in range(n-1,0,-1):#依次,根頂與最後的元素交換
        lis[i],lis[0]=lis[0],lis[i]
        adjust(lis,0,i-1)
    return lis

完成!

檢查:

if __name__ == '__main__':
    import random
    li = [i for i in range(1000)]
    random.shuffle(li)
    lis = build_stack(li)
    lis = sort(lis)
    print(lis)

三、時間複雜度分析

1、初始化建堆

初始化建堆只需要對二叉樹的非葉子節點呼叫adjust()函式,由下至上,由右至左選取非葉子節點來呼叫adjust()函式。那麼倒數第二層的最右邊的非葉子節點就是最後一個非葉子結點。

假設高度為k,則從倒數第二層右邊的節點開始,這一層的節點都要執行子節點比較然後交換(如果順序是對的就不用交換);倒數第三層呢,則會選擇其子節點進行比較和交換,如果沒交換就可以不用再執行下去了。如果交換了,那麼又要選擇一支子樹進行比較和交換;高層也是這樣逐漸遞迴。
  那麼總的時間計算為:s = 2^( i - 1 ) * ( k - i );其中 i 表示第幾層,2^( i - 1) 表示該層上有多少個元素,( k - i) 表示子樹上要下調比較的次數。
  S = 2^(k-2) * 1 + 2^(k-3)2…..+2(k-2)+2^(0)*(k-1) ===> 因為葉子層不用交換,所以i從 k-1 開始到 1;
  S = 2^k -k -1;又因為k為完全二叉樹的深度,而log(n) =k,把此式帶入;
  得到:S = n - log(n) -1,所以時間複雜度為:O(n)

2、排序重建堆

在取出堆頂點放到對應位置並把原堆的最後一個節點填充到堆頂點之後,需要對堆進行重建,只需要對堆的頂點呼叫adjustheap()函式。
  每次重建意味著有一個節點出堆,所以需要將堆的容量減一。adjustheap()函式的時間複雜度k=log(n),k為堆的層數。所以在每次重建時,隨著堆的容量的減小,層數會下降,函式時間複雜度會變化。重建堆一共需要n-1次迴圈,每次迴圈的比較次數為log(i),則相加為:log2+log3+…+log(n-1)+log(n)≈log(n!)。可以證明log(n!)和nlog(n)是同階函式:
  ∵(n/2)n/2≤n!≤nn,∵(n/2)n/2≤n!≤nn,
  ∴n/4log(n)=n/2log(n1/2)≤n/2log(n/2)≤log(n!)≤nlog(n)∴n/4log⁡(n)=n/2log⁡(n1/2)≤n/2log⁡(n/2)≤log⁡(n!)≤nlog⁡(n)
  所以時間複雜度為O(nlogn)

3、總結

初始化建堆的時間複雜度為O(n),排序重建堆的時間複雜度為nlog(n),所以總的時間複雜度為O(n+nlogn)=O(nlogn)。如果只排前k個元素,則時間複雜度為O(n+klogn)。另外堆排序的比較次數和序列的初始狀態有關,但只是在序列初始狀態為堆的情況下比較次數顯著減少,在序列有序或逆序的情況下比較次數不會發生明顯變化。