趣学算法系列-动态规划-实例分析
来源:互联网 发布:税务网络大学app 编辑:程序博客网 时间:2024/05/16 07:26
趣学算法系列-动态规划-实例分析
声明:本系列为趣学算法一书学习总结内容,在此推荐大家看这本算法书籍作为算法入门,
原作者博客链接,本书暂无免费电子版资源,请大家支持正版,更多实例分析请查看原书内容
第四章 动态规划
实际案例分析-最长公共子序列问题
问题情景
最长公共子序列问题是指:给定两个序列X={x1, x2, x3, …, xm}和
Y={y1, y2, y3, …,yn},找出 X 和 Y 的一个最长的公共子序列问题分析
例如: X=( A, B, C, B, A, D, B), Y=( B, C, B, A, A, C),那么最长公共子序列是 B, C, B, A。
如何找到最长公共子序列呢?
直接想法 暴力求解(一般问题的正确思路都是从最直接的想法慢慢优化的过程)
穷举 X 的所有子序列,检查每个子序列是否也是 Y 的子序列,记录找到的最长公共子序列。
X序列的子序列共有2的m次方个(不知如何计算的请自行百度),此方法的时间复杂度为指数阶动态规划的思路
(1)分析最优解的结构特征
假设已经知道 Zk={z1, z2, z3,…, zk}是 Xm={x1, x2, x3,…, xm}和 Yn={y1, y2, y3,…,
yn}的最长公共子序列。这个假设很重要,我们都是这样假设已经知道了最优解
那么可以分 3 种情况讨论
1: xm= yn= zk:那么 Zk−1={z1, z2, z3,…, zk−1}是 Xm−1 和 Yn−1 的最长公共子序列
2: xm≠yn, xm≠ zk:我们可以把 xm 去掉,那么 Zk 是 Xm−1 和 Yn 的最长公共子序列
3: xm≠yn, yn≠ zk:我们可以把 yn 去掉,那么 Zk 是 Xm 和 Yn−1的最长公共子序列
(2)建立最优值的递归式
设 c[i][j]表示 Xi 和 Yj 的最长公共子序列长度。
1: xm= yn= zk:那么 c[i][j]= c[i−1][j−1]+1;
2: xm≠yn:那么我们只需要求解 Xi 和 Yj−1 的最长公共子序列和 Xi−1 和 Yj 的最长公共子
序列,即 c[i][j]= max{c[i][j−1],c[i−1][j]}。
-最长公共子序列长度递归式
(3) 自底向上计算最优值,并记录最优值和最优策略
(4)构造最优解
求解过程只是得到了最长公共子序列长度,并不知道最长公共子序列是什么
例如,现在已经求出 c[m][n]=5,表示 Xm 和 Yn 的最长公共子序列长度是 5,那么这个 5
是怎么得到的呢?我们可以反向追踪 5 是从哪里来的。
xi= yj 时: c[i][j]= c[i−1][j−1]+1;
xi≠yj 时: c[i][j]= max{c[i][j−1], c[i−1][j]};
那么 c[i][j]的来源一共有 3 个: c[i][j]= c[i−1][j−1]+1, c[i][j]= c[i][j−1], c[i][j]= c[i−1][j]。
在第 3 步自底向上计算最优值时,用一个辅助数组 b [i][j]记录这 3 个来源:
c[i][j]= c[i−1][j−1]+1, b[i][j]=1;
c[i][j]= c[i][j−1], b[i][j]=2;
c[i][j]= c[i−1][j], b[i][j]=3
这样就可以根据 b[i][j]反向追踪最长公共子序列,当 b[i][j]=1 时,输出 xi;当 b [i][j]=2
时,追踪 c[i][j−1];当 b[i][j]=3 时,追踪 c[i−1][j],直到 i=0 或 j=0 停止算法设计
(1)确定合适的数据结构
采用二维数组 c[][]来记录最长公共子序列的长度, 二维数组 b[][]来记录最长公共子序列
的长度的来源
(2)初始化
输入两个字符串 s1、 s2,初始化 c[][]第一行第一列元素为 0(第一行第一列分别表示两个字符串为0个字符)
(3)循环阶段
• i = 1: s1[0]与 s2[j−1]比较, j=1, 2, 3,…, len2。
如果 s1[0]=s2[j−1], c[i][j] = c[i−1][j−1]+1;并记录最优策略来源 b[i][j]=1;
如果 s1[0] ≠s2[j−1],则公共子序列的长度为 c[i][j−1]和 c[i−1][j]中的最大值,如果 c[i][j−1]≥
c[i−1][j],则 c[i][j]=c[i][j−1],最优策略来源 b[i][j]=2;否则 c[i][j]= c[i−1][j],最优策略来源
b[i][j]=3。
• i = 2: s1[1]与 s2[j−1]比较, j=1, 2, 3,…, len2。
• 以此类推,直到 i > len1 时,算法结束,这时 c[len1][len2]就是最长公共序列的
长度。
(4)构造最优解
根据最优决策信息数组 b[][]递归构造最优解,即输出最长公共子序列。因为我们在求最
长公共子序列长度 c[i][j]的过程中,用 b[i][j]记录了 c[i][j]的来源,那么就可以根据 b[i][j]数
组倒推最优解。
(可以求出最优解的开头和当前位置的长度信息得出最优解具体信息,也可以直接倒序输出最后逆置)完美图解
以字符串 s1=“ ABCADAB”, s2=“ BACDBA”为例。
(1)初始化
len1=7, len2=6, 初始化 c[][]第一行、 第一列元素为 0,
( 2) i=1: s1[0]与 s2[j−1]比较, j=1, 2, 3,…, len2。
即“ A”与“ BACDBA”分别比较一次
如果字符相等, c[i][j]取左上角数值加 1,记录最优值
来源 b[i][j]=1。
如果字符不等,取左侧和上面数值中的最大值。如果左侧和上面数值相等,默认取左侧
数值。如果 c[i][j]的值来源于左侧 b[i][j]=2,来源于上面 b[i][j]=3。
• j=1: A≠B,左侧=上面,取左侧数值, c[1][1]= 0,最优策略来源 b[1][1]=2,如图 4-8
所示。
• j=2: A=A,则取左上角数值加 1, c[1][2]= c[0][1]+1=2,最优策略来源 b[1][2] =1,
如图 4-9 所示。后续略
(3) i=2: s1[1]与 s2[j−1]比较, j=1, 2, 3,…, len2。即“ B”与“ BACDBA”分别比较
一次。
如果字符相等, c[i][j]取左上角数值加 1,记录最优值来源 b[i][j]=1。
如果字符不等,取左侧和上面数值中的最大值。如果左侧和上面数值相等,默认取左侧
数值。如果 c[i][j]的值来源于左侧 b[i][j]=2,来源于上面 b[i][j]=3,如图 4-14 所示。
(5)构造最优解
伪代码详解
(1)最长公共子序列求解函数Void LCSL(){int I,j;for(I = 1;I <= len1;i++) //控制 s1 序列for(j = 1;j <= len2;j++) //控制 s2 序列{if(s1[i-1]==s2[j-1]) //字符下标从 0 开始{ //如果当前字符相同,则公共子序列的长度为该字符前的最长公共序列+1c[i][j] = c[i-1][j-1]+1;b[i][j] = 1;}else{if(c[i][j-1]>=c[i-1][j]) //两者找最大值,并记录最优策略来源{c[i][j] = c[i][j-1];b[i][j] = 2;}else{c[i][j] = c[i-1][j];b[i][j] = 3;}}}}
(2)最优解输出函数
Void print(int I, int j)//根据记录下来的信息构造最长公共子序列(从 b[i][j]开始递推){if(i==0 || j==0) return;if(b[i][j]==1){print(i-1,j-1);cout<<s1[i-1];}else if(b[i][j]==2)print(I,j-1);else print(i-1,j);}
- 实战演练
//program 4-1#include <iostream>#include<cstring>using namespace std;*ons tint N=1002;int c[N][N],b[N][N];char s1[N],s2[N];int len1,len2;void LCSL(){int I,j;for(I = 1;I <= len1;i++)//控制 s1 序列for(j = 1;j <= len2;j++)//控制 s2 序列{if(s1[i-1]==s2[j-1]){//如果当前字符相同,则公共子序列的长度为该字符前的最长公共序列+1c[i][j] = c[i-1][j-1]+1;b[i][j] = 1;}else{if(c[i][j-1]>=c[i-1][j]){c[i][j] = c[i][j-1];b[i][j] = 2;}else{c[i][j] = c[i-1][j];b[i][j] = 3;}}}}void print(int I, int j)//根据记录下来的信息构造最长公共子序列(从 b[i][j]开始递推){if(i==0 || j==0) return;if(b[i][j]==1){print(i-1,j-1);cout<<s1[i-1];}else if(b[i][j]==2)print(I,j-1);elseprint(i-1,j);}int main(){int I,j;cout << "输入字符串 s1: "<<endl;cin >> s1;cout << "输入字符串 s2: "<<endl;cin >> s2;len1 = strlen(s1);//计算两个字符串的长度len2 = strlen(s2);for(I = 0;I <= len1;i++){c[i][0]=0;//初始化第一列为 0}for(j = 0;j<= len2;j++){c[0][j]=0;//初始化第一行为 0}LCSL(); //求解最长公共子序列cout << "s1 和 s2 的最长公共子序列长度是: "<<c[len1][len2]<<endl;cout << "s1 和 s2 的最长公共子序列是: ";print(len1,len2); //递归构造最长公共子序列最优解return 0;}
- 算法时间复杂度分析
(1)时间复杂度:如果两个字符串的长度分别是 m、 n,那么算法时间复杂度为 Ο(m*n)。
(2)空间复杂度:空间复杂度主要为两个二维数组 c[][], b[][],占用的空间为 O(m*n)。
- 趣学算法系列-动态规划-实例分析
- 趣学算法系列-动态规划
- 算法系列1、动态规划
- 【LeetCode系列】动态规划算法
- 每天学一点算法-动态规划算法
- 算法分析之动态规划
- 研究生课程 算法分析-动态规划
- 研究生课程 算法分析-动态规划
- 菜鸟学算法之动态规划01
- 算法分析及实例解析(二)——贪心算法、动态规划
- 动态规划 ( DP ) 和 实例分析
- 【算法系列-5】动态规划-背包问题
- 算法系列—动态规划法
- C++ 算法系列之动态规划
- 动态规划算法实例三则
- 动态规划算法总结及实例简介
- 动态规划算法分析及实例——求解完全背包问题(java实现)
- 分治、动态规划、贪婪 之 算法分析
- Wireshark抓包练习
- 在网上搜索想要的答案的好技巧
- 【leetcode】Two Sum(unordered_map的使用)
- 运行交互式的容器
- HTTP 和 WebSocket 协议(下)
- 趣学算法系列-动态规划-实例分析
- 怎样使用Docker帮助
- UE4蓝图节点翻译---Get Parent Actor
- 后台模式启动容器
- 上传代码到Git远程仓库(Github)
- quick lua 简单快捷的纹理缓存和lua内存优化步骤总结。(续)
- docker容器相关命令
- Deeplab_v2 caffe 安装配置笔记
- 新手debug大全