1、什麼是堆
“(二叉)堆是一個數組,它可以被看成一個近似的完全二叉樹。樹上的每一個節點對應數組中的一個元素。除了最底層外,該樹是完全充滿的,而且是從左到右填充”。這是算法導論裏對堆的定義,說得很清晰明瞭,當然最好還是得上個圖,所以這裏就順便也從算法導論裏盜個圖了。。。。
二叉堆可以分爲兩種形式,最大堆和最小堆。在這兩種堆中,節點的值都要滿足堆的性質,同時在最大堆中,除了根以外的所有節點 都要滿足:
也就是說某個節點的值至多與其父節點一樣大。當然,最小堆就反過來就是了。上面的示意圖就是一個最大堆。
在這次的堆排序中,我們使用的是最大堆來對數組進行排序。
2、實現原理
堆排序的核心在於最大堆的根節點必然是數組的最大值,只要我們不斷地取出最大堆的根節點然後讓餘下的數組組成新的最大堆,這樣就可以不斷地取出剩餘數組中的最大值,從而實現對數組的倒序排列。
而要實現這樣的原理,核心就是將一個數組排序成最大堆的形式。而根據最大堆的性質,即父節點 下標的值大於等於其兩個子節點 與 (如果有的話)的下標的值。(PS:這裏的 指的是數組中的下標,從 開始,注意與堆上的節點數目有所區別,單純從堆的節點數目來說, 的子節點應該是 和 。)更具這個性質,我們每次可以只考慮原始數組所形成的無序的堆中一個父節點和它的子節點這最多三個值的大小,找出其最大值作爲這三個節點中的父節點,而這個父節點又是它上一層的一個節點的子節點(除非它是根節點),這樣我們可以從最下層開始,不斷地找出最大值並向上傳遞,直到將其傳遞給根節點,這樣我們就將整個數組的最大值傳遞到了數組的第一個元素(根節點的位置)。
話說這樣講起來可能很枯燥,我們用堆示意圖的變化來表示下這樣的一個過程。
3、堆排序的python實現
①實現下標爲 的元素找到在堆中的正確位置
#讓list0[i]找到在堆中的正確位置
def find_position(list0,i):
#l,r分別爲list0[i]的左右兩個子節點的下標值
l=2*i+1
r=2*i+2
length=len(list0)
lar=i
#找到list0[i]和它兩個子節點的最大值,下標值標記爲lar
if l<length:
if list0[l]>list0[i]:
lar=l
else:
lar=i
if r<length:
if list0[r]>list0[lar]:
lar=r
#如果list0[i]沒有站在正確的位置上(它的某個子節點比它大)
if lar!=i:
#交換最大值和list0[i]的位置,使最大值成爲父節點
x=list0[lar]
list0[lar]=list0[i]
list0[i]=x
#接着讓list0[i]接續循環,直到i==lar,即list0[i]站到了正確的位置上
find_position(list0,lar)
else:
return list0
②接下來通過對函數find_position的循環來讓一個數組中所有的元素都在堆中找到自己的正確位置,從而實現讓這個數組按照最大堆的順序進行排列。這裏需要注意的一點就是因爲每個元素都是和它的子節點或者父節點進行比較,從而得出最大值,所以我們不需要讓函數 find_position逐個調用所有的元素,我們只要調用所有的“枝”,即所有的父節點就行。
具體python代碼實現如下。
def build_heap(list0):
length=len(list0)
#因爲是使用父節點和它的子節點比較,讓最大值成爲父節點,相當於是向上傳遞最大值,所以我們從最下面
#的父節點開始循環,最後面的父節點下標爲⌊length/2⌋-1
for i in range(length//2-1,-1,-1):
find_position(list0,i)
③最後一步就是每次將根節點的元素(整個堆中的最大值)和最後面的元素調換位置,讓這個“葉”節點成爲整個根節點,成爲這個堆中第二個和第三個大的元素的父節點,然後我們再調用find_position函數,讓這個站錯位置的“葉”節點找到自己的位置,從而讓它的兩個子節點(整個堆中第二大和第三大的元素)競爭最大值,併成功上位成根節點,至於“葉”節點的位置找到哪去了我們並不關心。就這樣不斷地抽取其根節點,即不斷地抽取剩下的元素的最大值,從而實現排序的目的。
具體的python代碼實現如下。
def deap_sort(list0):
#首先讓無序的list0排列爲最大堆
build_heap(list0)
#list_res用來取得每個堆的最大值
list_res=[]
length=len(list0)
#循環尋找最大值並截取
for i in range(length-1,0,-1):
#list_res截取根節點的元素list0[0]
list_res.append(list0[0])
#交換根節點的和最後一個節點的元素的值
x=list0[0]
list0[0]=list0[i]
list0[i]=x
#刪除最後一個節點元素(數組的最大值)
del(list0[-1])
#繼續尋找剩下的元素中的最大值,然後循環截取最大值
find_position(list0,0)
#取得最後剩下的那一個最小值
list_res.append(list0[-1])
return list_res
4、總結
堆排序算法的時間複雜度爲 ,具體的計算過程這裏不詳細敘述,推薦去找一下堆排序算法複雜度的計算過程,還是很有意思的。這個排序算法的效率還是很不錯的。總的來說其核心就是通過構建最大堆不斷找出堆的最大值(根節點),從而抽取最大值最終達到倒序排列的效果。