--- title: Greedy Algorithm / Interval Scheduling tags: 演算法(交大OCW) --- # Greedy Algo 介紹 短視近利的一種方法。 將問題拆成很多小步驟,藉由每次選擇該步所看到的「最好」,組成最後的解法 特性是做了決定後不會回頭;可以簡單且快速的想出來。 # 缺點 有可能得出的解法是比較差的。 但不見得每次運氣都這麼差,有可能直接找到最佳解:tea: 所以要證明你採用某種準則的 Greedy 可以找到問題的最佳解 # 常用證法 ## 「保持領先」Stays ahead 每次都選擇最好的解,這樣你就是最好的 ## 「交換無異」Exchange argument 也是最常用的證明方法 先假設有個人是最佳解,然後我把他跟我的假設中,不同的地方換成我的 >也就是讓他也變成我 Greedy 的形式 發現這麼做並不會讓它變成不是最佳解,這就代表我的也是最佳解 之後會有更詳細的介紹 --- # Interval Scheduling - Given:你有一堆工作,每個工作都會給你開始跟結束時間 - 在這些工作中,選出一些工作,使得 **「工作數量」** 是最大的 常用在CPU排程,像早期只有單核就很常面臨 假設第 i 個工作,有開始的點 s(i) 跟結束的點 f(i) $$ s(i),f(i) $$ f 會是開區間,是為了能夠銜接下一個工作 ![](https://drive.google.com/uc?id=1Q-QUrqc8Pq_T7HeWiHKxxIgVNL6BRTQP&export=download) 總之我要選擇能夠工作最多份,不是最長時間,也就是258這三個工作 # Greedy Rule 貪婪演算法說要找到每一步的「最好」,這樣對於每個題目就要定義何謂「最好」 以上面排程的例子來說,制定了一個最好的準則後會篩選出一些工作,然後還要把重疊的給去除掉 然後再從剩下的工作中,再做一次「最好」的準則篩選,然後再去除重複的,直到做完為止。 那這個最好的準則到底是甚麼呢? 此時我們腦力激盪,想到了四種方法: * 最早開始時間 * 就是每次選工作都選最早開始的做,然後去除掉跟這項工作重疊的 * 最短的時長間距 * 就是每次選工作都選時長最短的做,然後去除掉跟這項工作重疊的 * 最少重疊/衝突 * 就是每次選工作都選跟其他工作衝突最少的做,然後去除掉跟這項工作重疊的 * 最早結束時間 * 就是每次選工作都選最早結束的做,然後去除掉跟這項工作重疊的 * 排程問題的話,最佳解是第四種準則:最早結束時間 :::info 這也是Greedy常令人苦惱的地方,常常最佳的準則都不直覺 ::: 那要怎麼證明其他都不行?只要取反例就好 ## 最早開始時間 下面是一個工作組合的情形,而在這個情形中,一開始你只能選最下面那個時間很長的工作做 但是這樣你就只能做一個工作,因為其他都被排除掉了。 一開始選擇上面的話可反而可以做四個工作,反例成立。 ![](https://drive.google.com/uc?id=1OPOka3wozD3iCm7reI2i00EirrmxAQdd&export=download) ## 最短的時長間距 一開始選擇下面那個時長最短的做工作,就會排除掉上面兩個工作 但選擇上面的話可以做兩份工作,反例成立 ![](https://drive.google.com/uc?id=1AntxxfgtSd-n9PODTQz6AyXK-lPyybb8&export=download) ## 最少重疊/衝突 一開始會發現只能選擇深綠色的工作做,因為其他工作都會跟另外其他4個重疊,只有他重疊數是2 但是可以發現,選了深綠色後,最多就只能做 3 份工作,選上面的話反而可以做 4 份,反例成立。 ![](https://drive.google.com/uc?id=1J3AKvv67peWWxyPyDGcZ7zQF7Tqr8FaU&export=download) --- # Pseudo Code 在證明前要先給出程式碼,後面證明才能夠敘述 ```c function:Innterval_Scheduling(R) //R:undetermined requests;A:accepted requests 1. A = {} // empty set 2. while(R is not empty)do 3. choose a request i in R with minimum f(i) // Greedy rule 4. A = A + {i} 5. R = R - {i} - X, where X = {j:j in R and j isn't compatible with i} 6. return A ``` # 證明 :::success 此處採用的是保持領先,只要證明我一直都是最佳解就好 但是我們不用證明「我就是最好」,我只要說 「我的個數」跟「最好的個數」一樣就好 ::: 在此之前需要先證明一個待會會用到的Lemma ## Lemma 我有一個自己用我的演算法所挑的A集合以及最佳集合O;這兩個集合以開始或結束時間排序 至於是選開始或結束時間排序不影響,因為O跟A都是工作不會衝突的集合。 $$ A = \{i_{1}...i_{k}\} \\ O = \{j_{1}...j_{m}\} \\ For\ all\ r\le k,\ f(i_{r})\le f(j_{r}) $$ ### Proof 數學歸納法 1. $r=1$ 成立。可以確保兩者是小於等於的關係 2. 如果 $r - 1$ 成立,則 $$ f(i_{r-1})\le f(j_{r-1})\le s(i_{r}) $$ 根據 Pseudo code, O的第 r 份工作一定也會被 A 給選到 因此對於 $r$ 也成立 ## 證明 反證法 如果我根據我的演算法得到的 A 不是最佳解,代表最佳解 O 有更多的工作數量 Requests 假設 O 有 m 份工作,A 有 k 份工作。根據 Lemma: $$ For\ all\ r\le k,\ f(i_{r})\le f(j_{r}) \\ f(i_{k})\le f(j_{k}) \le s(j_{k}) $$ 根據演算法,O 的第 k+1 個並沒有產生衝突,程式碼會把他加到 A 裡面 但是 A 只有 k 個,代表 A 加入第 k 個工作時, R 就已經空了,矛盾。 所以 m 只能小於等於 k ,代表 A 就是最佳解 # 時間複雜度 ## 初始化 R O(nlogn) 1. 將 R 以 f(i) 升序排列,O(nlogn) 還沒講到排序,但是排序最快就是O(nlogn) 這樣 Line 3. 就可以直接獲得最早結束時間的工作 3. 產生一個集合 S = {s(i)},順序是根據上面 R 的 i 這將會是一個關鍵步驟,待會會說明 ## 刪除重疊的工作 O(n) 假如每次都要掃整個 Set ,去找誰跟目前的工作衝突的話,會變成 O(n^2^) 雖然我不是數學家,但是這聽起來不太好,我們希望時間最大就是排序的O(nlogn) **所以我們得想些巧妙的方法。我們希望只要掃描一次整個 Set 就好** 1. 我選了第 i 個工作。如果第 i + 1 個工作的**開始時間**小於第 i 個工作的**結束時間** 代表會衝突,那我就把他剔除,繼續看向第 i + 2 個,直到第 j 個的**開始時間**,大於第 i 個的**結束時間**。 2. 但如果有個工作,因為結束時間比較大,排在比較後面,同時他的開始時間又小於你現在所選的第 i 個的結束時間;可是你在排除他之前就已經找到一個第 j 個工作,無法排除他。 此時,不用擔心,因為在 i 後面的工作,一定會有一個會將他排除 而要達到「排除下一個的開始時間,大於目前工作的結束時間的工作」的方法,就是前面所建立的 S :::success S 可以使用持續增加的 index 變數,或是直接建一個 list 這樣一來,「刪除」這個動作就只要 O(n) 雖然我不是數學家,但這聽起來很不錯對吧 ::: --- # 範例 ## 將工作排序好 ![](https://drive.google.com/uc?id=1ngQY-weOHxjLziNAGMz8CpI44hMPR5cF&export=download) ## 選取第 1 份,排除第 2 份 ![](https://drive.google.com/uc?id=1pDkwGk__TognUlQsmRTLRNoHyiIAmkv4&export=download) ## 選取第 3 份,排除第 4 份 ![](https://drive.google.com/uc?id=1s1NnuNGwz8AdluLnZmXuwBHhuXiPhP2j&export=download) ## 選取第 5 份,排除第 6 份、第 7 份 ![](https://drive.google.com/uc?id=1IE7VNA7086sQ5O71iI5tm4WaUIgtM6Zr&export=download) ## 選取第 8 份,排除第 9 份 ![](https://drive.google.com/uc?id=1Nl_qUmgCPiOEqAsjctAIJ6bmy7_1poQz&export=download) ## 結果 {1,3,5,8} :::danger 但是要記得,這個演算法目的是找到最大的數量,並給出一種情況 不能給出全部的可能,像是 {2,4,5,8}、{1,4,7,9} 都是可以的組合 :::