堆排序-python實現

選擇排序 每次在n個記錄中選擇一個最小的需要比較n-1次,但是這樣的操作并沒有把每一趟的比較結果保存下來,在后一趟的比較中,有許多的比較在前一趟就已經做過了,但是由于前一趟排序時并未保存這些比較結果,所以后一趟排序時又重復執行了這些比較操作,因而記錄的比較次數比較多
如果可以做到每次在選擇最小記錄時,并根據比較結果對其他記錄做出相應的調整,那樣排序的總體效率就會非常高了,而堆排序就是對簡單選擇排序進行的一種改進,這種改進的效率是非常明顯的

一.堆結構
1.堆是具有下列性質的完全二叉樹:
(1)每個結點的值都大于或等于其左右孩子結點的值,稱為大頂堆;
(2)或者每個結點的值都小于或等于其左右孩子結點的值,稱為小頂堆;
從堆的定義可知:根節點一定是堆中所有結點中共的最大值或者最小值

2.按照層序遍歷的方式給結點進行編號,那么非葉結點的編號滿足如下關系:
1<=i<=[n/2] [n/2]表示不超過n/2的最大整數
因為完全二叉樹的性質:(這里的i指的是編號)
(1)如果2i>n,那么這個i對應的節點是葉節點,且沒有左孩子,反之,我們知道不是葉節點的節點就滿足2i<=n,即得到了上面的表達式
(2)編號為i的節點的左右子節點編號分別是2i和2i+1
那么按照層序遍歷的方式,將最大堆和最小堆存入數組,那么一定滿足上面的關系

二.堆排序算法
1.基本思想
將待排序的序列構造成一個大頂堆,此時,整個序列的最大值就是堆頂的根節點,將它移走,然后將剩余的n-1個序列重新構造一個堆,這樣就會得到n個元素中的次大值,如此反復執行,便能得到一個有序序列了
那么實現這個思想要解決兩個問題
(1)如何由一個無序序列構建成一個堆
(2)在輸出堆頂元素后,如何調整剩余元素稱為一個新的堆

2.代碼實現思路:
(1)無序序列調整為大頂堆
調整為大頂堆主要是遍歷非葉子節點,對每個非葉子節點都找到其和左右子樹中的最大值,然后調換順序,調整最大值到雙親節點,按照這個過程,可以將一個無序序列調整為完全二叉樹。
復雜度分析
非葉子節點的個數大約為所有節點個數的一半,而每個非葉子節點的處理時間都是常量時間,因此時間復雜度為O(n)

(2)排序:
外循環:i=1,2,,,k:
第i次循環,將堆頂元素和倒數第i個元素交換,然后對前面的n-i個元素調整為最大堆,這里的調整略和上面的不同(原理,可參照算法導論),每次循環是從根節點到最大編號的葉節點進行遍歷,調整每個節點和其子節點的最大值到雙親節點,每一次循環過后,序列的最后i個元素都是有序的
復雜度分析
每次外循環從根節點到最后一層的葉節點只需要經過log(n-i)次內循環,如果我們需要得到序列的全排序,復雜度計算就是logn+log(n-1)+log(n-2)+...+log1=log(n!),根據時間復雜度計算原則:只保留最高次方的,去掉常數系數,調整n個無序元素為有序的時間復雜度為O(log(n^n))=O(nlogn);
如果是topk,那么我們只需要外循環k次,那么復雜度就是logn+log(n-1)+log(n-2)+...log(n-k)=O(log(n^k))=klogn

"""
heap-sort
"""
def heap_sort(lst,k):
    """
    contrust a max-heap based on given array ranging from the last non-leaf node to root node
    in python:non-leaf node number reduce from half of the last index of the array minus 1 to 0
    """
    for i in range((len(lst)-1-1)/2,-1,-1):
        heap_adjust(lst,i,len(lst)-1)
    print "\n"
    """
    put the the top of the heap in the end of array in turn,then re-construct a max-heap
    finally we can get a array whose element is sorted from small to large,if we only get
    the top k ,then we iterate k times ,in other words,we need put the top of the heap in
    the end k times
    """
    for j in range(len(lst)-1, len(lst)-1-k, -1):
        """
        swap the top of the heap and the last of unordered part ,if we iterate
        k times ,then we get a array whose the last k elements is the top k
        """
        # print lst[0]
        lst[j], lst[0]=lst[0], lst[j]
        heap_adjust(lst, 0, j-1)

def heap_adjust(lst,s,m):
    """
    re-contruct a max-heap from s to m based on array
    """
    i = 2*s+1 #the left node of the s
    temp=lst[s]
    while i <= m:
        if (i < m) and (lst[i] < lst[i+1]):
            i = i+1
        if temp >= lst[i]:
            break
        else:
            lst[s] = lst[i]
        s = i
        i=i*2+1
        # print lst
    lst[s]=temp

if __name__=="__main__":
    sequence1=[50,10,90,30,70,40,80,60,20]
    k=5
    heap_sort(sequence1,k)
    topk=sequence1[-k:len(sequence1)]
    topk.reverse()
    print "topk:"+str(topk)
運行結果

2. 堆排序的應用
海量數據的topk,即得到海量數據的topk元素
(1)如果沒有內存限制,可以采用內排序,將數據全部加載進來進行排序,可以采用類冒泡排序的思想,循環k次,每次將第k大的元素調整到上面,內存循環用來比較大小和交換元素順序,復雜度是n-1+n-2+...+n-k)=O(kn)

#coding=UTF-8
"""
inner sort
"""
sequence = [-23,18,2,3,9,-4,5,7]
k = 5
for i in range(k):
    for j in range(i + 1, len(sequence)):
        if sequence[i] < sequence[j]:
            sequence[i], sequence[j] = sequence[j], sequence[i]
    print sequence
print "topk:"+str(sequence[:k - 1])
運行結果

復雜度還是比較大,我們可以采用堆排序的方法:

(2)如果在有內存限制的情況下,即我們無法將數據全部加載進來,只能采用外排序方法,這里我們仍然采用堆排序,但是和上面的堆排序思路和過程都不一樣,區別在于上面我們是將數據全部加載到內存,實現的全排序(k=len(sequence1),但是這里因為在不消耗內存的情況下:
先初始化一個k維的數組存放海量數據的前k個元素,然后將這個k個元素構建成一個最小堆;
循環以下過程:再從海量數據的第k+1個元素進行遍歷,每次比較前面的最小堆的根節點與后面的每個元素的大小,如果根節點元素小于后面的元素,那么將前面的根節點元素替換為這個元素;再重新調整這個數組為一個最小堆,這樣每次都會扔掉更小的元素,加進來更大的元素,直至遍歷完所有元素,得到的數組就是我們的topk
時間復雜度分析:一開始構建最小堆的復雜度是O(K),然后后面遍歷了n-K個元素,每次的復雜度是O(K),因此總復雜度是O(k+(n-k)logk)=O(nlogK)
空間復雜度分析:這里比上面的堆排序增加了一個K維的數組作為緩存topk元素
總的算法效率分析:減少了內存的消耗,空間復雜度和時間復雜度都比上面增加了
具體實現如下:

#coding=UTF-8
"""
heap-sort
"""
def heap_adjust(lst,s,m):
    i = 2*s+1 #the left node of the s
    temp=lst[s]
    while i <= m:
        if (i < m) and (lst[i] > lst[i+1]):
            i = i+1
        if temp <= lst[i]:
            break
        else:
            lst[s] = lst[i]
        s = i
        i=i*2+1
        # print lst
    lst[s]=temp

def heap_sort(lst,k):
    topk = []
    m=0
    while len(topk) < k:
        topk.append(lst[m])
        m+=1
    print "初始tok:"+str(topk)
    for i in range((k-1-1)/2,-1,-1):
        heap_adjust(topk,i,k-1)
    min_k=topk[0]
    print "初始最小值:"+str(min_k)
    print "初始topk構成的最小堆:"+str(topk)
    print "\n"
    for j in range(k,len(lst)):
        # print lst[0]
        if min_k<lst[j]:
            topk[0]=lst[j]
            for i in range((k-1-1)/2, -1, -1):
                heap_adjust(topk,i,k-1)
            min_k=topk[0]
        # print topk
    return topk

if __name__=="__main__":
    sequence1=[50,10,90,30,70,40,80,60,20]
    k=5
    print "最后得到的topk:"+str(heap_sort(sequence1,k))
運行結果

可以看出來:這里得到的topk不是內部排序的,因為我們上面每次只是構建了最小堆,如果我們想要得到有序的topk,進一步實現如下:


if __name__=="__main__":
    sequence1=[50,10,90,30,70,40,80,60,20]
    k=5
    topk=heap_sort(sequence1,k)
    print "小頂堆tok:"+str(topk)
    for j in range(len(topk)-1,-1,-1):
        topk[0],topk[j]=topk[j],topk[0]
        heap_adjust(topk,0,j-1)
    print "有序的topk:"+str(topk)
運行結果

即在內存限制的情況下運用堆排序實現了海量數據的topk

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 概述 排序有內部排序和外部排序,內部排序是數據記錄在內存中進行排序,而外部排序是因排序的數據很大,一次不能容納全部...
    蟻前閱讀 5,220評論 0 52
  • 概述:排序有內部排序和外部排序,內部排序是數據記錄在內存中進行排序,而外部排序是因排序的數據很大,一次不能容納全部...
    每天刷兩次牙閱讀 3,743評論 0 15
  • 1.插入排序—直接插入排序(Straight Insertion Sort) 基本思想: 將一個記錄插入到已排序好...
    依依玖玥閱讀 1,282評論 0 2
  • 概述排序有內部排序和外部排序,內部排序是數據記錄在內存中進行排序,而外部排序是因排序的數據很大,一次不能容納全部的...
    Luc_閱讀 2,299評論 0 35
  • 親愛的自己: 您好! 我心疼你。你不要那么輕易就否定了自己,也不要忘了自己的初衷。你只是想還自己內心一片寧靜,喜歡...
    此喪非喪閱讀 202評論 0 0