数据结构与算法【进阶】
贪心算法
在对问题求解时,总市做出当前看来时最好的选择。
- 不从整体最优上考虑
- 局部最优解
找零问题
假设商店老板需要找零n元钱,钱币的面额:100元、50元、20元、5元、1元,如何找零使得所有钱币数量最少?
t = [100,50,20,5,1]
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
背包问题
一个小偷在某个商店发现右n个商品,第i个商品价值vi元,重wi千克。他希望拿走的价值尽量高,但他的背包子最多只能容纳W千克的东西。他应该拿走哪些商品?
0-1背包:对于一个商品,小偷要么把它完整拿走,要么留下。不能只拿走一部分,或把一个商品拿走多次。(商品为金条)
分数背包:对于一个商品,小偷可以拿走其中任意一部分。(商品为金砂)
goods = [(60,10),(100,20),(120,30)] #(价格,重量)
goods.sort(key=lambda x:x[0]/x[1],reverse=True)
def fractional_backpack(goods,w):
m = [0 for _ in range(len(goods))]
for i,(price,weight) in enumerate(goods):
if w >= weight:
m[i]=1
w -= weight
else:
m[i] = w / weight
w = 0
break
return m
拼接最大数字问题
有n个非负整数,将其按照字符串拼接的方式拼接为一个整数。如何拼接可以使得得到的整数最大?
from functools import cmp_to_key
li = [32,94,238,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(li):
li = list(map(str,li))
li.sort(key=cmp_to_key(xy_cmp))
return "".join(li)
活动选择问题
假设有n个活动,这些活动要占用同一片场地,而场地在某时刻只能共一个活动使用
每个活动都有一个开始时间si和结束时间fi,表示活动在[si,fi)区间占用场地
问:安排哪些活动能够使该场地举办的活动的个数最多?
| i | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
|---|---|---|---|---|---|---|---|---|---|---|---|
| si | 1 | 3 | 0 | 5 | 3 | 5 | 6 | 8 | 8 | 2 | 12 |
| fi | 4 | 5 | 6 | 7 | 9 | 9 | 10 | 11 | 12 | 14 | 16 |
贪心结论:最先结束的活动一定是最优解的一部分
activities = [(1,4),(3,5),(8,6),(5,7),(3,9),(6,10),(8,11),(8,12),(2,14),(12,16)]
# 保证活动是按结束时间排号序的
activities.sort(key=lambda x:x[1])
def activity_slection(a):
res = [a[0]]
for i in range(1,len(a)):
if a[i][0]>=res[-1][1]: # 当前活动的开始时间小于等于最后一个入选活动的结束时间
# 不冲突
res.append(a[i])
return res
动态规划
- 最优子结构(递推式)
- 重复子问题,将重复的子问题存起来
斐波那契数列
-
Fn=Fn-1+Fn-2
-
递归求解第n项
def fib(n): if n == 1 or n == 2: return 1 else: return fib(n-1)+fib(n-2) ''' 为什么递归效率低? 因为有重复计算 f(5)=f(4)-f(3) f(4)=f(3)-f(2) ''' # 此处循环方法使用的动态规划思想 def fib_no_rec(n): f = [0,1,1] if n > 2: for i in range(n-2): num = f[-1]+f[-2] f.append(num) return f[n]
钢条切割问题
某公司出售钢条,出售价格与钢条长度之间的关系如下表:
| 长度i | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
|---|---|---|---|---|---|---|---|---|---|---|
| 价格pi | 1 | 5 | 8 | 9 | 10 | 17 | 17 | 20 | 24 | 30 |
问题:现有一段长度为n的钢条和上面的价格表,求切割钢条方案,使得总收益最大
思考:长度为n的钢条的不同切割方案有几种



p = [0,1,5,8,9,10,17,17,20,24,30]
# 递归方法
def cut_rod_recurision_1(p,n):
if n==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
# 自顶向下实现
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

# 自底向上实现
def cur_rod_dp(p,n):
r = [0]
for i in range(1,n+1):
q = 0
for j in range(1,i+1):
q = max(q,p[j]+r[i-j])
r.append(q)
return r[n]
# 最优解+最优切割方案
def cur_rod_extend(p,n):
r = [0]
s = [0]
for i in range(1,n+1):
res_r = 0 # 价格的最大值
res_s = 0 # 价格最大值对应方案的左边不切割部分的长度
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 = cur_rod_extend(p,n)
ans = []
while n > 0:
ans.append(s[n])
n -= s[n]
return ans
时间复杂度O(n²)

最长公共子序列




# 动态规划法
def lcs_length(x,y):
m = len(x)
n = len(y)
c = [[0 for _ in range(n+1)] for _ in range(m+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)]
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
elif c[i-1][j] > c[i][j-1]: # 来自上方
c[i][j] = c[i-1][j]
b[i][j] = 2
else: # 来自左方
c[i][j] = c[i][j-1]
b[i][j] = 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:
j -= 1
return "".join(reversed(res))
欧几里得算法
最大公约数
gcd(a,b) = gcd(b,a mod b)
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
实现分数计算
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(a,b):
while b > 0:
r = a % b
a = b
b = r
return a
def __str__(self):
retunr "%d%d" %(%self.a,self.b)
def zxgs(a,b): # 最小公倍数
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.zxgs(b,d)
fenzi = a * fenmu / b + c * fenmu / d
return Fraction(fenzi,fenmu)
RSA算法
现代密码系统:加密算法是公开的,密钥是秘密的
- 对称加密
- 非对称加密

过程



浙公网安备 33010602011771号