0012算法笔记——【动态规划】最长公共子串问题

   1、问题相关定义

          (1)字符串:一个字符串S是将n 个字符顺次排列形成的数组, n称为S的长度,表示为len(S) 。S的第i字符表示为S[ i ]。

      (2)子串:字符串S的子串S[ i:j ] ( i ≤ j)表示S串中从i到j这一段,也就是排列S[ i ] , S[ i + 1 ] , ⋯,S[ j ] 形成的字符串。

      (3)后缀:后缀是指从某个位置i开始到整个串末尾结束的一个特殊子串。字符串S的从i开头的后缀表示为Suffix( S,i) ,也就是Suffix( S, i) = S[ i:len ( S) ]。

      (4)公共后缀:字符串U 如果既是字符串S的后缀又是字符串T的后缀,则字符串U 是字符串S和T的一个公共后缀。

      (5)最长公共后缀:字符串S和T的最长公共后缀是指字符串S和T的所有公共后缀中长度最大的子串。

      (4)公共子串:字符串U 如果既是字符串S的子串又是字符串T的子串,则字符串U 是字符串S和T的一个公共子串。

      (5)最长公共子串:字符串S和T的最长公共子串是指字符串S和T的所有公共子串中长度最大的子串。 

      例:给定2 个长度分别为4, 4 的字符串“abab”,“baba”,它们的公共子串(Common Substring) 有“”,“a”,“b”,“ab”,“ba”,“aba”,“bab”。其中最长公共子串(LCS) 即为“aba”或“bab”。

          2、动态规划求解

      在使用动态规划求解2个长度分别为p, q的字符串S,T的最长公共子串问题前,先给出求它们任意前缀子串对S[1:i],T[1:j]的最长公共后缀的算法,其中:1 ≤ i ≤ p,1 ≤ j ≤ q。设LCSuffix(S[1:i],T[1:j])表示前缀子串对S[1: i] ,T[1:j] 的最长公共后缀,则该问题的递推关系式如下

 

       例如:字符串S“baba”, T“abab”, 使用上面递推关系式求得字符串S, T所有前缀子串对的最长公共后缀,如下表所示:

           字符串S, T所有前缀子串对应的最长公共后缀中长度最大的即为字符串S, T的最长公共子串,即:其中LCS( S,T) 表示字符串S, T的最长公共子串。对于字符串S“baba”, T“abab”,从表1中可以看出它们的最长公共后缀中长度最大为3,对应两个最长公共子串,即:"aba"和"bab"。 

[cpp]  view plain  copy
  1. // CPPPRO.cpp : 定义控制台应用程序的入口点。  
  2. //3m6 最长公共子串,动态规划实现  
  3. #include "stdafx.h"  
  4. #include <iostream>    
  5. #include <string>  
  6. using namespace std;   
  7.   
  8. string getLCSLength(string &s, string &t);  
  9.   
  10. int main()  
  11. {  
  12.     string s,t;  
  13.     cout<<"请输入字符串s:"<<endl;  
  14.     cin>>s;  
  15.     cout<<"请输入字符串t:"<<endl;  
  16.     cin>>t;  
  17.     cout<<"最长公共子串为:"<<endl;  
  18.     cout<<getLCSLength(s,t)<<endl;  
  19.     return 0;  
  20. }  
  21.   
  22. string getLCSLength(string &s, string &t)  
  23. {  
  24.     int p = s.length();  
  25.     int q = t.length();  
  26.   
  27.     string **num = new string *[p];    
  28.     for(int i=0;i<p;i++)      
  29.     {      
  30.         num[i] = new string[q];    
  31.     }     
  32.   
  33.     char char1 = '\0';  
  34.     char char2 = '\0';  
  35.   
  36.     int len = 0;  
  37.     string lcs = "" ;  
  38.   
  39.     for(int i=0; i<p; i++)  
  40.     {  
  41.         for (int j=0; j<t.length(); j++)  
  42.         {  
  43.             char1 = s.at(i);  
  44.             char2 = t.at(j);  
  45.             if(char1!=char2)  
  46.             {  
  47.                 num[i][j] = "" ;  
  48.             }  
  49.             else  
  50.             {  
  51.                 if (i==0||j==0)  
  52.                 {  
  53.                     num[i][j]=char1;  
  54.                 }  
  55.                 else  
  56.                 {  
  57.                     num[i][j]=num[i-1][j-1]+char1;  
  58.                 }  
  59.                 if(num[i][j].length()>len)  
  60.                 {  
  61.                     len = num[i][j].length() ;  
  62.                     lcs = num[i][j];  
  63.                 }  
  64.                 else if(num[i][j].length()==len)  
  65.                 {  
  66.                     lcs = lcs + "," + num[i][j];  
  67.                 }  
  68.             }  
  69.         }  
  70.     }  
  71.   
  72.     for(int i=0;i<p;i++)      
  73.     {      
  74.         delete[] num[i];   
  75.     }   
  76.     delete[] num;  
  77.   
  78.     return lcs;  
  79. }  

          程序运行结果如下:

           使用动态规划算法求2个长度分别为p, q的字符串S, T的最长公共子串需要构造一个二维表,该二维表含有pq个项,使用递推的方法求每项的时间负责度为O (1) ,所以求出所有项的时间复杂度为O (pq) 。在二维表中只需找到一个最大值项,其对应于原2个字符串的最长公共子串,因此使用动态规划算法的时间复杂度为O (pq) 。

          建立2个长度分别为p, q的字符串S, T的广义后缀数组的时间复杂度为O (p + q) ,求广义后缀数组中相邻子串的最长公共前缀的时间复杂度为<O(p + q) ,O(1)>,即需要预处理时间复杂度为O(p + q) ,每次查询最长公共前缀的时间复杂度为O(1) 。遍历所有最长公共前缀的时间复杂度为O(p+q) ,因此使用广义后缀数组解决最长公共子串的时间复杂度为O(p+q) 。对于字符串S, T,它的广义后缀数组在不降低性能同时需要使用2 (p + q) 空间。

      解决2个长度分别为p, q的字符串最长公共子串问题的动态规划算法,广义后缀树算法以及广义后缀数组算法在时间复杂度,空间复杂度以及编程实现上的比较分析总结如下表所示:

       其中动态规划算法以及广义后缀树算法的研究已经非常成熟,在计算机算法领域有着各种各样的应用,但在解决字符串最长公共子串问题上还有一定不足,即动态规划算法占用的时间复杂度以及空间复杂度都很高,而广义后缀树算法虽然降低了时间复杂度,但其空间复杂度还是较高,另外编程实现也较难。虽然广义后缀数组的理论研究还在发展中,但就目前的研究成果而言,在解决字符串最长公共子串问题上,根据上面给出的算法,它可以综合动态规划算法以及广义后缀树算法的优点,既保证了线性的时间复杂度,较低的空间复杂度又易于编程实现。改进的后缀数组算法及相关定义应用可查看博文《最长公共子串问题的后缀数组解法》。


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C语言中实现动态规划最长公共子串算法可以通过以下步骤来实现: 1. 定义一个二维数组dp[m+1][n+1],其中m和n分别为两个字符串的长度。 2. 初始化数组dp的第一行和第一列为0,即dp[i] = dp[j] = 0。 3. 使用两个循环遍历字符串的每个字符,i从1到m,j从1到n。 4. 如果两个字符相等,即str1[i-1] == str2[j-1],则dp[i][j] = dp[i-1][j-1] + 1。 5. 如果两个字符不相等,即str1[i-1] != str2[j-1],则dp[i][j] = 0。 6. 在循环过程中,记录最大的dp[i][j]值,以及对应的i和j的位置。 7. 循环结束后,最大的dp[i][j]就是最长公共子串的长度。 8. 可以根据最长公共子串的长度和i、j的位置,来获取最长公共子串的具体内容。 以下是一个示例代码: ```c #include <stdio.h> #include <string.h> void longestCommonSubstring(char* str1, char* str2) { int m = strlen(str1); int n = strlen(str2); int dp[m+1][n+1]; int maxLen = 0; int maxEndIndex = 0; // 初始化dp数组 for (int i = 0; i <= m; i++) { for (int j = 0; j <= n; j++) { dp[i][j] = 0; } } // 计算dp数组 for (int i = 1; i <= m; i++) { for (int j = 1; j <= n; j++) { if (str1[i-1] == str2[j-1]) { dp[i][j] = dp[i-1][j-1] + 1; if (dp[i][j] > maxLen) { maxLen = dp[i][j]; maxEndIndex = i - 1; } } else { dp[i][j] = 0; } } } // 获取最长公共子串 char longestSubstring[maxLen + 1]; strncpy(longestSubstring, &str1[maxEndIndex - maxLen + 1], maxLen); longestSubstring[maxLen] = '\0'; printf("最长公共子串: %s\n", longestSubstring); } int main() { char str1[] = "abcdefg"; char str2[] = "cdefgh"; longestCommonSubstring(str1, str2); return 0; } ``` 这段代码会输出最长公共子串:"cdef"。你可以根据需要修改字符串的内容进行测试。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值