动态规划 样例解析

动态规划算法是一种解决最优化问题的算法,其主要思想是将原问题分解成若干个子问题,通过保存子问题的解来避免重复计算,从而得到原问题的最优解。动态规划算法通常采用自底向上的方式求解,即先求解子问题,然后根据子问题的解求解更大规模的问题,直到求解原问题。

动态规划算法的基本步骤如下:

1.定义状态:将原问题分解成若干个子问题,定义状态表示子问题的解。
2.定义状态转移方程:根据子问题的解推导出更大规模的问题的解,即定义状态转移方程。
3.定义边界条件:确定最小子问题的解。
4.计算顺序:按照计算顺序计算子问题的解,最终求解原问题。

以下是一些例子,大家不懂的时候可以一步步把dp数组画出来,会比较直观。

(1)最长上升子序列
最长上升子序列问题是指:给定一个序列,找出其中最长的上升子序列。例如,序列[10, 9, 2, 5, 3, 7, 101, 18]的最长上升子序列为[2, 5, 7, 101],长度为4。

动态规划算法的实现方法如下:
1.定义状态:设dp[i]表示以第i个元素为结尾的最长上升子序列的长度。
2.定义状态转移方程:对于第i个元素,如果它前面有元素j比它小且dp[j]的值大于等于dp[i],则dp[i] = dp[j] + 1;否则,dp[i] = 1。
3.定义边界条件:dp[0] = 1。
4.计算顺序:按照从小到大的顺序计算dp[i]的值,最终得到dp中的最大值即为最长上升子序列的长度。

def length1(nums):
    n=len(nums)
    dp=[1]*n
    for i in range(1,n):
        if nums[i]>nums[i-1]:
            dp[i]=dp[i-1]+1
    return max(dp)
print(length1([4,3,7,6,7,8,2,1,9])) # print 3

(2) 最长公共子序列

最长公共子序列问题是指:给定两个序列,找出它们的最长公共子序列。例如,对于序列X="ABCBDAB"和Y="BDCABA",它们的最长公共子序列为"BCBA"。

实现方法如下:

1.定义状态:设dp[i][j]表示序列X的前i个元素和序列Y的前j个元素的最长公共子序列的长度。
2.定义状态转移方程:对于序列X的第i个元素和序列Y的第j个元素,如果它们相等,则dp[i][j] = dp[i-1][j-1] + 1;否则,dp[i][j] = max(dp[i-1][j], dp[i][j-1])。
3.定义边界条件:dp[0][j] = dp[i][0] = 0。
4.计算顺序:按照从小到大的顺序计算dp[i][j]的值,最终得到dp[n][m]即为序列X和Y的最长公共子序列的长度。

def longestCommonSubsequence(X, Y):
    m, n = len(X), len(Y)
    dp = [[0] * (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]:
                dp[i][j] = dp[i-1][j-1] + 1
            else:
                dp[i][j] = max(dp[i-1][j], dp[i][j-1])
    return dp[m][n]

X = "ABCBDAB"
Y = "BDCABA"
print(longestCommonSubsequence(X, Y))  # 输出:4

 (3)背包问题

背包问题是指:有一个背包,容量为C,现在有n个物品,第i个物品的重量为w[i],价值为v[i],求在不超过背包容量的前提下,能够获得的最大价值。
实现方法如下:

1.定义状态:设dp[i][j]表示前i个物品放入容量为j的背包中所能获得的最大价值。
2.定义状态转移方程:对于第i个物品,如果将其放入背包中,则dp[i][j] = dp[i-1][j-w[i]] + v[i];否则,dp[i][j] = dp[i-1][j]。因为如果第i个物品的重量已经超过了背包的容量j,则无法将其放入背包中。
3.定义边界条件:dp[0][j] = dp[i][0] = 0。
4.计算顺序:按照从小到大的顺序计算dp[i][j]的值,最终得到dp[n][C]即为能够获得的最大价值。

def knapsack(C, w, v):
    n = len(w)
    dp = [[0] * (C+1) for _ in range(n+1)]
    for i in range(1, n+1):
        for j in range(1, C+1):
            if w[i-1] <= j:
                dp[i][j] = max(dp[i-1][j], dp[i-1][j-w[i-1]]+v[i-1])
            else:
                dp[i][j] = dp[i-1][j]
    return dp[n][C]

C = 10
w = [2, 3, 4, 5]
v = [3, 4, 5, 6]
print(knapsack(C, w, v))  # 输出:13

上述是,不可分解的背包问题哦,可分解的背包问题一般使用贪心算法即可(直接 价值/重量得到每个物品的优先级)。

当然,用一维数组也是可以的。

n=5
m=11
dp=[0]*(m+1)
w=[2,3,4,5,7]
t=[3,5,6,7,8]
for i in range(n):
    for j in range(m,w[i]-1,-1):
            dp[j]=max(dp[j],dp[j-w[i]]+t[i])
    print(dp)

完全背包问题就是要倒过来啦, 因为可以重复一个物品,那么我们就应该使用这一次装填过程的最优解。

n=5
m=11
dp=[0]*(m+1)
w=[2,3,4,5,7]
t=[3,5,6,7,8]
for i in range(n):
    for j in range(w[i],m+1):
            dp[j]=max(dp[j],dp[j-w[i]]+t[i])
    print(dp)

(4)所有点对的最短路径(Floyd算法)

算法的核心是一个二维数组dist,其中dist[i][j]表示节点i到节点j的最短路径长度。

算法的步骤如下:

1.初始化dist数组,也就是不经过其他节点的,两个节点之间的距离。

2.对于每个中间节点k,遍历所有节点对(i, j),更新dist[i][j]dist[i][k] + dist[k][j]dist[i][j]的较小值。(相当于每次使用一个节点来充当中间节点,看看加入了这个节点后,会不会使得某些点之间距离变短或者变得可达)

3.重复步骤2,直到所有节点对之间的最短路径都被计算出来。

import math

def floyd(graph):
    n = len(graph)
    dist = [[math.inf] * n for _ in range(n)]

    for i in range(n):
        for j in range(n):
            if i == j:
                dist[i][j] = 0
            else:
                dist[i][j] = graph[i][j]

    for k in range(n):
        for i in range(n):
            for j in range(n):
                dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j])

    return dist

# 示例图的邻接矩阵表示
graph = [
    [0, 5, math.inf, 10],
    [math.inf, 0, 3, math.inf],
    [math.inf, math.inf, 0, 1],
    [math.inf, math.inf, math.inf, 0]
]

result = floyd(graph)
for row in result:
    print(row)

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值