貪心算法解決活動安排-Python實現(排序+貪心選擇)

貪心算法解決活動安排

問題

問題概述

分析問題

解決問題

編程

編程流程以及數據類型選擇

發現問題以及解決

最終實現

總結

程序缺陷以及完善

解題心路歷程


問題


問題概述

設有n個活動的集合E={1,2,……,n},其中每個活動都要求使用同一資源,如演講會場等,而在同一時間內只有一個活動能使用這一資源。每個活動i都有一個要求使用該資源的起始時間si和一個結束時間fi,且si<fi。如果選擇了活動i,則它在時間區間[si,fi]內佔用資源。若區間[si,fi]與區間[sj,fj]不相交,則稱活動i與活動j是相容的。也就是說,當si>=fj或者sj>=fi時,活動i與活動j相容。活動安排問題就是要在所給的活動集合中選出最大的相容活動子集合。


分析問題

通過問題概述,可以模擬一個場景:

一個舞臺在一天中是空閒的,可以用作活動的場合,不可以同時進行兩個活動。所以活動的開始時間和結束時間是活動安排的判斷標準。

問題的答案以及目的,是讓舞臺得到充分利用,進行最多的活動,即求出進行最大的活動的個數。


解決問題

可以將申請場地的所有的活動進行非減序排列(非減序排列,即說明當兩個活動的結束時間相同,可以進行並列)。

定義兩個集合,用於存儲入選活動和候選活動。

獲得非減序排列的活動排序後,對於第一個的活動,直接進行選擇。此時,第一個活動即是入選活動。其它的活動是候選活動,對活動的開始時間和結束時間進行比較。有以下幾種情況:

  1. 入選活動集合的最後一個活動的結束時間大於候選活動集合的第1個活動的開始時間,那個這個候選活動即被淘汰出候選活動集合。
  2. 入選活動集合的最後一個活動的結束時間小於等於候選活動集合的第一個活動的開始時間,那麼這個候選活動將被選擇到入選活動集合中。

重複以上操作,當候選活動集合沒有活動時,則解決問題。以上的選擇即是貪心選擇,選擇出當前的最優解。

綜上,問題的解決有兩個大的部分,部分1是對活動進行非減序排序;部分2是對活動進行貪心選擇。

 


編程


編程流程以及數據類型選擇

結合問題的部分,可以確定有以下步驟

  1. 通過鍵盤輸入獲得活動的開始時間和結束時間,使用元組存儲活動的開始時間和結束時間,爲活動元組;使用2個列表分別存儲活動id、活動元組
  2. 根據活動的結束時間對活動進行非減序排列,並使用字典存儲已經排序好的活動,字典的鍵對應活動的id,值對應活動元組。爲候選活動字典
  3. 打印排序好的活動列表,打印活動字典
  4. 使用貪心算法對活動進行選擇,使用字典存儲入選活動的id和開始時間和結束時間,爲入選活動字典
  5. 打印結果

發現問題以及解決

問題1:如何根據列表中元組元素的第二個參數進行排序,即對活動的結束時間進行排序

解決:定義一個函數

def takeSecond(elem):
    return elem[1]

用於返回列表的每個元組的第2個元素,並使用內置函數sort對列表進行排序

sort(key=takeSecond)

 


最終實現

程序代碼:

#proname:greedy_act
    #author:zhj
    #time:2019.10.29
    #language:python
    #version:1.0
#arg:tuple=>(0,1)
#return:tuple[1]=>1
def takeSecond(elem):
    return elem[1]
#arg:lst_id:[1,2,3,..,n]
#    lst_act:[(s[1],f[1]),(s[2],f[2]),...,(s[n],f[n])]
#return:sort of dict_act
# {1:(,*),2:(,**),...,n:(,**n)}
def sortAct(lst_id,lst_act):
    lst_sort_act = lst_act
    lst_sort_act.sort(key=takeSecond)
    dict_act = dict(zip(lst_id,lst_sort_act))
    return dict_act#test:success
#arg:dict_act
#{1:(,*),2:(,**),...,n:(,**n)}
#return:gree of gree_act[id:(,*),id:(,**),...,id:(,*****)]
def greedy(dict_act):
    true_id = []
    true_act = []
    #purpose: get the first to append the list
    true_ele_tuple = dict_act[1]
    true_id.append(1)
    true_act.append(true_ele_tuple)
    #purpose: get the  finish time of fist activity to divide element
    ele_divi = true_ele_tuple[1]
    for i in range(1,num_act):
#get the first act finish time
        id = int(1+i)
        #purpose:get the seconde activity to the last activity
        tuple_ele_act = dict_act[id]
        #print(tuple_ele_act) #test:success
        if (tuple_ele_act[0] >= ele_divi):
            true_id.append(id)
            true_act.append(tuple_ele_act)
            ele_divi = tuple_ele_act[1]
    greedy_act = dict(zip(true_id,true_act))
    return greedy_act
#main
input_act = input("請輸入活動的個數,n = ")
num_act = int(input_act)
print("請分別輸入開始時間s[i]和結束時間f[j]:")
lst_id = []
lst_act = []
for i in range(1,num_act+1):
    lst_id.append(i)
    input_start =eval(input("s["+str(i)+"]="))
    input_finish =eval(input("f["+str(i)+"]="))
    tuple = ()#purpose:def tuple to save the act_start and  act_finish
    tuple = (input_start,input_finish)
    lst_act.append(tuple)
    del tuple
    print("")
dict_act = sortAct(lst_id,lst_act)
print("按結束時間非減序排列如下:")
print("序號----開始時間----結束時間")
for i in range(1,num_act+1):
    id = int(i)
    ele_tuple = dict_act[i]
    print(id,"------",ele_tuple[0],"----------",ele_tuple[1])
#test;success
greedy_act = greedy(dict_act)
#print(greedy_act)#test:success
true_id = list(greedy_act.keys())
# print(true_id)#test:success
print("----------------------")
print("安排的活動序號依次爲:")
printlen = len(true_id)
for id in true_id:
      tuple_print = greedy_act[id]
      print(id,"    ",tuple_print[0],"------>",tuple_print[1])

運行結果截圖:

總結


程序缺陷以及完善

  • 缺陷:沒有異常判斷,可能出現輸入異常等等異常;
    • 完善:可以進行增加的話,因爲代碼工作量不大
  • 無法打印所有的活動安排方案


題目中是要求輸出最多活動安排,所以也可以是另外一個方案。例如本題中,活動安排方案是 1->4->8->11,共4個活動。而2->4->8->11,1->4->9->11也是4個活動。

  • 完善:另一種處理方式,不只是貪心選擇,而是將不符合的活動直接進行刪除:第一個候選活動的開始時間小於最後一個入選活動的結束時間,那麼這個候選活動,則直接進行刪除
    • 例如本題中
      • 活動1已成功入選,那麼活動2,3都直接刪除
      • 活動4入選,同貪心選擇,可以輸出1->4->8->11
      • 回到第1步,刪除活動4,則活動5直接刪除;
      • 進行貪心選擇,發現有1->6->11,存入列表,因爲活動個數小於4,所以排除
      • 不斷重複以上邏輯
    • 注:哪天有空,可以試試編寫,並會在這裏引用。
  • 代碼優化:
    • 有很多代碼可以進行優化,邏輯可以更加嚴密。

解題心路歷程

使用貪心算法解決活動安排問題,可以說是對初學Python的一次有效的實踐和應用。有以下幾點需要筆記:

  • 使用正確的數據類型可以事半功倍:之前有嘗試使用字典嵌套列表,結果發現處理流程,特別繁雜。所以使用字典嵌套元組,可以讓問題處理邏輯更加清晰。減少大量的代碼。
  • 程序邏輯清楚,可以使用函數優化代碼,要明確每個函數的參數以及返回值是什麼,以及這個函數的目的是什麼

本篇已完成編寫,如有更改,會在此列出。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章