基于Python3的数据结构与算法 - 22 贪心算法

一、贪心算法

  • 贪心算法(又称贪婪算法)是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,它所做出的是在某种意义上的局部最优解。
  • 贪心算法并不会保证会得到最优解,但是在某些问题上贪心算法的解就是最优解。要学会判断一个问题能否用贪心算法来计算。

1. 找零问题

假设商店老板需要找零n元钱,钱币的面额有:100元,50元、20元、5元和1元,(无限多张)如何找零使得所需要的钱币的数量最少?

t = [100,50,20,5,1]

def change(t, n):   # n表示需要找零多少
    m = [0 for _ in range(len(t))]  # m = [0 0 0 0 0]  # 表示数量
    for i, money in enumerate(t):
        m[i] = n // money
        n = n % money
    return m, n

print(change(t, 376))

输出结果如下:

2. 背包问题

一个小偷在某个商店发现有n个商品,第i个商品价值vi元,重wi千克。他希望拿走的价值尽量高,但他的背包最多只能容纳W千克的东西。他应该拿走哪些商品?

背包问题可以分为两种类型的问题:0-1背包 + 分数背包

  • 0-1背包:对于一个商品,小偷要么把它完整拿走,要么留下,不能之拿走一部分,或把一个商品拿走多次。
  • 分数背包:对于一个商品,小偷可以拿走其中任意一部分。(商品为金砂)

举例:

  • 商品1:v1 = 60 ,w1 = 10
  • 商品2:v2 = 100,w1 = 20
  • 商品3:v3 = 120, w3 = 30
  • 背包容量: W = 50

对于0-1背包,贪心算法不能得到最优解。(选择0-1背包最后可能出现包装不满的情况,可能剩下好多容量)

对于分数背包,贪心算法可以得到最优解。(选择最贵的直至包里不剩位置)

分数背包的代码实现:

def fractional_backpack(goods, W):  # W表示最大负重
    # 贪心是指拿走单位重量更值钱的商品
    goods.sort(key=lambda x: x[0] / x[1], reverse=True)
    m = [0 for _ in range(len(goods))]  # m = [(), (), ()]   # m返回的指是对应的排好序之后的
    total_v = 0      # 总价值
    for i, (prize, weight) in enumerate(goods):
        if W >= weight:
            m[i] = 1
            W -= weight
            total_v += prize
        else:
            m[i] = W / weight
            total_v += m[i] * prize
            W = 0
            break
    return m, total_v


print(fractional_backpack(goods, 50))

3. 数字拼接问题

  •  有n个非负整数,将其按照字符串拼接的方式拼接为一个整数。如何拼接可以使得得到的整数最大?
  • 例如:32,94,128,1286,6,71可以拼接城的最大整数为94716321286128

方法一:可以通过Python中内置的 functools import cmp_to_key进行书写

from functools import cmp_to_key

li = [32, 94, 128, 1286, 6, 71]


def xy_cmp(x, y):
    if x + y < y + x:
        return 1
    elif x + y > y + x:
        return -1
    else:
        return 0


def number_join_1(li):
    li = list(map(str, li))
    li.sort(key=cmp_to_key(xy_cmp))
    return "".join(li)

 方法二:使用冒泡排序进行书写:

def number_join_2(li):
    li = list(map(str, li))
    for i in range(len(li)):
        for j in range(i, len(li)):
            if li[i] + li[j] <= li[j] + li[i]:
                li[i], li[j] = li[j], li[i]
    return "".join(li)

4. 活动选择问题

  • 假设有n个活动,这些活动要占用同一片场地,而场地在某时刻只能供一个活动使用。
  • 每个活动都有一个开始时间si和结束时间fi(题目中时间以整数表示),表示活动在[si, fi)遇见占用场地。
  • 问:安排哪些活动能够使该场地举办的活动的个数最多?

贪心结论:最先结束的活动一定是最优解的一部分。

证明:假设a是所有活动中最先结束的活动,b是最优解中最先结束的活动

  • 如果a = b,结论成立
  • 如果a ≠ b,则b的结束时间一定晚于a的结束时间,则此时用a替换最优解中的b,a一定不与最优解中的其他活动时间重叠,因此替换后的解也是最优解。

示例代码如下所示:

def activity_selection(a):
    res = [a[0]]     #res指最优解,首先只有最先开始的活动
    for i in range(1, len(a)):
        if a[i][0] >= res[-1][1]:   # a[i][0] 表示当前活动的开始时间  res[-1][1]表示最后一个活动的结束时间
            # 满足上述条件则不冲突
            res.append(a[i])
    return res

print(activity_selection(activities))

上一篇:rabbitMQ的基础操作与可视化界面


下一篇:node.js 常用命令