貪心演算法 1 演算法導論 21

2022-02-10 17:53:58 字數 3014 閱讀 6474

我們先來看看乙個適應貪心演算法求解的問題:選擇活動問題

假定有乙個\(n\)個活動的集合\(s = \\),每個活動\(a_i\)的舉辦時間為\([s_i, f_i),0 \leqslant s_i < f_i\),並且假定集合\(s\)中的活動都已按結束時間遞增的順序排列好。由於某些原因,這些活動在同一時刻只能有乙個被舉辦,即對於任意兩個活動\(a_i\)與\(a_j(i \neq j)\),區間\([s_i, f_i)\)與區間\([s_i, f_i)\)不能重疊,此時我們稱活動\(a_i\)與\(a_j\)相容。在選擇活動問題中,我們希望選擇出乙個最大相容活動集。

例如,給出如下活動集合\(s\):

\(i\)12

3456

78910

11\(s_i\)13

0535

6882

12\(f_i\)45

6799

1011

1214

16子集$ } $是相互相容的,但它不是乙個最大集,因為子集 \(a_i, a_4, a_8, a_\)更大。實際上,\(}\)是乙個最大相容活動集,另乙個最大集是:\(a_2, a_4, a_9, a_\)。

2.2.1. 動態規劃演算法

我們先嘗試使用動態規劃演算法來解決該問題。首先驗證該問題是否具有最優子結構性質

令\(s_\)表示在\(a_i\)結束之後開始,且在\(a_j\)開始之前結束的活動集合。我們的任務是求\(s_\)的乙個最大相容的活動子集。假設\(a_\)就是這樣乙個子集,其中包含\(a_k\),於是原問題就被分解為了兩個子問題:求解 \(s_\)與\(s_\)中的相容活動。顯然,這兩個子問題的相容活動子集的並是原問題的乙個相容活動子集。

現在問題的關鍵是兩個子問題的最優相容活動子集的並是否是原問題的最優解。或者反過來,原問題的乙個最優解\(a_\)是否包含兩個子問題的最優解。答案是肯定的,同樣可以用剪下-貼上證明,這裡不再贅述。

證明了該問題具有最優子結構性質,於是我們可以用乙個遞迴式來描述原問題的最優解。設\(c[i, j]\)表示集合\(s_\)的最優解的大小,則有:

\[c[i, j] =

\begin

0 & \text = \varnothing$ }\\

\max \limits_}\&\text \neq \varnothing$}

\end

\]於是接下來就可以設計乙個自頂向上的動態規劃演算法。

2.2.2. 貪心選擇

我們看到,在上述的動態規劃演算法中,由於我們不確定到底選擇哪乙個\(a_k\),會產生最優解,因此我們必須考察每一種\(a_k\)的選取情況。自然地,我們便會想,對於每一次\(a_k\)的選擇,我們可不可以直接找出「最優的\(a_k\)」的呢?如果能這樣,那麼演算法的效率會大大地提公升。

事實上,對於該問題,我們在面臨選擇時,還真的可以只考慮一種選擇(貪心選擇)。直觀上我們可以想象,要想使活動更多的舉辦,我們在每次選擇活動時,應盡量選擇最早結束的活動,這樣可以把更多的時間留給其他的活動。

更確切地說,對於活動集合\(s_\),由於活動都按照結束時間遞增的順序排列好,因此貪心選擇是\(a_i\)。如果貪心選擇是正確的,按照該選擇方法,我們便能將所有選擇的結果組合成原問題的最優解。

現在問題的關鍵是,貪心選擇選擇出來的元素總是最優解的一部分嗎?答案同樣還是肯定的,下面的定理說明了這一點:

設\(s_k = \\)表示在活動\(a_k\)結束後開始的活動集合。考慮任意非空子問題\(s_k\),令\(a_m\)是\(s_k\)中最早結束的活動,則\(a_m\)在\(s_k\)的某個最大相容活動子集中。

我們可以如下證明該定理:設\(a_i\)是\(s_i\)的乙個最大相容活動子集,且\(a_j\)是其中最早結束的活動。若\(a_m = a_j\),則自然滿足上述結論;若\(a_m \neq a_j\),用\(a_m\)替代\(a_i\)中的\(a_j\),得到子集\(a'\),即 \(a' = (a - a_j) \cup a_m\),則\(a'\)也是\(s_i\)的乙個最大相容活動子集。因為\(a_m\)是\(s_k\)中最早結束的活動,於是有\(f_m \leq f_j\),因此\(a'\)仍然是相容的。並且顯然\(|a'| = |a|\),所以得出上面的結論,也就得出了定理中的結論。

有了上述分析的基礎,我們可以很容易設計出乙個貪心演算法來解決原問題。和動態規劃演算法相比,由於我們每次都是一次性的找出了當時的最優解,而不必像動態規劃演算法那樣需要考慮每種可能的選擇情況,因此貪心演算法就不必考慮子問題是否重疊,也就不需要解決重疊問題的「備忘錄」了。因此,與動態規劃演算法相反的是,貪心演算法通常都是自頂向下進行設計的。

下面給出一種python的實現:

def recursive_activity_selector(s, f, k, n, ls):

m = k + 1

while m <= n:

if s[m] >= f[k]:

recursive_activity_selector(s, f, m, n, ls)

return ls

m += 1

# 測試

if __name__ == '__main__':

# 注意,這裡新增了乙個開始時間為0,結束時間也為0的活動。

s = [0, 1, 3, 0, 5, 3, 5, 6, 8, 8, 2, 12]

f = [0, 4, 5, 6, 7, 9, 9, 10, 11, 12, 14, 16]

k = 0

n = 11

ls =

recursive_activity_selector(s, f, k, n, ls)

print(ls)

列印結果為:

[1, 4, 8, 11]

演算法導論 13 貪心演算法

與動態規劃類似,貪心演算法也將問題化簡為規模較小的子問題,並通過遞迴解決子問題來獲取整個問題的解。不同的是,貪心問題不對子問題進行比較,而是只生成乙個非空的子問題,而使選擇在當時看上去是最優的 即 貪心 的含義 幾個互相競爭的活動都要求以獨佔的方式占用某個公用資源 如選修課程對個人可支配時間的要求,...

演算法導論之貪心演算法

參考 下面請看示例題 有n個商品,每個商品的重量為wi,為 pi,現有乙個揹包,最多能裝 的重量 其中 0 i問 怎樣裝能使包中裝入的商品價值最高 對於每個商品可以只裝該商品的一部分 偽 引數分別為 n 物品數 m 揹包最多能裝的重量 v 價值陣列 w重量陣列 void knapsack int n...

演算法導論05 貪心演算法 Dijkstra演算法

問題描述 請設計乙個演算法,實現在乙個有向圖中,給出乙個源點,計算該源點到其他所有頂點的單源最短路徑。演算法描述 dijkstra演算法的目的是尋找單源最短路徑,其具有以下最優子結構性質 最短路徑上的子路徑也是兩個頂點的最短路徑,此性質可以用cut paste反證 見筆記 因此,該演算法滿足貪心演算...