引言: 动态规划是我们求两个字符串最长公共子序列的重要算法, 求编辑距离时同样也要用到。
a b c d c b a
0 0 0 0 0 0 0 0
a 0 1 1 1 1 1 1 1
b 0 1 2 2 2 2 2 2
d 0 1 2 2 3 3 3 3
a 0 1 2 2 3 3 3 4
c 0 1 2 3 3 4 4 4
求动态规划求最长公共子序列的原则是 当前 n-1 个序列的最长公共子是C时
假设 abcd
bca 两个串最长公共子序列是c = 2 ,
那么 当两个串同时 加一个a
1.abcda
2. bcaa 显然 这时 最长公共子序列的长度是c + 1; 这个好理解,
那么当
1,2两串后面加的字符不一样时该怎么办,例如
1变成 abcda
2变成 bcab 这时该如何求公共子序列?
这时应该求 c[i-1][j] 和c[i][j-1],
即求 1. abcda 和 bca 2. abcd 和bcab 算出1,2式的值,取最大值作为当前 abcda 和bcab 的公共子序列长。
很好理解, 就是把 串A长度减1 求一次序列, 再把串B长度减1 再求一次序列长。
那么好了, 现在已经有算法了 ,我们用代码实现以下。
1.我们先用递归的方式先实现
1 #include <iostream> 2 #include <string> 3 using namespace std; 4 5 /* 6 * i和j表示的是字符数量 7 * 所以参数的含义是 A的前i个字符 8 * B的前j个字符 9 */ 10 11 //本算法用于求最长公共字序列 12 //递归版本随着串长的增加 复杂度成指数爆炸式增长 13 //最好优化为 循环版本 14 int LongestCommonSubsequence(const string &A, 15 const string &B, 16 int i, 17 int j) 18 { 19 if(i == 0 || j == 0) 20 return 0; 21 22 if(A[i-1] == B[j-1]) //字符串下标操作获取字符(charl类型) 23 { 24 return LongestCommonSubsequence(A, B, i-1, j-1) + 1; 25 }else 26 { 27 int t1 = LongestCommonSubsequence(A, B, i-1, j); 28 int t2 = LongestCommonSubsequence(A, B, i, j-1); 29 return t1 > t2 ? t1 : t2; 30 } 31 } 32 33 34 int main(int argc, const char *argv[]) 35 { 36 string s1 = argv[1]; 37 string s2 = argv[2]; 38 cout << LongestCommonSubsequence(s1, s2, s1.size(), s2.size()) 39 << endl; 40 return 0; 41 }
递归版本的逻辑在代码上十分的清楚, 但是当比较的两串 长度十分大时, 递归的效率将变得十分低下, 因为递归版本存在着大量的重复计算。
我们来说明一下, 哪里存在着重复计算, 当我们用递归版本求 A串 长6 , B串长5时, 要把 A长度减1, B长度减一,求它们前面子串的序列, 那么每次递归都要把前面的子串都求一边, 那么计算就十分冗余。
我们比较循环和递归两个版本时, 发现效率差距是惊人的。
2. 优化递归版本, 很简单,设置一个数组,保存计算结果, 每次都判断是否已经计算过了
memo[100][100] 用于保存结果, 初始全部设置为-1; 不为-1时,即表示已经计算过
1 #include <iostream> 2 #include <string> 3 #include <string.h> 4 using namespace std; 5 6 /* 7 * i和j表示的是字符数量 8 * 所以参数的含义是 A的前i个字符 9 * B的前j个字符 10 */ 11 12 int memo[100][100]; //保存计算结果, 防止重复计算 13 14 int LongestCommonSubsequence(const string &A, 15 const string &B, 16 int i, 17 int j) 18 { 19 if(i == 0 || j == 0) 20 return 0; 21 if(memo[i][j] != -1) //如果计算过,直接返回备忘录的内容 22 return memo[i][j]; 23 24 if(A[i-1] == B[j-1]) 25 { 26 memo[i][j] = LongestCommonSubsequence(A, B, i-1, j-1) + 1; 27 return memo[i][j]; 28 }else 29 { 30 int t1 = LongestCommonSubsequence(A, B, i-1, j); 31 int t2 = LongestCommonSubsequence(A, B, i, j-1); 32 memo[i][j] = t1 > t2 ? t1 : t2; 33 return memo[i][j]; 34 } 35 } 36 37 38 int main(int argc, const char *argv[]) 39 { 40 string s1 = argv[1]; 41 string s2 = argv[2]; 42 memset(memo, -1, sizeof memo); 43 cout << LongestCommonSubsequence(s1, s2, s1.size(), s2.size()) 44 << endl; 45 return 0; 46 }
3.循环版本也十分简单, 利用我们手算的逻辑既可以实现。
1 #include <iostream> 2 #include <string> 3 #include <string.h> 4 5 #include <queue> 6 #include <algorithm> 7 8 using namespace std; 9 10 inline int MAX(int a, int b) { 11 return a > b ? a : b; 12 } 13 14 int memo[100][100]; //暂存结果的集合 15 16 int LongestCommonSequence(const string &a, const string &b, int i, int j) { 17 if (i == 0 || j == 0) { 18 return 0; 19 } 20 21 if (memo[i][j] == -1) { //缓存中没有结果,需要自己计算 22 23 if (a[i - 1] == b[j - 1]) { 24 return LongestCommonSequence(a, b, i - 1, j - 1) + 1; 25 } 26 27 return MAX(LongestCommonSequence(a, b, i, j - 1), 28 LongestCommonSequence(a, b, i - 1, j)); 29 } else { //直接返回结果 30 return memo[i][j]; 31 } 32 } 33 34 int LCS(const string &a, const string &b) { 35 int memo[100][100]; //暂存结果 36 37 // memo[i][0] = 0 38 for (int i = 0; i <= a.size(); ++i) { 39 memo[i][0] = 0; 40 } 41 for (int j = 0; j <= b.size(); ++j) { 42 memo[0][j] = 0; 43 } 44 45 for (int i = 1; i <= a.size(); ++i) { 46 for (int j = 1; j <= b.size(); ++j) { 47 if (a[i - 1] == b[j - 1]) { 48 memo[i][j] = memo[i - 1][j - 1] + 1; 49 } else { 50 memo[i][j] = MAX(memo[i - 1][j], memo[i][j - 1]); 51 } 52 } 53 } 54 55 return memo[a.size()][b.size()]; 56 57 } 58 int main(int argc, char **argv) { 59 // memset(memo, 0xff, 100 * 100 * sizeof(int)); 60 // 61 // string a = "abcbdab"; 62 // string b = "bdcaba"; 63 // int ret = LongestCommonSequence(a, b, a.size(), b.size()); 64 // 65 // cout << ret << endl; 66 // 67 // ret = LCS(a, b); 68 // cout << ret << endl; 69 70 71 // priority_queue<int, vector<int>, less<int> > q; 72 // 73 // q.push(13); 74 // q.push(23); 75 // 76 // cout << q.top() << endl; 77 78 79 80 //建立一个大根堆 81 priority_queue<Student, vector<Student>, compare > q; 82 83 q.push(Student(1, "hello")); 84 q.push(Student(12, "test")); 85 q.push(Student(9, "test")); 86 87 Student tmp = q.top(); 88 89 cout << tmp._id << endl; 90 91 //其他方式 92 //priority_queue<Student, vector<Student>, less<Student> > q; 93 //这种需要重载Student的<操作符 94 95 }