1. 程式人生 > >16 貪心演算法(筆記)

16 貪心演算法(筆記)

釋義

在設計演算法求解最優化問題的過程中,每一步都做出當時看起來最佳的選擇,這樣的演算法稱作貪心演算法,每一步做出的選擇稱作貪心選擇

設計步驟

  1. 將最優化問題轉化為這樣的形式:對其做出一次選擇後,只剩下一個待求解的子問題。
  2. 證明做出貪心選擇後,原問題總是存在最優解,即貪心選擇總是安全的。
  3. 證明做出貪心選擇後,剩餘的子問題滿足性質:其最優解與貪心選擇組合即可得到原問題的最優解,這樣就得到了最優子結構。

應用示例

活動選擇問題

問題描述

假定有一個 n 個活動的集合 S={a1,a2,…,an},這些活動使用同一個資源,而這個資源在某個時刻只能供一個活動使用。每個活動 ai 都有一個開始時間 si 和結束時間 fi,其中 0<=si<fi<∞。如果被選中,活動 ai 發生在 [si, fi) 期間。如果兩個活動不重疊,則稱他們是相容的

。在活動選擇問題中,我們的目標是從 S 中選擇一個最大相容活動集。假定活動已按結束時間遞增排序:f1<=f2<=f3<=…<=fn-1<=fn(16.1)。

設計步驟

貪心選擇:
如何做出貪心選擇呢?想要得到 S 的最大相容活動集,也就是說:儘可能多的挑選 S 中時間不重疊的活動。需要在每一次的選擇中,選擇儘可能早結束的活動。因為這樣以來,就留出更多的時間供之後的活動選擇,更多的時間意味著可以選擇更多不重疊的活動。

步驟如下:

  1. 假定 Sk 為在 ak 結束後開始的活動集合,在第一次選擇 a1 後,只剩下一個待求解的子問題 S1(Sk既可表示ak結束後開始的活動集合,也可表示一個子問題,需要結合上下文進行理解)。由於活動按照結束時間遞增排序,所以 a1 一定是當前最早結束的活動,為貪心選擇。而 S1 中都是 a1結束之後的活動,所以只需在下一次的選擇中選擇 S1 中最早結束的活動即為貪心選擇。這樣以來,每次都能做出貪心選擇,且只剩下一個待解決的子問題。
  2. 在此方案中每次做出貪心選擇後,能保證之前的活動已經沒有更優的,而之後的活動則包含在選擇後產生的子問題中。因此,按照此方案進行貪心選擇,原問題總是存在最優解,貪心選擇總是安全的。
  3. 由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