找回密码
 立即注册

QQ登录

只需一步,快速开始

扫一扫,访问微社区

查看: 1205|回复: 2

[分享] 【动态规划】最长公共子串问题

[复制链接]

已领礼包: 40个

财富等级: 招财进宝

发表于 2014-11-29 09:55:15 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×
本帖最后由 newer 于 2014-11-29 09:59 编辑


  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] 的最长公共后缀,则该问题的递推关系式如下
0.jpg

       例如:字符串S“baba”, T“abab”, 使用上面递推关系式求得字符串S, T所有前缀子串对的最长公共后缀,如下表所示:
1.jpg
           字符串S, T所有前缀子串对应的最长公共后缀中长度最大的即为字符串S, T的最长公共子串,即: 2.jpg 其中LCS( S,T) 表示字符串S, T的最长公共子串。对于字符串S“baba”, T“abab”,从表1中可以看出它们的最长公共后缀中长度最大为3,对应两个最长公共子串,即:"aba"和"bab"。


  1. // CPPPRO.cpp : 定义控制台应用程序的入口点。
  2. //3m6 最长公共子串,动态规划实现
  3. #include "stdafx.h"
  4. #include <iostream>  
  5. #include <string>
  6. using namespace std;

  7. string getLCSLength(string &s, string &t);

  8. int main()
  9. {
  10.         string s,t;
  11.         cout<<"请输入字符串s:"<<endl;
  12.         cin>>s;
  13.         cout<<"请输入字符串t:"<<endl;
  14.         cin>>t;
  15.         cout<<"最长公共子串为:"<<endl;
  16.         cout<<getLCSLength(s,t)<<endl;
  17.         return 0;
  18. }

  19. string getLCSLength(string &s, string &t)
  20. {
  21.         int p = s.length();
  22.         int q = t.length();

  23.         string **num = new string *[p];  
  24.     for(int i=0;i<p;i++)   
  25.     {   
  26.         num = new string[q];  
  27.     }   

  28.         char char1 = '\0';
  29.         char char2 = '\0';

  30.         int len = 0;
  31.         string lcs = "" ;

  32.         for(int i=0; i<p; i++)
  33.         {
  34.                 for (int j=0; j<t.length(); j++)
  35.                 {
  36.                         char1 = s.at(i);
  37.                         char2 = t.at(j);
  38.                         if(char1!=char2)
  39.                         {
  40.                                 num[j] = "" ;
  41.                         }
  42.                         else
  43.                         {
  44.                                 if (i==0||j==0)
  45.                                 {
  46.                                         num[j]=char1;
  47.                                 }
  48.                                 else
  49.                                 {
  50.                                         num[j]=num[i-1][j-1]+char1;
  51.                                 }
  52.                                 if(num[j].length()>len)
  53.                                 {
  54.                                         len = num[j].length() ;
  55.                                         lcs = num[j];
  56.                                 }
  57.                                 else if(num[j].length()==len)
  58.                                 {
  59.                                         lcs = lcs + "," + num[j];
  60.                                 }
  61.                         }
  62.                 }
  63.         }

  64.         for(int i=0;i<p;i++)   
  65.     {   
  66.         delete[] num;
  67.     }
  68.         delete[] num;

  69.         return lcs;
  70. }

程序运行结果如下:


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


论坛插件加载方法
发帖求助前要善用【论坛搜索】功能,那里可能会有你要找的答案;
如果你在论坛求助问题,并且已经从坛友或者管理的回复中解决了问题,请把帖子标题加上【已解决】;
如何回报帮助你解决问题的坛友,一个好办法就是给对方加【D豆】,加分不会扣除自己的积分,做一个热心并受欢迎的人!

已领礼包: 40个

财富等级: 招财进宝

 楼主| 发表于 2014-11-29 10:01:09 | 显示全部楼层
  1. /*
  2. 35、编程实现:找出两个字符串中最大公共子字符串,如"abccade","dgcadde"的最大子串为
  3. "cad"

  4. 不同于56的最长公共子串
  5. DP题算法导论上有:
  6. c[i,j]=0                      i=0||j=0
  7.           =c[i-1,j-1]+1           a==a[j]
  8.           =max(c[i-1,j],c[i,j-1]) a!=a[j]       

  9. 本题 就是循环枚举
  10. */

  11. #include<iostream>
  12. #include<stdio.h>
  13. #include<cmath>
  14. #include<algorithm>
  15. using namespace std;

  16. int GetCommon(char s1[], char s2[])
  17. {
  18.         int len1=strlen(s1);
  19.         int len2=strlen(s2);
  20.         int r,maxlen=0;
  21.        
  22.         for(int i=0;i<len1;i++)
  23.         {
  24.                 for(int j=0;j<len2;j++)
  25.                 {
  26.                         if(s1==s2[j])
  27.                         {
  28.                                 int as=i,bs=j,count=1;
  29.                                 while(as+1<len1&&bs+1<len2 &&s1[++as]==s2[++bs])
  30.                                         count++;
  31.                                 if(count>maxlen)
  32.                                 {
  33.                                         maxlen = count;
  34.                                         r=i;
  35.                                 }
  36.                         }
  37.                 }
  38.         }
  39.         for(int i=r;i<r+maxlen;i++)
  40.                 printf("%c",s1);
  41.         printf("\n");
  42.         return maxlen;
  43. }
  44. int main()
  45. {
  46.         char a[]={"abccade"};
  47.         char b[]={"dgcadde"};
  48.         printf("%s和%s的最长公共子串是:\n",a,b);
  49.         printf("长度为:%d\n",GetCommon(a,b));
  50.        
  51.         return 0;
  52. }
论坛插件加载方法
发帖求助前要善用【论坛搜索】功能,那里可能会有你要找的答案;
如果你在论坛求助问题,并且已经从坛友或者管理的回复中解决了问题,请把帖子标题加上【已解决】;
如何回报帮助你解决问题的坛友,一个好办法就是给对方加【D豆】,加分不会扣除自己的积分,做一个热心并受欢迎的人!
回复 支持 反对

使用道具 举报

已领礼包: 40个

财富等级: 招财进宝

 楼主| 发表于 2014-11-29 10:14:44 | 显示全部楼层
  1. //获取两个字符串公共子串
  2. int GetMaxCommonSubStr(string &strFirst, string &strSecond)
  3. {
  4.         if((strFirst=="") || (strSecond==""))
  5.         {
  6.                 return 0;
  7.         }
  8.         int i, j;
  9.         int iLenFirst = strFirst.length();
  10.         int iLenSecond = strSecond.length();
  11.         int iMaxCmnLne = 0; //最大公共子串长度
  12.         string strLCS = "";      //存储最大公共子串
  13.         char chFirst, chSecond;
  14.         string **num = new string *[iLenFirst];

  15.         assert(num!=NULL);

  16.         for(i=0; i<iLenFirst; i++)
  17.         {
  18.                 num = new string[iLenSecond];
  19.         }

  20.         chFirst = '\0';
  21.         chSecond = '\0';

  22.         for(i=0; i<iLenFirst; i++)
  23.         {
  24.                 chFirst = strFirst.at(i);
  25.                 for(j=0; j<iLenSecond; j++)
  26.                 {
  27.                         chSecond = strSecond.at(j);
  28.                         if(chFirst != chSecond)
  29.                         {
  30.                                 num[j] ="";
  31.                         }
  32.                         else
  33.                         {
  34.                                 if(0==i || 0==j)
  35.                                 {
  36.                                         num[j] = chFirst;
  37.                                 }
  38.                                 else
  39.                                 {
  40.                                         num[j] = num[i-1][j-1] + chFirst;
  41.                                 }

  42.                                 if(num[j].length()>iMaxCmnLne) //如果当前获得的最大公共子串比以前的最大的大,则更新最大公共子串长度
  43.                                 {
  44.                                         strLCS = "";  //有新的最大公共子串,以前的作废
  45.                                         iMaxCmnLne = num[j].length();
  46.                                         strLCS = num[j];
  47.                                 }
  48.                                 else if(num[j].length()==iMaxCmnLne) //获取到目前为止多个当前最大公共子串,用,分得开
  49.                                 {
  50.                                         strLCS += ',' + num[j];
  51.                                 }


  52.                         }

  53.                 }
  54.         }

  55.         //内存回收
  56.         for(i=0; i<iLenFirst; i++)
  57.         {
  58.                 delete [] num;
  59.         }
  60.         delete [] num;

  61.         return iMaxCmnLne;
  62. }


  63. int _tmain(int argc, _TCHAR* argv[])
  64. {

  65.         int iResultLen;
  66.         string strLine, InputStr1, InputStr2;
  67.         getline(cin, strLine);

  68.         istringstream stream(strLine);
  69.         stream>>InputStr1;
  70.         stream>>InputStr2;

  71.         iResultLen = GetMaxCommonSubStr(InputStr1, InputStr2);       
  72.         cout<<iResultLen;
  73.         Sleep(5000);

  74.         return 0;
  75. }
论坛插件加载方法
发帖求助前要善用【论坛搜索】功能,那里可能会有你要找的答案;
如果你在论坛求助问题,并且已经从坛友或者管理的回复中解决了问题,请把帖子标题加上【已解决】;
如何回报帮助你解决问题的坛友,一个好办法就是给对方加【D豆】,加分不会扣除自己的积分,做一个热心并受欢迎的人!
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|申请友链|Archiver|手机版|小黑屋|辽公网安备|晓东CAD家园 ( 辽ICP备15016793号 )

GMT+8, 2024-6-9 08:11 , Processed in 0.340872 second(s), 35 queries , Gzip On.

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表