当前位置:   article > 正文

Python数据结构与算法——算法(贪心算法、动态规划

Python数据结构与算法——算法(贪心算法、动态规划

贪心算法

介绍:贪心算法又称贪婪算法,是指在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,它所做出的是在某种意义上的局部最优解。

贪心算法并不保证会得到最优解,但是在某些问题上贪心算法的解就是最优解。要会判断一个问题能否用贪心算法来计算。

例一:找零问题

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

思路:为了找的钱币最少,从最大面额找,找不开就用比它小的找...

  1. t = [100, 50, 20, 5, 1]
  2. def change(t, n):
  3. m = [0 for _ in range(len(t))]
  4. for i, money in enumerate(t):
  5. m[i] = n // money
  6. n = n % money
  7. return m, n
  8. print(change(t, 376))

 例二:背包问题

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

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

分数背包使用贪心算法没有问题,但是0-1背包不可以!

分数背包

  1. goods = [(60, 10), (120, 30), (100, 20)] # 每个商品元组表示(价格, 重量)
  2. goods.sort(key = lambda x:x[0]/x[1], reverse = True) # 按照单价排序
  3. def fractional_backpack(goods, w):
  4. m = [0 for _ in range(len(goods))]
  5. total_v = 0
  6. for i, (price, weight) in enumerate(goods):
  7. if w >= weight:
  8. m[i] = 1
  9. w -= weight
  10. total_v += price
  11. else:
  12. m[i] = w/weight
  13. total_v += m[i] * price
  14. w = 0
  15. break
  16. return m, total_v
  17. print(fractional_backpack(goods, 50))

例三:拼接最大数字问题 、

问题:有n个非负整数,将其按照字符串拼接的方式拼接为一个整数,如何拼接可以使得到的整数最大?

  • 例:32、94、128、1286、6、71可以拼接的最大整数为:94716321286128
  1. from functools import cmp_to_key
  2. li = [32, 94, 128, 1286, 6, 71]
  3. def xy_cmp(x, y):
  4. if x+y > y+x:
  5. return -1
  6. elif x+y < y+x:
  7. return 1
  8. else:
  9. return 0
  10. def number_join(li):
  11. li = list(map(str, li))
  12. li.sort(key=cmp_to_key(xy_cmp))
  13. return "".join(li)
  14. print(number_join(li))

例四:活动选择问题 

问题:假设有n个活动,这些活动要占用同一片场地,而场地在某时刻只能供一个活动使用。

每个活动都有一个开始时间si和结束时间fi(题目中时间以整数表示),表示活动在[si, fi)区间占用场地。

问:安排那些活动能够使该场地举办的活动个数最多?

i1234567891011
si130535688212
fi4567991011121416

思路:最先结束的活动一定是最优解的一部分。

  1. activities = [(1, 4), (3, 5), (0, 6), (5, 7), (3, 9), (5, 9), (6, 10), (8, 11), (8, 12), (2, 14), (12, 16)]
  2. # 保证活动是按照结束时间排好序的
  3. activities.sort(key=lambda x:x[1])
  4. def activity_selection(a):
  5. res = [a[0]]
  6. for i in range(1, len(a)):
  7. if a[i][0] >= res[-1][1]: # 当前活动的开始时间小于等于最后一个入选活动的结束时间
  8. res.append(a[i])
  9. return res
  10. print(activity_selection(activities))

动态规划

介绍:是一种解决复杂问题的数学方法,通常用于优化问题。动态规划将问题分解为更小的子问题,通过解决这些子问题并将它们的解存储起来,最终得到原始问题的解。

思想:

  • 最优子结构(递推式)
  • 重复子问题

什么问题使用动态规划方法?

  • 最优子结构
    • 原问题的最优解中涉及多少个子问题
    • 在确定最优解使用哪些子问题时,需要考虑多少种选择
  • 重叠子问题

例一:斐波那契数列

  1. # 子问题的重复计算
  2. def fibnacci(n):
  3. if n == 1 or n == 2:
  4. return 1
  5. else:
  6. return fibnacci(n-1) + fibnacci(n-2)
  7. # 无重复计算,存在列表中——动态规划(DP)
  8. def fibnacci_no_recurision(n):
  9. f = [0, 1, 1]
  10. if n > 2:
  11. for i in range(n-2):
  12. num = f[-1] + f[-2]
  13. f.append(num)
  14. return f[-1]
  15. print(fibnacci_no_recurision(100))

例二:钢条切割问题

思路:

  • 设长度为n的钢条切割后的最优收益值为r_{n},可以得出递推式:r_{n} = max(p_{n}, r_{1} + r_{n-1}, r_{2} + r_{n-2}, ... , r_{n-1} + r_{1})
  • 第一个参数p_{n }表示不切割
  • 其他n-1个参数分别表示另外n-1种不同切割方案,对方案i=1,2,...,n-1
    • 将钢条切割为长度为i和n-i两段
    • 方案i的收益为切割两端的最优收益之和
  • 考察所有的i,选择其中收益最大的方案

钢条切割问题满足最优子结构,比如长度为9要切为8和1,前面已经算了8的最佳情况,直接使用即可。

自顶向下递归实现

时间复杂度:2^{n}

  1. p = [0, 1, 5, 8, 9, 10, 17, 17, 20, 21, 23, 24, 26, 27, 27, 28, 30, 33, 36, 39, 40]
  2. def cut_rod_recurision_1(p, n):
  3. if n == 0:
  4. return 0
  5. else:
  6. res = p[n]
  7. for i in range(1, n):
  8. res = max(res, cut_rod_recurision_1(p, i) + cut_rod_recurision_1(p, n-i))
  9. return res
  10. def cut_rod_recurision_2(p, n):
  11. if n == 0:
  12. return 0
  13. else:
  14. res = 0
  15. for i in range(1, n+1):
  16. res = max(res, p[i] + cut_rod_recurision_2(p, n-i))
  17. return res
  18. print(cut_rod_recurision_1(p, 15))
  19. print(cut_rod_recurision_2(p, 15))

自底向上动态规划实现

时间复杂度:n^{2}

  1. p = [0, 1, 5, 8, 9, 10, 17, 17, 20, 21, 23, 24, 26, 27, 27, 28, 30, 33, 36, 39, 40]
  2. def cut_rod_dp(p, n):
  3. r = [0]
  4. for i in range(1, n+1):
  5. res = 0
  6. for j in range(1, i+1):
  7. res = max(res, p[j] + r[i-j])
  8. r.append(res)
  9. return r[n]
  10. print(cut_rod_dp(p, 9))

重构解

同时输出最优解和最优切割方案

  • 对于每个子问题,保存切割一次时左边切下的长度
  1. def cut_rod_extend(p, n):
  2. r = [0]
  3. s = [0]
  4. for i in range(1, n+1):
  5. res_r = 0 # 记录价格的最大值
  6. res_s = 0 # 记录价格最大值对应方案的左边不切割部分的长度
  7. for j in range(1, i+1):
  8. if p[j] + r[i-j] > res_r:
  9. res_r = p[j] + r[i-j]
  10. res_s = j
  11. r.append(res_r)
  12. s.append(res_s)
  13. return r[n], s
  14. def cut_rod_solution(p, n):
  15. r, s = cut_rod_extend(p, n)
  16. ans = []
  17. while n > 0:
  18. ans.append(s[n])
  19. n -= s[n]
  20. return ans
  21. print(cut_rod_solution(p, 9))

例三:最长公共子序列实现

问题:一个序列的子序列是在该序列中删去若干元素后得到的序列。

        例:"ABCD"和"BDF"都是"ABCDEFG"的子序列

给定两个序列X和Y,求X和Y长度最大的公共子序列

返回最大公共子序列长度

  1. def lcs_length(x, y):
  2. m = len(x)
  3. n = len(y)
  4. # 创建一个二维数组来存储子问题的解
  5. dp = [[0] * (n + 1) for _ in range(m + 1)]
  6. # 填充dp数组
  7. for i in range(1, m + 1):
  8. for j in range(1, n + 1):
  9. if x[i - 1] == y[j - 1]:
  10. dp[i][j] = dp[i - 1][j - 1] + 1 # i j 位置上的字符匹配时,来自与左上方
  11. else:
  12. dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])
  13. # 从dp数组中找到最长公共子序列的长度
  14. return dp[m][n]
  15. # 测试
  16. x = "abcde"
  17. y = "ace"
  18. print(lcs_length(x, y))

 

代码自己手动敲一遍理解更深哦!

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小蓝xlanll/article/detail/434806
推荐阅读
相关标签
  

闽ICP备14008679号