动态规划
选择的算法: 最长公共子序列
算法概述:
- 动态规划是一种在数学、管理科学、计算机科学、经济学和生物信息学中使用的,通过把原问题分解为相对简单的子问题的方式求解复杂问题的方法。
- 最长公共子序列,最长公共子序列是一个在一个序列集合中用来查找所有序列中最长子序列的问题。
步骤分析:
- 假设我们求题——对于两个子序列 Str1 和 Str2,找出它们最长的公共子序列。
- 定义一个二维数组 dp 用来存储最长公共子序列的长度如下面的表格所示,用 dp[i][j] 表示 Str1 的前 i 个字符与 Str2 的前 j 个字符最长公共子序列的长度。
0 | 1 | 2 | j | |
---|---|---|---|---|
0 | 0 | 0 | 0 | 0 |
1 | 0 | 0 | 1 | 1 |
2 | 0 | 0 | 1 | 1 |
i | 0 | 0 | 1 | dp[i][j] |
*上面这个表格中,Str1是行,Str2是列; 该表用二维数组的关系展示它们之间的信息表dp *
- 这里分为两种情况判断Str1[ i ] 与 Str2[ j ] 值是否相等:
当 Str1[ i ]== Str2[ j ] 时,那么就能在 Str1 的前 i -1 个字符与 Str2 的前 j -1 个字符最长公共子序列的基础上再加上 Str1[i] 这个值,最长公共子序列长度加 1,即 dp[i][j] = dp[i-1][j-1] + 1。
当 Str1[ i ] != Str2[ j ]时,此时最长公共子序列为 Str1 的前 [ i -1] 个字符和 Str2 的前 j 个字符最长公共子序列,或者 Str1 的前 i 个字符和 Str2 的前 j-1 个字符最长公共子序列,取它们的最大者,即 dp[i][j] = max{ dp[i-1][j], dp[i][j-1] }。 - 对于长度为 M 的序列 Str1 和长度为 N 的序列 Str2,dp[M][N] 就是序列 Str1 和序列 Str2 的最长公共子序列长度。
实验代码:
#include <iostream>
#include <vector>
using namespace std;
//longestCommonSubsequence() 用于分析最长子序列号并返回该序列的长度
//str1和str2分别是两个字符串
int longestCommonSubsequence(string str1, string str2) {
// 先计算两条字符串的长度
int M = str1.size();
int N = str2.size();
// 构建dp矩阵 默认初始值0
//因为对于 s1[1..M] 和 s2[1..N],它们的最大长子序列是 dp[M][N]。所以这里
// 所以当i或者j为零时 LCS的长度默认为0
vector< vector<int> > dp ( M+1 , vector<int> ( N+1 , 0 ) );
// 开始序列计算的dp[i][j]移动
// i、j都从1开始遍历 因为下面的操作中都会-1 相当于从0开始
for ( int i=1 ; i<M+1 ; i++ ) {
for ( int j=1 ; j<N+1 ; j++ ) {
// 如果str1和str2相同,就在它们的最长公共子序列的基础上再加上Str[i]这个值
// 如果不同,就只能在之前的两者中取最大值
dp[i][j] = (str1[i-1] == str2[j-1]) ? dp[i-1][j-1] + 1 : max( dp[i-1][j] , dp[i][j-1] );
}
}
// 返回最终dp表里右下角的值,也即最长子序列的长度
return dp[M][N];
}
int main() {
string str1 ,str2;
cout << "请输输入第一个字符串的值:" << endl;
cin >> str1;
cout << endl << "请输入第二个字符串的值:"<<endl;
cin >> str2;
cout << longestCommonSubsequence(str1,str2);
return 0;
}
运行截图 >>>
**时间复杂度:**因为遍历的过程是MN,所以该算法的空间、时间复杂度均为O(n2),经过优化后,空间复杂度可为O(n)。
实际用途:
该算法被广泛地应用在团队开发代码版本控制中,用于数据比较,GitHub和SVN的团队项目开发和个人项目的历史版本控制有用到该算法。