• 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 }

      

  • 相关阅读:
    ES6——ECMAScript与Javascript
    sass 成熟、稳定、强大的专业级CSS扩展语言
    前端开发自动化工作流工具:JavaScript自动化构建工具grunt、gulp、webpack介绍
    CSS布局模型(流动模型、浮动模型、层模型)
    document.write
    HTML页面加载和解析流程
    script标签
    javascript 对象方法、类方法、原型方法
    RabbitMQ如何解决各种情况下丢数据的问题
    SpringBoot集成Quartz定时任务(持久化到数据库)
  • 原文地址:https://www.cnblogs.com/DLzhang/p/4056069.html
Copyright © 2020-2023  润新知