16 貪心演算法(筆記)
阿新 • • 發佈:2018-11-12
釋義
在設計演算法求解最優化問題的過程中,每一步都做出當時看起來最佳的選擇,這樣的演算法稱作貪心演算法,每一步做出的選擇稱作貪心選擇。
設計步驟
- 將最優化問題轉化為這樣的形式:對其做出一次選擇後,只剩下一個待求解的子問題。
- 證明做出貪心選擇後,原問題總是存在最優解,即貪心選擇總是安全的。
- 證明做出貪心選擇後,剩餘的子問題滿足性質:其最優解與貪心選擇組合即可得到原問題的最優解,這樣就得到了最優子結構。
應用示例
活動選擇問題
問題描述
假定有一個 n 個活動的集合 S={a1,a2,…,an},這些活動使用同一個資源,而這個資源在某個時刻只能供一個活動使用。每個活動 ai 都有一個開始時間 si 和結束時間 fi,其中 0<=si<fi<∞。如果被選中,活動 ai 發生在 [si, fi) 期間。如果兩個活動不重疊,則稱他們是相容的
設計步驟
貪心選擇:
如何做出貪心選擇呢?想要得到 S 的最大相容活動集,也就是說:儘可能多的挑選 S 中時間不重疊的活動。需要在每一次的選擇中,選擇儘可能早結束的活動。因為這樣以來,就留出更多的時間供之後的活動選擇,更多的時間意味著可以選擇更多不重疊的活動。
步驟如下:
- 假定 Sk 為在 ak 結束後開始的活動集合,在第一次選擇 a1 後,只剩下一個待求解的子問題 S1(Sk既可表示ak結束後開始的活動集合,也可表示一個子問題,需要結合上下文進行理解)。由於活動按照結束時間遞增排序,所以 a1 一定是當前最早結束的活動,為貪心選擇。而 S1 中都是 a1結束之後的活動,所以只需在下一次的選擇中選擇 S1 中最早結束的活動即為貪心選擇。這樣以來,每次都能做出貪心選擇,且只剩下一個待解決的子問題。
- 在此方案中每次做出貪心選擇後,能保證之前的活動已經沒有更優的,而之後的活動則包含在選擇後產生的子問題中。因此,按照此方案進行貪心選擇,原問題總是存在最優解,貪心選擇總是安全的。
- 由2的論述可以發現:某次選擇 ak 後,所做的選擇加上之前做的選擇總能保證原問題存在最優解,因此 ak 及之前做出的選擇的活動集合並上 ak 結束後的集合 Sk 的最優解能得出原問題的最優解。
根據貪心選擇,可以很容易得出如下遞迴演算法:
/**
輸入
s: 活動開始時間序列
f: 活動結束時間序列
k: Sk的下標k
n: 活動的數量
注意:為了方便,加入一個活動a0,f0=0,
這樣以來,S0即表示原問題(所有活動的集合)
輸出
Sk的最大互相相容的活動集合
*/
recursiveActivitySelector(s, f, k, n)
m = k+1
while m<=n and s[m]<f[k]
m = m+1
if m<=n
return {a[m]} U recursiveActivitySelector(s, f, m, n)
else return null
以下為recursiveActivitySelector的一個迭代版本:
/**
輸入
s: 活動開始時間序列
f: 活動結束時間序列
輸出
Sk的最大互相相容的活動集合
*/
greedyActivitySelector(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