贪心算法解决活动安排
问题
问题概述
分析问题
解决问题
编程
编程流程以及数据类型选择
发现问题以及解决
最终实现
总结
程序缺陷以及完善
解题心路历程
问题
问题概述
设有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是对活动进行贪心选择。
编程
编程流程以及数据类型选择
结合问题的部分,可以确定有以下步骤
- 通过键盘输入获得活动的开始时间和结束时间,使用元组存储活动的开始时间和结束时间,为活动元组;使用2个列表分别存储活动id、活动元组
- 根据活动的结束时间对活动进行非减序排列,并使用字典存储已经排序好的活动,字典的键对应活动的id,值对应活动元组。为候选活动字典
- 打印排序好的活动列表,打印活动字典
- 使用贪心算法对活动进行选择,使用字典存储入选活动的id和开始时间和结束时间,为入选活动字典
- 打印结果
发现问题以及解决
问题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的一次有效的实践和应用。有以下几点需要笔记:
- 使用正确的数据类型可以事半功倍:之前有尝试使用字典嵌套列表,结果发现处理流程,特别繁杂。所以使用字典嵌套元组,可以让问题处理逻辑更加清晰。减少大量的代码。
- 程序逻辑清楚,可以使用函数优化代码,要明确每个函数的参数以及返回值是什么,以及这个函数的目的是什么
本篇已完成编写,如有更改,会在此列出。