算法分析与设计作业--动态规划

本文深入探讨了动态规划在解决最长公共子序列和01背包问题中的应用。首先介绍了动态规划的基本思想,通过一个实例展示了如何利用动态规划求解最长公共子序列,接着详细解释了01背包问题的动态规划解决方案,并通过代码实现进行了演示。最后,对算法的时间复杂度进行了分析,并讨论了如何通过空间优化减少复杂度。

自己学校的作业,删了觉得太可惜了,不如发出来

动态规划

动态规划就是一个优化版的暴力法, 优化了遍历的顺序和数量, 使得将一个问题降低到一个时间可观的复杂度以内


最长公共子序列

给定两个长度分别为 NNNMMM 的字符串 AAABBB,求既是 AAA 的子序列又是 BBB 的子序列的字符串长度最长是多少, 并输出该字串。

输入格式

第一行包含两个整数 NNNMMM

第二行包含一个长度为 NNN 的字符串,表示字符串 AAA

第三行包含一个长度为 MMM 的字符串,表示字符串 BBB

字符串均由小写字母构成。

输出格式

第一行输出一个整数,表示最大长度。

第二行输出一个字符串,表示该字串

数据范围

1≤N,M≤10001≤N,M≤10001N,M1000

输入样例:
4 5
acbd
abedc
输出样例:
3
abd

C++代码

#include <iostream>
#include <algorithm>

using namespace std;

const int N = 1010;

int n, m;
char a[N], b[N], c[N];
int dp[N][N];

int main() {
    cin >> n >> m;                      // 读取两个序列的长度
    scanf("%s", a + 1);                 // 读取a的值
    scanf("%s", b + 1);                 // 读取b的值

    for (int i = 1; i <= n; i++) {      // dp求解
        for (int j = 1; j <= m; j++) {
            if (a[i] == b[j]) {
                dp[i][j] = max(dp[i - 1][j - 1] + 1, dp[i][j]);
            } else {
                dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
            }
        }
    }
    cout << "最长的子序列的长度: " << dp[n][m] << endl;

    int len = 0;    // 子序列的长度
    for (int i = n, j = m; i >= 1 && j >= 1;) {
        if (a[i] == b[j]){
            c[len ++] = a[i];
            i --, j --;
        } else {
            if (dp[i - 1][j] > dp[i][j - 1])i --;
            else j --;
        }
    }
    reverse(c, c + len);
    cout << "子序列为: " << c << endl;
    return 0;
}
运行截图
image-20220518200332822

算法分析

集合表示:f[i][j]f[i][j]f[i][j] 表示 aaa 的前 iii 个字母,和 bbb 的前 jjj 个字母的最长公共子序列长度

集合划分:以 a[i],b[j]a[i],b[j]a[i],b[j] 是否包含在子序列当中为依据,因此可以分成四类:

  1. a[i]a[i]a[i] 不在,b[j]b[j]b[j] 不在

    f[i][j]=f[i−1][j−1]f[i][j] = f[i - 1][j - 1]f[i][j]=f[i1][j1]

  2. a[i]a[i]a[i] 在, b[j]b[j]b[j] 不在

    f[i][j]=max(f[i][j],f[i][j−1])f[i][j] = max(f[i][j],f[i][j - 1])f[i][j]=max(f[i][j],f[i][j1])

  3. a[i]a[i]a[i] 不在, b[j]b[j]b[j]

    f[i][j]=max(f[i][j],f[i−1][j])f[i][j] = max(f[i][j],f[i - 1][j])f[i][j]=max(f[i][j],f[i1][j])

  4. a[i]a[i]a[i] 在, b[j]b[j]b[j]

    f[i][j]=max(f[i][j],f[i][j]+1)f[i][j] = max(f[i][j],f[i][j] + 1)f[i][j]=max(f[i][j],f[i][j]+1)

复杂度分析:

aaa 串和 bbb 串均会被遍历一次, 所以时间复杂度为 O(N∗M)=O(n2)O(N*M)=O(n^{2})O(NM)=O(n2) , NNNaaa 的长度, MMMbbb 的长度


01背包

NNN 件物品和一个容量是 VVV 的背包。每件物品只能使用一次。

iii 件物品的体积是 viv_{i}vi,价值是 wiw_{i}wi

求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。

输入格式

第一行两个整数,N,VN,VNV,用空格隔开,分别表示物品数量和背包容积。

接下来有 NNN 行,每行两个整数 vi,wiv_{i},w_{i}vi,wi,用空格隔开,分别表示第 iii 件物品的体积和价值。

输出格式

输出一个整数,表示最大价值。

数据范围

0<N,V≤10000<N,V≤10000<N,V1000
0<vi,wi≤10000<v_{i},w_{i}≤10000<vi,wi1000

输入样例
4 5
1 2
2 4
3 4
4 5
输出样例:
8

C++代码

#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;

const int N = 1010;

int n, m;       // n个物品, m的容量
int w[N], v[N]; // 第i个物品的价值和体积
int dp[N][N];   // 第i个物品, 体积为j的背包能装的最大的价值

int main() {
    cin >> n >> m;
    for (int i = 1; i <= n; i++) scanf("%d%d", &v[i], &w[i]);

    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            dp[i][j] = dp[i - 1][j];    // 不装
            if (j - v[i] >= 0)
                dp[i][j] = max(dp[i][j], dp[i - 1][j - v[i]] + w[i]);
        }
    }
    cout << dp[n][m] << endl;
    return 0;
}
运行截图
image-20220518201722876

算法分析

状态 f[i][j]f[i][j]f[i][j] 定义:前 iii 个物品,背包容量 jjj 下的最优解(最大价值):

两个大状态:

  1. 不将此物品放入背包中

    此时,当前的价值和上一个物品的价值一样

    f[i][j]=f[i−1][j]f[i][j] = f[i - 1][j]f[i][j]=f[i1][j]

  2. 将此背包放入背包中

    1. 可以将此物品放入背包中

      当前的价值等于 i - 1个物品的 j - v[i] 的价值加上当前的价值

      f[i][j]=f[i−1][j−v[i]]+w[i]f[i][j] = f[i - 1][j - v[i]] + w[i]f[i][j]=f[i1][jv[i]]+w[i]

    2. 不可以将此物品放入背包中

      直接跳过这个情况即可

复杂度分析

所有的背包容量物品都要遍历一次, 所以时间复杂度为O(N∗V)=O(n2)O(N*V)=O(n^{2})O(NV)=O(n2), NNN为物品的个数, VVV为背包的容量


体积优化(滚动数组)

#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;

const int N = 1010;

int n, m;       // n个物品, m的容量
int w[N], v[N]; // 第i个物品的价值和体积
int dp[N];   // 第i个物品, 体积为j的背包能装的最大的价值

int main() {
    cin >> n >> m;
    for (int i = 1; i <= n; i++) scanf("%d%d", &v[i], &w[i]);

    for (int i = 1; i <= n; i++) {
        for (int j = m; j >= v[i]; j--) {
            if (j - v[i] >= 0)
                dp[j] = max(dp[j], dp[j - v[i]] + w[i]);
        }
    }
    cout << dp[m] << endl;
    return 0;
}

优化思路:

可以看到:第 iii 个物品的所有的状态只依赖于第 i−1i - 1i1 个物品的状态, 不依赖于再之前的状态,所以可以将那些状态抛弃

此时, 由于将状态方程dp二维转换成一维的了, 所以也要对代码进行等价转换.

复杂度分析

空间复杂度由 O(N∗V)=O(n2)O(N * V)=O(n^{2})O(NV)=O(n2) 优化成了 O(V)=O(n)O(V) = O(n)O(V)=O(n)

可以看到优化前后的算法的时间复杂度和空间复杂度的变化还是很明显的:

  1. 二维版

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zsMOU4dK-1655861947924)(C:\Users\ghost\AppData\Roaming\Typora\typora-user-images\image-20220518203829043.png)]

  1. 优化版

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kSfYbdSy-1655861947924)(C:\Users\ghost\AppData\Roaming\Typora\typora-user-images\image-20220518203838682.png)]


额外补充

背包问题状态问题的总结

体积最多是 j

全部初始化成 000 , 算的时候, 要保证体积 v >= 0

体积恰好是 j

初始状态 f(0) = 0, 其余的全部初始化为正无穷, 同时保证体积 v >= 0

体积至少是j

初始状态 f(0) = 0, 其余的全部初始化为正无穷

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值