贪心算法①–使用贪心算法思想解活动安排问题-python

'''
一、具有贪心选择结构
    复杂问题可以划分成小问题解决
二、具有贪心选择性质
    是否能够用贪心选择开始一个最优起点,使用贪心选择能否得到一个完整解
案例1:最优装载问题
    有n个集装箱需要装上一艘重量为W的轮船。
    其中,集装箱i(i=1,2,3....n)的重量Wi。
    最优装载问题要求在确定转载体积不受限制的情况下,怎么样
    装才可以尽可能多的把集装箱装上轮船?
    1.贪心选择结构
    sum(Wi) = W
    假设已经把第一个集装箱装上轮船了,此时该集装箱的重量为W1,
    轮船重量为W,剩余轮船可以装的重量W - W1.
    当装到i个集装箱的时候,满足:
    Wy = W(总问题) - Wi(子问题)
    Wy + Wi(子问题) = W(总问题)

案例2:活动安排问题
    有n个活动需要安排在礼堂举行,每个活动有编号、开始时间、结束时间,
    请您去安排本次活动,在有限时间内尽量多的安排活动?
    n个活动n1 n2 n3
    meetings = [(2,1,4),(4,5,7),(1,3,6),(3,2,8),(5,4,7),(6,6,9)]
    比如礼堂开放的时间是:0-7点
     1.最优子结构性质?能不能拆 -->能拆就可以使用两种算法:贪心算法和动态规划(重叠子问题)
     3 1 0.8 0.5 0.1
     4.6 = 3 + 1 + 0.5 + 0.1
     2.贪心选择性质
        2.1  i2 + i1 + i4 + i5 + i6=5(假设是最好的)
        2.2 以一个最优的选择i2开始

活动安排三种思路:
    a开始时间早的活动优先安排;
    b结束时间早的活动优先安排;
    c使用时间短的活动优先安排。

    班级      开始时间--结束时间      活动
1    1班      8:00-12:00          上课
2    2班      8:30-10:30           讲座
3    3班      11:00-11:30          开会
4    4班      10:40-11:20          竞选活动

整个教室开放时间是0-24:
最优方案就是:3

可以选择贪心-->怎么组合才能解出此题?
方案b,2-->4-->3-->1
方案a,1-->2-->4-->3.

元祖有三个参数,第一个表示活动编号,第二个表示活动开始时间,第三个表示活动结束时间

meetings = [
    (1,3,6),
    (2,1,4),
    (3,5,7),
    (4,2,5),
    (5,5,9),
    (6,3,8),
    (7,8,11),
    (8,6,10),
    (9,8,12),
    (10,12,14)
]
三种思路:
    a开始时间早的活动优先安排;
    b结束时间早的活动优先安排;
    c使用时间短的活动优先安排。

方案b(结束时间早):2--> 3 -->7 -->10
方案a(开始时间早):2--> 3 -->7 -->10
方案c(用时最短)  :3-->10
满足:最优子结构 && 贪心选择性质(2号在当前是最优选择),此题可以使用贪心算法求解,
并且能够求到一个最优解或近似最优解
'''
# 测试数据
# meetings = [
#     (1, 3, 6),
#     (2, 1, 4),
#     (3, 5, 7),
#     (4, 2, 5),
#     (5, 5, 9),
#     (6, 3, 8),
#     (7, 8, 11),
#     (8, 6, 10),
#     (9, 8, 12),
#     (10, 12, 14)
# ]

Python详细解:

meetings = [
    (4, 1, 4),
    (2, 3, 5),
    (1, 0, 6),
    (5, 5, 7),
    (7, 3, 8),
    (8, 5, 9),
    (9, 6, 10),
    (3, 8, 11),
    (10, 8, 12),
    (11, 2, 13),
    (6, 12, 14)
]


# 贪心算法过程
def eventArrangement(m):
    # 活动总数
    # n = 0
    # for i in m:
    #     n += 1
    n = len(m)
    # print(n)
    '''
    按照活动结束时间对活动进行升序排列-活动结束时间早的排在前面使其满足贪心选择性质
    推荐使用方法一:
    方法一:此处选用冒泡排序方法,提供冒泡排序的口诀:
    冒泡口诀:
        n数来冒泡,
        两两相比较。
        外层循环n-1,
        内层循环n-1-i。
        上上下下,左右右左,BABA
    得到的排序结果如下:
    [(2, 1, 4), (4, 2, 5), (1, 3, 6), (3, 5, 7), (6, 3, 8), (5, 5, 9), (8, 6, 10), (7, 8, 11), (9, 8, 12), (10, 12, 14)]
    
    复杂度 < Log2n
    '''
    # 双层循环为了构造出一对多的比较,如i=1的时候,j分别是从0到n-i-1的
    for i in range(n - 1):
        for j in range(n - i - 1):
            '''
            前后元素比较大小,j与j+1构造出前后元素进行比较:
            m[j]和m[j + 1]是构造出前后两个元素,由于得到的元素是元组类型,
            因此使用m[j][2]和m[j + 1][2]获取到活动结束时间
            '''
            if m[j][2] >= m[j + 1][2]:
                # 执行元素交换,而不是活动结束时间交换
                m[j], m[j + 1] = m[j + 1], m[j]
    '''
    方法二:
    调用系统提供的排序函数sort(),只是消耗的排序复杂度略高。
    m.sort(key = lambda x:x[2])
    得到的排序结果如下:
    [(2, 1, 4), (4, 2, 5), (1, 3, 6), (3, 5, 7), (6, 3, 8), (5, 5, 9), (8, 6, 10), (7, 8, 11), (9, 8, 12), (10, 12, 14)]
    复杂度 >= Log2n
    '''
    # m.sort(key=lambda x: x[2])
    # print(m)
    # 定义一个标志位列表,初始化时其中的值置为False表示所有活动在初始时均未被安排,后续满足条件被选中时置为True即可
    meetingsFlag = [False for i in range(n)]
    # print(meetingsFlag)

    # j = 0是为了满足贪心选择性质,默认选中第0号活动,由于升序排列,第0号活动是当前这一步中最优解(结束时间最早的活动)
    j = 0
    # 由于已经选择了这一个活动,将其标志位置为True,也满足贪心选择走出当前这一步以后不允许回退的原则
    meetingsFlag[j] = True
    '''
    此处的自变量i是从1开始的,意味着i默认是选择候选活动中的第1号活动,因为0号活动已经被选择.
    所以此时j=0表示第0号活动,i=1表示1号活动,也正好构造了前一个活动与后一个活动,为下面的活动比较构造好了比较序列
    '''
    for i in range(1, n):
        '''
        如果后一个活动的开始时间是晚于前一个活动的结束时间的,说明后一个活动可以被选择:
        m[i]表示后一个活动,m[i][1]表示后一个活动的开始时间,因为m[i]是个元组,如(1, 3, 6),
        那么m[i][1]得到的是元组中第二个元素就是3,3表示的是1号活动的开始时间是3点.
        
        m[j]表示前一个被选择的活动,m[j][2]表示前一个活动的结束时间.如(3, 5, 7),那么m[j]是当前这个元组
        (3,5,7),m[j][2]是元组中的第三个元素7,表示上一个活动结束时间.
        
        因此,此处构造了前一个活动的结束时间与下一个候选活动的开始时间进行比较.
        '''
        if m[i][1] >= m[j][2]:  # 若满足表示后一个活动可以安排
            # 将j的值更新为当前已经被选中的活动i
            j = i
            # 因此将可以安排的这个活动的标志位改为True,标志着它已经被选中了
            meetingsFlag[j] = True

    '''
        循环结束表示此时活动已经安排完成,那就根据题目要求来灵活处理,若是需要统计有多少个活动被选择,那就直接统计
        meetingsFlag列表中有多少个True即可,若需要统计是哪些具体活动被选择了,就需要将被选择的活动进行输出
    '''
    return meetingsFlag


def output():
    # 函数调用
    resulst = eventArrangement(meetings)
    n = 0
    for i in resulst:
        n += 1
    print(resulst)
    # 统计有多少活动被选择
    changeCount = 0
    '''
    统计具体是哪一些活动被选择了:
    1.通过遍历标志位中为True的元素获取其下标,这个下标同时也是候选集活动meetings中的当前活动的下标
    2.通过此下标去候选集meetings中获取到被选中的活动
    '''
    for i in range(n):
        if resulst[i]:
            print('第' + str(meetings[i][0]) + '号活动被安排。')
            changeCount += 1  # 计数器+
    print('共计:' + str(changeCount) + '个活动被选择。')


# 函数调用
output()

Python精简解:

def m(meetings):
    length = len(meetings)
    meetings.sort(key=lambda x: x[2])
    result = [False for i in range(length)]
    j = 0
    result[j] = True
    for i in range(1, length):
        if meetings[i][1] >= meetings[j][2]:
            j = i
            result[j] = True
    return result


meetings = [(4, 1, 4), (2, 3, 5), (1, 0, 6), (5, 5, 7), (7, 3, 8), (8, 5, 9), (9, 6, 10), (3, 8, 11), (10, 8, 12),
            (11, 2, 13), (6, 12, 14)]
result = m(meetings)
print(result)

文章出处登录后可见!

已经登录?立即刷新

共计人评分,平均

到目前为止还没有投票!成为第一位评论此文章。

(0)
社会演员多的头像社会演员多普通用户
上一篇 2023年11月9日
下一篇 2023年11月9日

相关推荐