该篇学习笔记来自于《你也能看得懂的python算法书》
随着不断的学习,越来越感觉到算法的神奇魅力,它的使用逻辑堪称完美。现分享以下学习过程,希望对大家有所帮助,若发现错误,也欢迎向小编留言哦。
动态规划算法
将待求解问题拆分成一系列相互交叠
的子问题,通过递推关系
定义各子问题的求解策略,并随时记录子问题的解,避免了对交叠子问题重复求解。
动态规划算法中有三要素:即最优子结构
、边界
和状态转移函数
。最优子结构是指每个阶段的最优状态可以从之前某个或某些状态直接得到;边界问题是指问题最小子集
的解;状态转移函数是指从一个阶段向另一个阶段过度的具体模式,描述的是两个相邻子问题
之间的关系。
具备以上三个要素的问题均可以采用动态规划的策略进行求解,以下面四个问题为例:
问题描述:
最长递归子序列即给定一个序列,求解其中最长的递增的子序列的长度
注意:
在求解过程中,长度为n的递增子序列可能不止一个,但是在所有长度为n的子序列中,有一个子序列是比较特殊的,那就是最大元素最小的递增子序
def getdp1(arr):
n=len(arr)
dp=[0]*n
for i in range(n):
dp[i]=1
for j in range(i):
if arr[i]>arr[j]:
dp[i]=max(dp[i],dp[j]+1)
return dp
def generateLIS(arr,dp):#dp中存放以各元素为结尾元素时形成的递增序列的最大长度
n=max(dp)#数组的最大值-整体的最大长度
index=dp.index(n)#最大值下标,形成最长递增子序列的结尾元素的下标
lis=[0]*n
n-=1
lis[n]=arr[index]#定位序列中最后一个元素
for i in range(index,-1,-1):#从右向左
if arr[i]<arr[index] and dp[i]==dp[index]-1:
n-=1
lis[n]=arr[i]
index=i
return lis
arr=[2,3,1,4,5,9,6,0]
dp=getdp1(arr)
result=generateLIS(arr,dp)
print(result)
时间复杂度为O(n*n),考虑使用二分查找,降低复杂度为O(nlogn)
def getdp2(arr):
n=len(arr)
end,dp=[0]*n,[0]*n
end[0],dp[0]=arr[0],1
l,r,right,m=0,0,0,0
for i in range(1,n):
l=0
r=right
#二分查找,若找不到则end[l或r]是比arr[i]大而又接近的数
#若arr[i]比ends有效区的值都大,则l=right+i
while l<=r:
m=(l+r)//2
if arr[i]>end[m]:
l=m+1
else:
r=m-1
right=max(right,l)
end[l]=arr[i]
dp[i]=l+1
return dp
问题描述:
求N个重量为W1,W2,…,Wn、价值为V1,V2,…,Vn的物品和一个承重量为W的背包,求让包里装入的物品具有最大价值总合的物品子集。
def map_record(n,c,w,v):
#初始化记录表
record_map=[[0 for i in range(c+1)]for i in range(len(n)+1)]
for i in range(1,len(n)+1):
for j in range(1,c+1):#应用状态转移函数填写记录表
if j<w[i-1]:
record_map[i][j]=record_map[i-1][j]
else:
record_map[i][j]=max(record_map[i-1][j],record_map[i-1][j-w[i-1]]+v[i-1])
return record_map
def show(n,c,w,res):
print('最大价值为:',res[len(n)][c])
x=[False for i in range (len(n)+1)]
j=c
i=len(n)
#回溯
while i>=0:
if res[i][j]>res[i-1][j]:
x[i]=True
j-=w[i-1]
i-=1
print('选择的物品为:')
for i in range(len(n)+1):
if x[i]:
print('第',i,'个,',end='')
print('')
n=['a','b','c','d']
c=8
w=[2,4,5,3]
v=[5,4,6,2]
res=map_record(n,c,w,v)
show(n,c,w,res)
问题描述:
某一地区发现了5座金矿,每座金矿的黄金储量不同,需要参与挖掘的工人数也不同,假设参与挖矿工人的总数是10人,且每座矿要么全挖,要么不挖,不能派出一半人挖取一半金矿。要求用程序求解,要想尽可能得到多的黄金,应该选择挖取哪几座金矿?
def goldMining(n,w,g,p):
results=[]#保存返回结果的数组
preresults=[]#保存上一行结果的数组
results.append(0)#数组第一位为辅助位,填充为0
preresults.append(0)
for i in range(1,w+1):#从左到右填充表格第一行的内容,填充边界格子的值
results.append(0)#初始化结果数组
if i<p[0]:#人数少于第一个金矿所需人数,黄金量为0
preresults.append(0)
else:#否则,黄金量为第一个金矿储量
preresults.append(g[0])
results[i]=preresults[i]
for i in range(1,n):#外层为金矿数量
for j in range(1,w+1):#内层为矿工数量(注意下标)
if j<p[i]:
results[j]=preresults[j]
else:
results[j]=max(preresults[j],preresults[j-p[i]]+g[i])
preresults=results.copy()
del results[0]
return results
n=5
w=10
g=[400,500,200,300,350]
p=[5,5,3,4,3]
result=goldMining(n,w,g,p)
print (result)
该问题解决思路和背包问题一致,但注意两者在处理第一行数据的不同之处
问题描述:
小明家住在二楼,每次回家都需要经过一个有10层台的楼梯。小明每次选择可以走一级台阶或二级台阶。请帮助小明算算他从楼下到家一共有多少种走法。
dp=getdp(arr)
result=generateLIS(arr,dp)
print(result)
def upstairs(n):
a=1
b=2
temp=0
if n<1:
print(0)
if n==1:
print(1)
if n==2:
print(2)
for i in range(3,n+1):#迭代求解各级台阶的走法
temp=a+b
a=b
b=temp
print(temp)
upstairs(3)