Python Prep随想练习-Day3

Day3-part1

Longest Common Subsequence 最长公共子序列问题

问题描述

这里主要想解决的问题是,确定两个字符串所包含的公共序列,允许字符之间存在间隙,但是不允许改变字符之间的顺序,例如:
序列1: GATTACA
序列2: TACTGTC
最长公共子序列显然为: ATTC, 但是如何确定公共子序列最大长度,以及具体是什么呢?

公共子序列解决思路

[1 ] 确定dp数组(dp table)以及下标的含义
[2 ] 确定递推公式
[3 ] dp数组如何初始化
[4 ] 确定遍历顺序
[5 ] 举例推导dp数组

我们解决这个问题,如果分别从两个序列的头部向后看,比如现在:
S_source=GATTACA
S_target=TACTGTC
从target第一个元素开始看起

GATTACT
TAC
此时LCS=3,因为S_target后续元素T,在source中找不到了,最大的长度就为3!

同理,如果反过来看呢:

TACTGTC
GATTACA
此时LCS=3,因为后续元素找不到了,最大的长度就为3!

因此,很自然我们可以想到,想获取最长公共子序列,需要两个字符串序列,分别去对方的序列中寻找,那么dp数组应该是一个二维数组。dp数组每个位置的含义是什么呢?
dp[i][j] 对应的是 S_target[0:j] 在序列 S_source[0:i] 中, 两者对应的最大公共子序列。
比如: dp[0][5]对应的是 ‘G’ 在序列 ‘TACTGT’ 中的公共子序列长度, 显然为1
比如: dp[3][5]对应的是 ‘GATT’ 在序列 ‘TACTGT’ 中的公共子序列长度, 显然为3

s_source='GATTACA'
s_target='TACTGTC'
dp=[[0 for i in range(len(s_target))] for j in range(len(s_source))]

下图为对应生成的二维数组,并且全部由0充满
Alt
那么,我们应该对dp数组进行初始化。根据如上的分析,很容易对第一行和第一列进行初始化

for i in range(len(s_target)):
        if s_source[0]==s_target[i]:
            for j in range(i,len(s_traget)):
                result[0][j]=1
            break
for i in range(len(s_source)):
        if s_target[0]==s_source[i]:
            for j in range(i,len(s_source)):
                result[j][0]=1
            break	

在这里插入图片描述

递推公式的确定

动态规划的核心是确定递推公式,针对很多问题应该首先确定递推公式,随后确定dp数组初始化,但是在这里的话,为了针对分析的便利显示,我们首先将图绘出,进行分析。
针对问号点的数值我们应该如何确定呢?

针对点dp[ i ] [ j ],如果满足s_source[ i ] == s_target[ j ]的话,那公共子序列的长度肯定就会+1
是针对谁+1呢? 显然是针对dp[ i-1 ] [ j-1 ]的数值+1, 即针对上一个状态+1
if s_source[ i ] == s_target[ j ] dp[ i ] [ j ]=dp[ i-1 ] [ j-1 ]+1
当时当 s_source[ i ] != s_target[ j ],现在应该执行什么操作呢?
想一想~
那现在肯定不能是前一个状态+1了,甚至说,都不应该考虑+1的事情了!
但是现在还是想知道公共子序列的最长长度,那么现在dp[ i ] [ j ] 应该就是对应未进入i,j状态的最大值了!
dp[ i ] [ j ] =max(dp[ i-1 ] [ j ],dp[ i ] [ j-1 ])
if s_source[ i ] != s_target[ j ] dp[ i ] [ j ]=max(dp[ i-1 ] [ j ],dp[ i ] [ j-1 ])

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

for i in range(1,len(s_source)):
	for j in range(1,len(s_target)):
		if s_source[i]==s_target[j]:
			dp[i][j]=dp[i-1][j-1]+1
		else:
			dp[i][j]=max(dp[i-1][j],dp[i][j-1])

至此,可以获得完全的dp矩阵,最大子序列长度应当出现在dp[ len(s_source)-1 ][ len(s_target)-1 ]
即右下角的点的值!

获取序列信息

在这里插入图片描述
如何获得子序列信息分别是什么呢?
需要进行的操作是从右下角开始寻找,逐步往上或者往左移动,标记dp[ i ][ j ] >dp[ i-1 ][ j ] 同时dp[ i ][ j ] >dp[ i ][ j-1 ]的点,对应的字符即为我们想知道的子序列

common_string=[]
i=len(s_source)-1
j=len(s_target)-1
while(i>=1 and j>=1):
    if dp[i][j]>dp[i-1][j] and dp[i][j]>dp[i][j-1]:
        common_string.append((s_source[i]))
        i-=1
        j-=1
    elif dp[i][j]==dp[i-1][j]:
        i-=1
    elif dp[i][j]==dp[i][j-1]:
        j-=1
return common_string

Day3-part2

The 0/1 knapsack Problem 0/1背包问题

问题描述

你有一个背包需要填充,背包的体积容量有要求。针对每个物品,你只可以选择0件/1件。每个物品有自己的体积(重量),和他对应的价值,现在你要做的事情:合理选择物品获得背包最大价值!
物品(items):( wi , valuei ) 物品重量、价值
(3,9);(1,7);(12,18);(5,3);(2,11)
背包容量:10 最大价值:9+7+11=27 选择物品:3+1+2
穷举的话,肯定是可以举出来的,但是这是否有点太暴力了。
我们在这里想一下用动态规划怎么来做呢?

0/1背包解决思路

[1 ] 确定dp数组(dp table)以及下标的含义
[2 ] 确定递推公式
[3 ] dp数组如何初始化
[4 ] 确定遍历顺序
[5 ] 举例推导dp数组

第一步,确定dp数组,这里的dp数组我们首先想到的是,延续part1的思路,我们构建一个二维数组。
整个二维数组,row=len(items), column=weight+1。
行代表是否放入某个物品进行考虑,列代表针对不同容量的背包从0容量开始考虑。
dp[ i ][ j ] 代表目前情况下背包内能放下的最大价值。
在这里插入图片描述
第二步,确定递推公式。
针对任意的 i,j。i 代表是否要放入物品 i ,j 代表的是当前的背包容量。 很直观我们想到的是双重循环,先遍历 i 再遍历 j。针对dp[ i ][ j ],肯定要取当前能取到的最大值,才能确保背包含有的价值是最大值。
dp[ i ][ j ]的取值无非是两种情况:

  • 不放入items[ i ],那么dp[ i ][ j ]=dp[ i-1 ][ j ] ,也就是说和不放入的情况一模一样喽!
  • 如果放入items[ i ],那么dp[ i ][ j ]=dp[ i-1 ][ j-items[ i ][ 0 ] ]+items[ i ][ 1 ] (这个不懂看图)
    在这里插入图片描述
    现在看我画圈圈的地方,现在是不是针对背包容量为5,考虑item(1,7)的情况!
    如果不放入item的话,那么dp[ i ][ j ]=9
    如果放入item的话,那么就相当于上一行背包容量为4的时候的值**(黄色点)**,再加上item(1,7),dp[ i ][ j ]=9+7=16,这两个取最大值就是这个红色圈圈应该放进去的值,对吧!
    dp[ i ][ j ]=max(dp[ i-1 ][ j ], dp[ i-1 ][ j-items[ i ][0]] +items[ i ][ 1 ])

第三步,dp数组初始化
这一步也很简单的对吧!因为最开始肯定是空空荡荡的背包喽,每个点的价值都会是0!
dp[ i ][ j ]=0,千真万确。

第四步,确定遍历顺序
遍历顺序:先行后列,前面有说过,那么现在通过我们的代码实现吧!

value=[
    [3,9],
    [1,7],
    [12,18],
    [5,3],
    [2,11]
] #前项代表重量,后项代表价值
def get_max_value(weight):
	dp=[[0 for i in range(weight+1)] for j in range(len(value)+1)]
	for i in range(1,len(value)+1):
		for j in range(1,weight+1):
			if j >=value[i-1][0]:
				dp[i][j]=max(dp[i-1][j],dp[i][j-value[i-1][0]]+value[i-1][1])
			else:
				dp[i][j]=dp[i-1][j]
	return dp[len(value)][weight]

在这里插入图片描述

def get_max_value(weight):
    dp=[[0 for i in range(weight+1)]for j in range(len(value))]
    for i in range(value[0][0],weight+1):
        dp[0][i]=value[0][1]
    for i in range(1,len(value)):
        for j in range(0,weight+1):
            if j>=value[i][0]:
                dp[i][j]=max(dp[i-1][j],dp[i-1][j-value[i][0]]+value[i][1])
            else:
                dp[i][j]=dp[i-1][j]
    return dp[len(value)-1][weight]

这就是执行完的样子了,右下角就是最大价值了,自己动手实现一下吧!

找到具体放入的物品

那么,我们怎么才能知道具体放入了什么物品呢

因为在dp数组中,最后一列代表着背包容量为weight的最大价值,那么在这里对最后一列进行分析。
在这里插入图片描述

27-16=11 筛选出item(2,11)
16-9=7 筛选出item(1,7)
9-0=9 筛选出item(3,9)
最终得到结果 item(2,1,3)

这里理解怎么出来的就好,基本上不会让你去实现的,fine!
现在pdf上面的内容就实现完了!Day3你已经成功拿下了!

一种可行的解决方案

现在我们的dp数组是一个二维数组对吧!这肯定会占用很大的存储空间,我们用一维数组能不能处理这个事情呢,可以的,是可以的。
在这里插入图片描述
数组递推方法:

dp[ i ]=max(dp[ i ],dp[ i-items[ i ][ 0 ] ]+items[ i ][ 1 ] )
还是考虑两种情况,是否放入items[i]
如果不放入: dp[ i ]=dp[ i ]
如果放入: dp[ i ]=dp[ i-items[ i ][ 0 ] ]+items[ i ][ 1 ] 想一下为什么呢?其实和二维是一样的,就是预留容量价值 + 物品价值!
两者取最大值是不是就完事了!

数组初始化:
初始均为0

def max_value(weight):
     dp=[0]*(weight+1)
     for i in range(len(value)):
         for j in range(weight,value[i][0]-1,-1):
             dp[j]=max(dp[j],dp[j-value[i][0]]+value[i][1])
    return dp[weight]

注意仔细阅读两层循环嵌套,第二层循环嵌套,是不是逆向进行的? 为什么要逆向呢?
自己想一下,如果是正向的话,如果重量满足情况,在不断更新中,是不是将这个物体重复放入了,这样就不是 0/1背包 问题了!

自己动手实现吧,辛苦了!

海客谈瀛洲,烟涛微茫信难求。

越人语天姥,云霞明灭或可睹。

天姥连天向天横,势拔五岳掩赤城。

天台一万八千丈,对此欲倒东南倾。

我欲因之梦吴越,一夜飞度镜湖月。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值