一、貪婪算法介紹
算法基本思路:從問題的某一個初始解出發一步一步地進行,根據某個優化測度,每一步都要確保能獲得局部最優解。每一步只考慮一個數據,他的選取應該滿足局部優化的條件。若下一個數據和部分最優解連在一起不再是可行解時,就不把該數據添加到部分解中,直到把所有數據枚舉完,或者不能再添加算法停止。(摘自 貪婪算法_百度百科)
簡單直接的描述,就是指每步都選擇局部最優解,最終得到的就是全局最優解。
二、引入:集合覆蓋問題
假設你辦了個廣播節目,要讓全美個州的聽眾都收聽得到,為此,你需要決定在哪些廣播台播出。在每個廣播台播出都需要支付費用,因此你試圖在盡可能少的廣播台播出。現有廣播台名單如下:
每個廣播台都覆蓋特定的區域,不同廣播台的覆蓋區域可能重疊。
如何找出覆蓋全美個州的最小廣播台合集呢?下面是解決步驟:
- 列出每個可能的廣播台集合,這被稱為冪集(power set)。可能的子集有2n個。
- 在這些集合中,選出覆蓋全美50個州的最小集合。
那么問題來了,計算每個可能的廣播台子集需要很長的時間。
我們可以嘗試使用貪婪算法。
三、算法實現
算法步驟
- 選出這樣一個廣播台,即它覆蓋了最多未覆蓋的州。即便這個廣播台覆蓋了一些已覆蓋的州(就是重復覆蓋),也沒有關系。
- 重復第一步,直到覆蓋了所有的州。
這是一種近鄰算法(approximation algorithm)。在獲得精確解需要的時間太長時,可以考慮使用近似算法。判斷近似算法優劣的標准如下:
- 速度有多快;
- 得到的近似解與最優解的接近程度。
因此貪婪算法是一個不錯的選擇,它們不僅簡單,而且通常運行速度很快。在本例中,貪婪算法的運行時間為O(n2),其中n為廣播台數量。
代碼如下
# 創建一個列表,其中包含要覆蓋的州 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() # 使用一個集合來存儲最終選擇的廣播台 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): # 檢查該廣播台的州是否比best_station多 best_station = station # 如果多,就將best_station設置為當前廣播台 states_covered = covered states_needed -= states_covered # 更新states_needed final_stations.add(best_station) # 在for循環結束后將best_station添加到最終的廣播台列表中 print(final_stations) # 打印final_stations
四、小結
- 貪婪算法尋找局部最優解,企圖以這種方式獲得全局最優解。
- 貪婪算法易於實現、運行速度快,是不錯的近似算法。
- 廣度優先搜索、迪傑斯特拉算法是貪婪算法。
五、代碼部分解讀
在上述算法中,有一段代碼很有趣
covered = states_needed & states # 計算交集
它是用來進行集合之間的相關計算,在此介紹下並集、交集和差集
- 並集意味着將集合合並;
- 交集意味着找出兩個集合中都有的元素;
- 差集意味着將從一個集合中剔除出現在另一個集合中的元素。
下面將舉例進行說明
>>> fruits = set(["avocado","tomato","banana"]) >>> vegetables = set(["beets","carrots","tomato"]) >>> fruits | vegetables # 計算並集 {'carrots', 'tomato', 'avocado', 'beets', 'banana'} >>> fruits & vegetables # 計算交集 {'tomato'} >>> fruits - vegetables # 計算差集 {'avocado', 'banana'} >>> vegetables - fruits {'carrots', 'beets'}
由此我們可以看出,集合類似於列表,只是不能包含重復的元素。