C++学习之路: 动态规划之求最长公共子序列

引言: 动态规划是我们求两个字符串最长公共子序列的重要算法,  求编辑距离时同样也要用到。

         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 }

  

原文地址:https://www.cnblogs.com/DLzhang/p/4056069.html