第四部分 高級設計和分析技術
第16章 貪心算法
貪心算法在每一步都做出當時看起來最佳的選擇,它總是做出局部最優的選擇,寄希望這樣的選擇能導致全局最優解。
1. 活動選擇問題
有一個需要使用每個資源的n個活動組成的集合S= {a1,a2,···,an },資源每次只能由一個活動使用。每個活動ai都有一個開始時間si和結束時間fi,且 0≤ si < fi <∞ 。一旦被選擇後,活動ai就佔據半開時間區間[si,fi)。如果[si,fi]和[sj,fj]互不重疊,則稱ai和aj兩個活動是兼容的。該問題就是要找出一個由互相兼容的活動組成的最大子集。例如下圖所示的活動集合S,其中各項活動按照結束時間單調遞增排序。
從圖中可以看出S中共有11個活動,最大的相互兼容的活動子集爲:{a1,a4,a8,a11,}和{a2,a4,a9,a11}。
遞歸貪心算法
貪心算法通常採用自頂向下的設計,因爲不需要做出過多的選擇而求解所有的子問題。
Recursive-Activity-Selector(s, f, k, n)
m = k + 1
// find the first activity in Sk to finish
while m <= n and s[m] < f[k]
m = m + 1
if m <= n
return {am} U Recursive-Activity-Selector(s, f, m, n)
else
return Ø
### 迭代貪心算法
Greedy-Activity-Selector(s, f)
n = s.length
A = {a1}
k = 1
for m = 2 to n
if s[m] >= f[k]
A = A U {am}
k = m
return A
2. 貪心算法原理
貪心選擇性質
第一個關鍵要素是該性質:我們可以通過做出局部最優(貪心)選擇來構造全局最優解。換句話說,當進行選擇時,我們直接做出在當前問題中看來最優的選擇,而不必考慮子問題的解。
在動態規劃方法中,每個步驟都要進行一次選擇,但選擇通過依賴於子問題的解。因此,我們通常以一種自底向上的方式來解動態規劃問題,先求解較小的子問題,然後是較大的子問題。
貪心算法中,總是做出當時看來最佳的選擇,然後求解剩下的唯一的子問題。
最優子結構
如果一個問題的最優解包含其子問題的最優解,是稱此問題具有最優子結構性質。當應用於貪心算法時,我們通常使用更爲直接的最優子結構。我們可以假定,通過對原問題應用貪心選擇即可得到子問題。我們真正要做的全部工作就是論證:將子問題的最優解與貪心選擇組合在一起就能生成原問題的最優解。
3. 赫夫曼編碼
討論赫夫曼編碼問題,赫夫曼編碼的思想就是變長編碼。變長編碼就是讓字符表中出現概率高的字符的編碼長度儘可能小,而出現概率高的字符的編碼長度相對較長。然後還要遵循前綴碼的要求,就是任意一個編碼都不是其他編碼的前綴碼,這樣方便解碼。
構造赫夫曼編碼
Huffman(C)
n = |C|
Q = C
for i = 1 to n
allocate a new node z
z.left = x = Extract-Min(Q)
z.right = y = Extract-Min(Q)
z.freq = x.freq + y.freq
Insert(Q, z)
return Extract-Min(Q) // return the root of the tree