貪心算法
貪心算法是一種解決問題的思路:每一步選擇局部最優解,最終也許不會得到最優結果,但是也會接近最優結果。
貪心算法具有以下特點:
- 每一步選擇局部最優解;
- 並非在任何情況下行之有效;
- 貪心算法簡單易實現;
教室調度問題
假設有如下課程表,你希望將盡可能多的課程安排在某間教室上:
課程 | 開始時間 | 休息時間 |
---|---|---|
美術 | 9 AM | 10 AM |
英語 | 9:30 AM | 10:30 AM |
數學 | 10 AM | 11 AM |
計算機 | 10:30 AM | 11:30 AM |
音樂 | 11 AM | 12 PM |
你希望在這間教室上儘可能多的課。如何選出儘可能多且時間不衝突的課程呢?貪心算法的做法是:每一步選擇價值回報最大的操作。具體做法如下:
- 選出結束最早的課,它就是要在這間教室上的第一堂課。
- 接下來,必須選擇第一堂課結束後纔開始的課。同樣,你選擇結束最早的課,這將是要在這間教室上的第二堂課。
重複這樣做就能找出答案!最終選擇的結果如下:
揹包問題
假設你是個貪婪的小偷,揹着可裝35磅()重東西的揹包,在商場伺機盜竊各種可裝入揹包的商品。
貪心算法的策略是:
- 盜竊可裝入揹包的最貴商品;
- 再盜竊還可裝入揹包的最貴商品,以此類推;
以這種方式可能並不能得到最優解。有時候,你只需找到一個能夠大致解決問題的算法,此時貪婪算法正好可派上用場,因爲它們實現起來很容易,得到的結果又與正確結果相當接近。記住,對有些問題,可能並不存在完美的解,例如 NP 難問題。
集合覆蓋問題
集合覆蓋問題: 選擇最少的集合,覆蓋全部的元素。
假設你辦了個廣播節目,要讓全美 個州的聽衆都收聽得到。爲此,你需要決定在哪些廣播臺播出。在每個廣播臺播出都需要支付費用,因此你力圖在儘可能少的廣播臺播出。現有廣播臺名單如下。
廣播臺 | 覆蓋的州 |
---|---|
(1) KONE | ID,NV,UT |
(2) KTWO | WA,ID,MT |
(3) KTHREE | OR,NV,CA |
(4) KFOUR | NV,UT |
(5) KFIVE | CA,ZA |
如何找出覆蓋全美 個州的最小廣播臺集合呢?
最容易考慮到的可能是暴力求解法:列出每一種可能的廣播集合(可能的子集有 個);在這些集合中選出能覆蓋全美 個州的最小集合數。假設每秒可篩選出 個子集,其花費的時間如下:
沒有任何算法可以足夠快地解決這個問題!怎麼辦呢?使用貪婪算法可以得到非常接近的解。
- 選出這樣一個廣播臺,即它覆蓋了最多的未被選擇的州。即便這個廣播臺覆蓋了一些已經選擇的州,也沒有關係;
- 重複第一步,直到覆蓋了所有的州。
貪婪算法是不錯的選擇,它們不僅簡單,而且通常運行速度很快。在這個例子中,貪婪算法的運行時間爲 ,其中 爲廣播臺數量。
解決上述問題的代碼如下:
- 第一步:準備工作:構建數據結構;
- 用集合
states_needed
存儲所有的州; - 用字典表示電視臺
stations
的覆蓋面;
- 用集合
states_needed = set(['mt', 'wa', 'or', 'id', 'nv', 'ut', 'ca', 'az']) # 包含要覆蓋的州
stations = {} # 存儲電視臺集合
stations["kone"] = set(["id", "nv", "ut"])
stations["ktwo"] = set(["wa", "id", "mt"])
stations["kthree"] = set(["or", "nv", "ca"])
stations["kfour"] = set(["nv", "ut"])
stations["kfive"] = set(["ca", "az"])
final_stations = set() # 存儲最終電視臺的集合
- 第二步:利用貪心算法求解最少的電視臺
- 選出這樣一個廣播臺,即它覆蓋了最多的未被選擇的州。我們將這個廣播臺存儲在
best_station
中; - 重複第一步,直到覆蓋了所有的州。
- 選出這樣一個廣播臺,即它覆蓋了最多的未被選擇的州。我們將這個廣播臺存儲在
while states_needed:
best_station = None # 最佳電視臺:覆蓋了最多未覆蓋的州
states_covered = set() # 最佳電視臺與未覆蓋州集合的交集
for station, states in stations.items():
covered = states_needed & states # 當前廣播臺與未覆蓋州集合的交集
if len(covered) > len(states_covered):
states_covered = covered
best_station = station
states_needed -= states_covered # 更新未覆蓋州的集合
final_stations.add(best_station) # 添加貪心算法選擇的電臺
最終打印的結果可能如下:
print(final_stations)
# set(['ktwo', 'kthree', 'kone', 'kfive'])
暴力求解算法 和 貪心算法 求解時間對比:
NP完全問題
什麼樣的問題稱爲 NP 完全問題呢? 就像集合覆蓋問題一樣,你需要計算所有的解,並從中選擇優的解。NP 完全問題以計算量大無法求得最優解而著稱,貪心策略是解決 NP 完全問題一個很重要且有效的方法。它也許不能得到最優解,但是可以得到最接近最優解的結果。
總結
- 貪婪算法尋找局部最優解,企圖以這種方式獲得全局最優解。
- 對於NP完全問題,還沒有找到快速解決方案。
- 面臨NP完全問題時,最佳的做法是使用近似算法。
- 貪婪算法易於實現、運行速度快,是不錯的近似算法。