《算法圖解》第八章_貪婪算法_集合覆蓋問題


一、貪婪算法介紹

算法基本思路:從問題的某一個初始解出發一步一步地進行,根據某個優化測度,每一步都要確保能獲得局部最優解。每一步只考慮一個數據,他的選取應該滿足局部優化的條件。若下一個數據和部分最優解連在一起不再是可行解時,就不把該數據添加到部分解中,直到把所有數據枚舉完,或者不能再添加算法停止。(摘自 貪婪算法_百度百科

簡單直接的描述,就是指每步都選擇局部最優解,最終得到的就是全局最優解。

 

二、引入:集合覆蓋問題

假設你辦了個廣播節目,要讓全美個州的聽眾都收聽得到,為此,你需要決定在哪些廣播台播出。在每個廣播台播出都需要支付費用,因此你試圖在盡可能少的廣播台播出。現有廣播台名單如下:

每個廣播台都覆蓋特定的區域,不同廣播台的覆蓋區域可能重疊。

如何找出覆蓋全美個州的最小廣播台合集呢?下面是解決步驟:

  1. 列出每個可能的廣播台集合,這被稱為冪集(power set)。可能的子集有2n個。
  2. 在這些集合中,選出覆蓋全美50個州的最小集合。

那么問題來了,計算每個可能的廣播台子集需要很長的時間。

我們可以嘗試使用貪婪算法。

 

三、算法實現

算法步驟

  1. 選出這樣一個廣播台,即它覆蓋了最多未覆蓋的州。即便這個廣播台覆蓋了一些已覆蓋的州(就是重復覆蓋),也沒有關系。
  2. 重復第一步,直到覆蓋了所有的州。

這是一種近鄰算法(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'}

 

 

 

由此我們可以看出,集合類似於列表,只是不能包含重復的元素

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM