這節我們介紹堆排序。
、堆排序
在直接選擇排序中,順序表是乙個線性結構,要從有n個記錄的順序表中選擇出乙個最小的記錄需要比較n-1 次。如能把待排序的n個記錄構成乙個完全二叉樹結構,則每次選擇出乙個最大(或最小)的記錄比較的次數就是完全二叉樹的高度, 即log2n次, 則排序演算法的時間複雜度就是o (nlog2n) 。 這就是堆排序(heap sort)的基本思想。
堆分為最大堆和最小堆兩種。最大堆的定義如下:
設順序表sqlist中存放了n個記錄, 對於任意的i(0≤i≤n-1), 如果2i+1如果把這 n 個記錄看作是一棵完全二叉樹的結點,則 sqlist[0]對應完全二叉樹的根, sqlist[1]對應樹根的左孩子結點, sqlist[2]對應樹根的右孩子結點, sqlist[3]對應 sqlist[1]的左孩子結點,sqlist[4]對應 sqlist[2]的右孩子結點,如此等等。在此基礎上,只需調整所有非葉子結點滿足:sqlist[i] 的關鍵碼不小於 sqlist[2i+1] 的關鍵碼和 sqlist[i] 的關鍵碼不小於 sqlist[2i+2]
的關鍵碼,則這樣的完全二叉樹就是乙個最大堆。
下圖1(a)所示是一棵完全二叉樹,下圖1(b)所示是乙個最大堆。
類似地,最小堆的定義如下:
設順序表sqlist中存放了n個記錄, 對於任意的i(0≤i≤n-1), 如果2i+1
由堆的定義可知,堆有如下兩個性質:
(1)最大堆的根結點是堆中關鍵碼最大的結點,最小堆的根結點是堆中關鍵碼最小的結點,我們稱堆的根結點記錄為堆頂記錄。
(2)對於最大堆,從根結點到每個葉子結點的路徑上,結點組成的序列都是遞減有序的;對於最小堆,從根結點到每個葉子結點的路徑上,結點組成的序列都是遞增有序的。
堆排序的過程是:設有 n 個記錄,首先將這 n 個記錄按關鍵碼建成堆,將堆頂記錄輸出,得到 n 個記錄中關鍵碼最大(或最小)的記錄。然後,再把剩下的n-1 個記錄,輸出堆頂記錄,得到 n 個記錄中關鍵碼次大(或次小)的記錄。如此反覆,便可得到乙個按關鍵碼有序的序列。
因此,實現堆排序需解決兩個問題:
(1)如何將 n 個記錄的序列按關鍵碼建成堆;
(2)輸出堆頂記錄後,怎樣調整剩下的 n-1 個記錄,使其按關鍵碼成為乙個新堆。
首先,以最大堆為例討論第乙個問題:如何將 n 個記錄的序列按關鍵碼建成堆。
根據前面的定義,順序表 sqlist 中的 n 個記錄構成一棵完全二叉樹,所有的葉子結點都滿足最大堆的定義。 對於第 1 個非葉子結點 sqlist[i] (i=(n-1)/2) ,由於其左孩子結點 sqlist[2i+1]和右孩子結點 sqlist[2i+2]都已是最大堆,所以,只需首先找出 sqlist[2i+1]結點和 sqlist[2i+2]結點中關鍵碼的較大者,然後與 sqlist[i]結點的關鍵碼進行比較,如果 sqlist[i]結點的關鍵碼大於或等於這個較大的結點的關鍵碼,則以 sqlist[i]結點為根結點的完全二叉樹已滿足最大堆的定義;否則,對換 sqlist[i]結點和關鍵碼較大的結點,對換後以sqlist[i]結點為根結點的完全二叉樹滿足最大堆的定義。按照這樣的方法,再調整第 2 個非葉子結點 sqlist[i-1] (i=(n-1)/2) ,第 3 個非葉子結點sqlist[i-2],……,直到根結點。當根結點調整完後,則這棵完全二叉樹就是乙個最大堆了。
當要調整結點的左右孩子結點是葉子結點時,上述調整過程非常簡單;當要調整結點的左右孩子結點不是葉子結點時,上述調整過程要稍微複雜一些。因為調整過後,可能引起以左孩子結點(或右孩子結點)為根結點的完全二叉樹不是乙個最大堆,這時,需要調整以左孩子結點(或右孩子結點)為根結點的完全二叉樹,使之成為乙個最大堆.
下圖3
說明了如何把圖1(a)所示的完全二叉樹建成圖1(b)所示的最大堆的過程。
第一步:從 i=(n-1)/2=(7-1)/2=3 開始,sqlist[3]的關鍵碼 27 小於sqlist[7]的關鍵碼 48, 所以, sqlist[3]與 sqlist[7]交換, 這樣, 以 sqlist[3]為根結點的完全二叉樹是乙個最大堆,如圖(b)所示。
第二步:當 i=1 時,由於 sqlist[1]的關鍵碼 20 小於 sqlist[3]的關鍵碼48,所以將 sqlist[1]與 sqlist[3]交換,這樣導致 sqlist[3]的關鍵碼 20 小於sqlist[7]的關鍵碼 27, 所以將 sqlist[3]與 sqlist[7]交換, 這樣, 以 sqlist[1]為根結點的完全二叉樹是乙個最大堆,如圖(d)所示。
第三步:當 i=0 時,對堆頂結點記錄進行調整,由於 sqlist[0] 的關鍵碼42小於sqlist[1] 的關鍵碼 48,所以將 sqlist[0]與 sqlist[1]交換,這樣,
以 sqlist[0]為根結點的完全二叉樹是乙個最大堆,如圖(e)所示,整個堆建立的過程完成。
建堆的演算法如下所示,演算法中記錄的比較表示記錄關鍵碼的比較,順序表中只存放了記錄的關鍵碼。
//建立堆的演算法
public void createheap(seqlistsqlist, int low, int high)
//就想起商議5
if (tmp < sqlist[j])
else
} sqlist[k] = tmp;
} }
}//雙層迴圈 演算法的時間的複雜度是o(n2)
把順序表中的記錄建好堆後,就可以進行堆排序了。
堆排序的演算法如下所示,演算法中記錄的比較表示記錄關鍵碼的比較,順序表中只存放了記錄的關鍵碼:
1 //進行堆排序
2 public void heapsort(seqlistsqlist)
3
20 }
21 //由於用到遞迴,所以時間的複雜度是o(nlog2n)
下圖4是圖3(d)所示的最大堆進行堆排序的過程。
第一步:將堆頂記錄(關鍵碼為 48)與順序表最後乙個記錄(關鍵碼為 20進行交換,使得堆頂記錄的關鍵碼 20 比根結點的左孩子結點的關鍵碼 42 小,於是重新調整堆(記錄的範圍是從順序表的第乙個記錄到倒數第二個記錄,為了表示出順序表的最後乙個記錄不在調整的範圍之內,下圖4(a)已經把最後乙個結點從完全二叉樹中去掉,以下同)。調整過程見下圖4(b)所示。
第二步:將堆頂記錄(關鍵碼為 42)與順序表倒數第二個記錄(關鍵碼為17*)進行交換,使得堆頂記錄的關鍵碼 17*比根結點的左孩子結點的關鍵碼 22小,於是重新調整堆(記錄的範圍是從順序表的第乙個記錄到倒數第三個記錄) 。調整過程見下圖4(c)所示。
第三步: 將堆頂記錄 (關鍵碼為 27) 與順序表倒數第三個記錄 (關鍵碼為 8)進行交換,使得堆頂記錄的關鍵碼 8 比根結點的左孩子結點的關鍵碼 20 小,於是重新調整堆(記錄的範圍是從順序表的第乙個記錄到倒數第四個記錄) 。調整過程見下圖4(d)所示。
第四步:將堆頂記錄(關鍵碼為 20)與順序表倒數第四個記錄(關鍵碼為13)進行交換,使得堆頂記錄的關鍵碼 13 比根結點的左孩子結點的關鍵碼 17*小,於是重新調整堆(記錄的範圍是從順序表的第乙個記錄到倒數第五個記錄) 。調整過程見下圖4(e)所示。
第五步:將堆頂記錄(關鍵碼為 17*)與順序表倒數第五個記錄(關鍵碼為8)進行交換,使得堆頂記錄的關鍵碼比根結點的右孩子結點的關鍵碼 17 值小,於是重新調整堆(記錄的範圍是從順序表的第乙個記錄到倒數第六個記錄) 。調整過程見圖 下圖4(f)所示。
第六步: 將堆頂記錄 (關鍵碼為 17) 與順序表倒數第六個記錄 (關鍵碼為 8)進行交換,使得堆頂記錄的關鍵碼 8 比根結點的左孩子結點的關鍵碼 13 小,於1是重新調整堆(記錄的範圍是從順序表的第乙個記錄到倒數第七個記錄) 。調整過程見圖4(g)所示。
第七步:將堆頂記錄(關鍵碼為 13)與順序表第二個記錄(關鍵碼為 13)進行交換,調整過程結束。調整過程見圖4(h)所示
堆排序是一種較難的排序,思路是構建堆,在排序。 下節我繼續介紹排序的內容。
C 資料結構與演算法揭秘18
這節我們介紹堆排序。堆排序 在直接選擇排序中,順序表是乙個線性結構,要從有n個記錄的順序表中選擇出乙個最小的記錄需要比較n 1 次。如能把待排序的n個記錄構成乙個完全二叉樹結構,則每次選擇出乙個最大 或最小 的記錄比較的次數就是完全二叉樹的高度,即log2n次,則排序演算法的時間複雜度就是o nlo...
資料結構與演算法 揭秘
字面意思就是研究資料的一種方法,就是研究資料在程式中組織的一種方法。資料結構就是,元素與元素有一種或者多種關係的集合,在軟體界有一種比較普片的公式就是程式 資料結構 演算法。1 集合 set 和數學的集合一樣,具有唯一性,確定性,無序性。2 線性結構 典型的資料庫二維表,一對一的關係。3 樹形結構 ...
C 資料結構與演算法揭秘一
這裡,我們 來說一說c 的資料結構了。什麼是資料結構。資料結構,字面意思就是研究資料的方法,就是研究資料如何在程式中組織的一種方法。資料結構就是相互之間存在一種或多種特定關係的資料元素的集合。程式界有一點很經典的話,程式設計 資料結構 演算法。用源 來體現,資料結構,就是程式設計。他有哪些具體的關係...