算法进阶
作者:互联网
贪⼼心算法(⼜又称贪婪算法)
是指,在对问题求解时,总是做 出在当前看来是好的选择。也就是说,不不从整体优上加 以考虑,他所做出的是在某种意义上的局部优解。
贪⼼心算法并不不保证会得到优解,但是在某些问题上贪⼼心算 法的解就是优解。要会判断⼀一个问题能否⽤用贪⼼心算法来计 算。
找零问题:
假设商店⽼老老板需要找零n元钱,钱币的⾯面额有:100元、50元、 20元、5元、1元,如何找零使得所需钱币的数量量少?
t = [100, 50, 20, 5] def change(t, n): m = [0 for _ in range(len(t))] for i, money in enumerate(t): m[i] = n // money n = n % money return m, n print(change(t, 376))
背包问题
⼀一个⼩小偷在某个商店发现有n个商品,第i个商品价值vi元,重wi千克。他希望 拿⾛走的价值尽量量⾼高,但他的背包多只能容纳W千克的东⻄西。他应该拿⾛走哪些 商品?
0-1背包:对于⼀一个商品,⼩小偷要么把它完整拿⾛走,要么留留下。不不能只拿⾛走 ⼀一部分,或把⼀一个商品拿⾛走多次。(商品为⾦金金条)
分数背包:对于⼀一个商品,⼩小偷可以拿⾛走其中任意⼀一部分。(商品为⾦金金砂)
举例例:
商品1:v1=60 w1=10
商品2:v2=100 w2=20
商品3:v3=120 w3=30
背包容量量:W=50
对于0-1背包和分数背 包,贪⼼心算法是否都能 得到优解?为什什么?
#! /usr/bin/env python # -*- coding: utf-8 -*- # Date: 2018/4/14 # 分数背包 goods = [(60, 10),(100, 20),(120, 30)] # 每个商品元组表示(价格, 重量) goods.sort(key=lambda x: x[0]/x[1], reverse=True) # 排序按单位重量价格 降序 def fractional_backpack(goods, w): # w背包最大重量 m = [0 for _ in range(len(goods))] # m取的排好序的商品数量 total_v = 0 for i, (prize, weight) in enumerate(goods): if w >= weight: # 包的重量大于等于商品重量 m[i] = 1 # 表示这个商品拿多少 拿走所有的 total_v += prize #拿走商品的价值 w -= weight # 更新w值 else: # 否则只拿一部分 m[i] = w / weight # 拿走部分的数量 total_v += m[i] * prize # 拿走部分商品的价值 w = 0 # 更新w值 break return total_v, m print(fractional_backpack(goods, 50))
拼接最大数字问题:
有n个⾮非负整数,将其按照字符串串拼接的⽅方式拼接为⼀一个整数。 如何拼接可以使得得到的整数⼤大?
例:32,94,128,1286,6,71可以拼接除的⼤大整数为 94716321286128
from functools import cmp_to_key # 可以传一个老式的cmp函数可以转换成现在的key函数 li = [32, 94, 128, 1286, 6, 71] def xy_cmp(x, y): #老式的cmp比较函数 排序规则 if x+y < y+x: # 两个参数就是两个元素 如果 x+y < y+x return 1 # 返回1 表示x>y 他们俩要交换 elif x+y > y+x: return -1 else: return 0 def number_join(li): li = list(map(str, li)) li.sort(key=cmp_to_key(xy_cmp)) return "".join(li) print(number_join(li))
活动选择问题
假设有n个活动,这些活动要占⽤用同⼀一⽚片场地,⽽而场地在某时 刻只能供⼀一个活动使⽤用。
每个活动都有⼀一个开始时间si和结束时间fi(题⽬目中时间以整数 表示),表示活动在[si, fi)区间占⽤用场地。 问:安排哪些活动能够使该场地举办的活动的个数多? i是序号
贪⼼心结论:先结束的活动⼀一定是优解的⼀一部分。
证明:假设a是所有活动中先结束的活动,b是优解中先结束的活动。
如果a=b,结论成⽴立。
如果a≠b,则b的结束时间⼀一定晚于a的结束时间,则此时⽤用a替换掉优解中 的b,a⼀一定不不与优解中的其他活动时间重叠,因此替换后的解也是优解。
activities = [(1,4), (3,5), (0,6), (5,7), (3,9), (5,9), (6,10), (8,11), (8,12), (2,14), (12,16)] # 一个元组表示一个活动 开始时间和结束时间 # 保证活动是按照结束时间排好序的 activities.sort(key=lambda x:x[1]) def activity_selection(a): res = [a[0]] for i in range(1, len(a)): if a[i][0] >= res[-1][1]: # 当前活动的开始时间大于等于最后一个入选活动的结束时间 # 不冲突 res.append(a[i]) return res print(activity_selection(activities))
动态规划
从斐波那契数列看动态规划
斐波那契数列列:
Fn =Fn−1+Fn−2
练习:使⽤用递归和⾮非递归的⽅方法来求解斐波那契数列列的第n项
# 递归(执行效率低) # 子问题的重复计算 def fibnacci(n): if n == 1 or n == 2: return 1 else: return fibnacci(n-1) + fibnacci(n-2) # 子问题的重复计算 # f(5)=f(4)+f(3) # f(4)=f(3)+f(2) # ....... # 非递归 # 动态规划(DP)的思想 = 递推式 + 重复子问题(把需要的子问题存起来)
def fibnacci_no_recurision(n): f = [0,1,1] if n > 2: for i in range(n-2): num = f[-1] + f[-2] f.append(num) # 将算过的数加到f里 不会再重复计算了 return f[n] print(fibnacci_no_recurision(100))
钢条切割问题
某公司出售钢条,出售价格与钢条⻓长度之间的关系如下表:
问题:现有⼀一段⻓长度为n的钢条和上⾯面的价格表,求切割钢条 ⽅方案,使得总收益⼤大。
钢条切割问题̶̶递推式:
设⻓度为n的钢条切割后最优收益值为rn,可以得出递推式:
第⼀个参数pn表示不切割
其他n-1个参数分别表示另外n-1种不同切割⽅案,对⽅案i=1,2,...,n-1
将钢条切割为⻓度为i和n-i两段
案i的收益为切割两段的最优收益之和
考察所有的i,选择其中收益最⼤的⽅案
p = [0, 1, 5, 8, 9, 10, 17, 17, 20, 21, 23, 24, 26, 27, 27, 28, 30, 33, 36, 39, 40] # 价格表 # p = [0, 1, 5, 8, 9, 10, 17, 17, 20, 24, 30] # 第一种 会重复计算效率低 def cut_rod_recurision_1(p, n): if n == 0: # 如果长度是0 就没卖钱 return 0 else: res = p[n] for i in range(1, n): res = max(res, cut_rod_recurision_1(p, i) + cut_rod_recurision_1(p, n-i)) return res
钢条切割问题̶̶最优子结构:
可以将求解规模为n的原问题,划分为规模更⼩的⼦问题:完成⼀次切割 后,可以将产⽣的两段钢条看成两个独⽴的钢条切个问题。
组合两个⼦问题的最优解,并在所有可能的两段切割⽅案中选取组合收 益最⼤的,构成原问题的最优解。
钢条切割满⾜最优⼦结构:问题的最优解由相关⼦问题的最优解组合⽽ 成,这些⼦问题可以独⽴求解。
钢条切割问题还存在更简单的递归求解⽅法:
从钢条的左边切割下⻓度为i的⼀段,只对右边剩下的⼀段继续进⾏切割,左 边的不再切割
递推式简化为
不做切割的⽅案就可以描述为:左边⼀段⻓度为n,收益为pn,剩余⼀段⻓度 为0,收益为r0=0
p = [0, 1, 5, 8, 9, 10, 17, 17, 20, 21, 23, 24, 26, 27, 27, 28, 30, 33, 36, 39, 40] # 价格表 # p = [0, 1, 5, 8, 9, 10, 17, 17, 20, 24, 30]
# 第二种 左边不切割 右边切割 def cut_rod_recurision_2(p, n): if n == 0: return 0 else: res = 0 for i in range(1, n+1): res = max(res, p[i] + cut_rod_recurision_2(p, n-i)) return res
递归算法由于重复求解相同⼦问题,效率极低
钢条切割问题̶̶动态规划解法 --自底向上实现
动态规划的思想:
每个⼦问题只求解⼀次,保存求解结果
之后需要此问题时,只需查找保存的结果
p = [0, 1, 5, 8, 9, 10, 17, 17, 20, 21, 23, 24, 26, 27, 27, 28, 30, 33, 36, 39, 40] # 价格表 # p = [0, 1, 5, 8, 9, 10, 17, 17, 20, 24, 30] # 第三种自底向上 @cal_time def cut_rod_dp(p, n): r = [0] for i in range(1, n+1): res = 0 for j in range(1, i+1): res = max(res, p[j] + r[i - j]) r.append(res) return r[n]
最优解已经找到 那最优切割方案呢?
s[i]就是切一刀下去不切的部分是多少就是左边是多少
序号是1的 切割方案 左边是1 右边是0 就是不切割
序号是2、3的也是同理
序号是4的 最后是 左边是2右边是2 切割
序号9是 切割左边是3右边是6 再看6 左边是6右边是0 所以9分割方案 为3和6 最有价格25
# 动态规划思想 def cut_rod_extend(p, n): r = [0] s = [0] for i in range(1, n+1): res_r = 0 # 价格的最大值 res_s = 0 # 价格最大值 对应方案 的左边不切割部分的长度 如图表的s[i] for j in range(1, i + 1): if p[j] + r[i - j] > res_r: res_r = p[j] + r[i - j] res_s = j r.append(res_r) s.append(res_s) return r[n], s # 输出最优方案 def cut_rod_solution(p, n): r, s = cut_rod_extend(p, n) ans = [] while n > 0: ans.append(s[n]) n -= s[n] return ans r, s = cut_rod_extend(p,20) print(s) print(cut_rod_dp(p, 20)) print(cut_rod_solution(p, 20))
最⻓公共子序列:
⼀个序列的⼦序列是在该序列中删去若⼲元素后得 到的序列。 例:“ABCD”和“BDF”都是“ABCDEFG”的⼦序列
最⻓公共⼦序列(LCS)问题:给定两个序列X和Y,求X和Y⻓度最⼤的公共⼦ 序列。
例:X="ABBCBDE" Y="DBBCDB" LCS(X,Y)="BBCD"
应⽤场景:字符串相似度⽐对
怎么找这个最长公共子序列呢
思考:暴⼒穷举法的时间复杂度是多少?
思考:最⻓公共⼦序列是否具有最优⼦结构性质?
箭头指的是从哪来的值
这个表怎么看
看末位置是4 再看序列对应位置是否相等 B和A不等 再找4是哪来的看箭头4是上来的 再看这个4位置的序列是否相等 相等就出去 依次类推 看4 3 2 1 相等位置分别是ABCB
#! /usr/bin/env python # -*- coding: utf-8 -*- # Date: 2018/9/9 # 推导出表中的数字 def lcs_length(x, y): m = len(x) n = len(y) c = [[0 for _ in range(n+1)] for _ in range(m+1)] # m+1行 n+1列 for i in range(1, m+1): for j in range(1, n+1): if x[i-1] == y[j-1]: # i j 位置上的字符匹配的时候,来自于左上方+1 c[i][j] = c[i-1][j-1] + 1 else: c[i][j] = max(c[i-1][j], c[i][j-1]) return c[m][n] # 找箭头路径 def lcs(x, y): m = len(x) n = len(y) c = [[0 for _ in range(n + 1)] for _ in range(m + 1)] b = [[0 for _ in range(n + 1)] for _ in range(m + 1)] # 和c同样位置记录箭头用的 定义 1 左上方 2 上方 3 左方 for i in range(1, m+1): for j in range(1, n+1): if x[i-1] == y[j-1]: # i j 位置上的字符匹配的时候,来自于左上方+1 c[i][j] = c[i-1][j-1] + 1 b[i][j] = 1 # 1 左上方 elif c[i-1][j] > c[i][j-1]: # 来自于上方 i是行 c[i][j] = c[i-1][j] b[i][j] = 2 # 2 上方 else: c[i][j] = c[i][j-1] b[i][j] = 3 # 3 左方 return c[m][n], b # 从最后一个位置回溯找箭头 def lcs_trackback(x, y): c, b = lcs(x, y) i = len(x) j = len(y) res = [] while i > 0 and j > 0: if b[i][j] == 1: # 来自左上方=>匹配 只有是斜箭头才是匹配的 res.append(x[i-1]) # 拿出来村上 i -= 1 j -= 1 elif b[i][j] == 2: # 来自于上方=>不匹配 i -= 1 else: # ==3 来自于左方=>不匹配 j -= 1 return "".join(reversed(res)) print(lcs_trackback("ABCBDAB", "BDCABA"))
欧⼏⾥得算法:
最大公约数
约数:如果整数a能被整数b整除,那么a叫做b的倍数,b叫做 a的约数。
给定两个整数a,b,两个数的所有公共约数中的最⼤值即为最 ⼤公约数(Greatest Common Divisor, GCD)。
例:12与16的最⼤公约数是4
如何计算两个数的最⼤公约数:
欧⼏⾥得:辗转相除法(欧⼏⾥得算法)
《九章算术》:更相减损术
欧⼏⾥得算法:
gcd(a, b) = gcd(b, a mod b) # a mod b a模b a除以b取余
例:gcd(60, 21) = gcd(21, 18) = gcd(18, 3) = gcd(3, 0) = 3
def gcd(a, b): if b == 0: return a else: return gcd(b, a % b) # 两种方法 def gcd2(a, b): while b > 0: r = a % b a = b b = r return a print(gcd2(12,16))
利⽤欧⼏⾥得算法实现⼀个分数类,⽀持分数的四则运算
#! /usr/bin/env python # -*- coding: utf-8 -*- # Date: 2018/9/9 class Fraction: def __init__(self, a, b): self.a = a self.b = b x = self.gcd(a,b) self.a /= x self.b /= x def gcd(self, a, b): while b > 0: r = a % b a = b b = r return a def zgs(self, a, b): # 12 16 -> 4 # 3*4*4=48 x = self.gcd(a, b) return a * b / x def __add__(self, other): a = self.a b = self.b c = other.a d = other.b fenmu = self.zgs(b, d) fenzi = a * fenmu / b + c * fenmu / d return Fraction(fenzi, fenmu) def __str__(self): return "%d/%d" % (self.a, self.b) a = Fraction(1,3) b = Fraction(1,2) print(a+b)
RSA加密算法简介
标签:return,进阶,res,self,range,算法,def,切割 来源: https://www.cnblogs.com/erhuoyuan/p/16302642.html