最长公共子序列(LCS)问题

本文详细介绍了如何使用动态规划算法解决最长公共子序列(LCS)问题,并提供了完整的代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 下面的文字来自于网络,最后给出引用位置。
   
一、动态规划算法

   事实上,最长公共子序列问题也有最优子结构性质。
   记:
   Xi = 即X序列的前i个字符(1<= i <= m)(前缀)
   Yj = 即Y序列的前j个字符(1<= j <= m)(前缀)
   
   假定Z = 是LCS(X,Y)中的一个。
   ·若xm = yn(最后一个字符相同),则不难用反正法证明:该字符必是X与Y的任一最长公共子序列Z(设长度为k)的最后一个字符,即有zk = xm = yn,且显然有Zk-1∈LCS(Xm-1,Yn-1),即Z的前缀Zk-1是Xm-1与Yn-1的最长公共子序列。此时,问题化归成求Xm-1与Yn-1的LCS(LCS(X,Y))的长度等于LCS(Xm-1,Yn-1)的长度加1)。
   · 若xm≠yn,则亦不难用反证法证明:要么Z∈LCS(Xm-1, Y),要么Z∈LCS(X , Yn-1)。由于zk≠xm与zk≠yn其中至少有一个必成立,若zk≠xm则有Z∈LCS(Xm-1 , Y);类似的,若zk≠yn 则有Z∈LCS(X , Yn-1)。此时,问题化归成求Xm-1与Y的LCS及X与Yn-1的LCS。LCS(X , Y)的长度为:max{LCS(Xm-1 , Y)的长度, LCS(X , Yn-1)的长度}。
 
    由于上述当xm≠yn的情况中,求LCS(Xm-1 , Y)的长度与LCS(X , Yn-1)的长度,这两个问题不是相互独立的:两者都需要求LCS(Xm-1,Yn-1)的长度。另外两个序列的LCS中包含了两个序列的前缀的LCS,故问题具有最优子结构性质考虑用动态规划法。

   也就是说,解决这个LCS问题,你要求三个方面的东西:
   1> LCS(Xm-1,Yn-1)+1;
   2> LCS(Xm-1,Y),LCS(X,Yn-1);
   3> max{LCS(Xm-1,Y),LCS(X,Yn-1)};

二、动态规划算法解LCS问题

2.1 最长公共子序列的结构
 
   最长公共子序列的结构有如下表示:
   
   设序列X=和Y=的一个最长公共子序列Z=,则:
   1> 若 xm=yn,则 zk=xm=yn,且Zk-1是Xm-1和Yn-1的最长公共子序列; 
   2> 若 xm≠yn且 zk≠xm ,则 Z是 Xm-1和 Y的最长公共子序列;
   3> 若 xm≠yn且 zk≠yn ,则 Z是 X和 Yn-1的最长公共子序列;
   其中Xm-1=,Yn-1=,Zk-1=。

2.2 子问题的递归结构

   由最长公共子序列问题的最优子结构性质可知,要找出 Xm=和 Yn=的最长公共子序列,可按如下方式递归的进行:
   ·当xm = yn时,找出Xm-1和Yn-1的最长公共子序列,然后在其尾部加上xm或yn,即可得到X和Y的一个最长公共子序列;
   · 当xm≠yn时,必须解两个子问题,即找出Xm-1和Y的一个最长公共子序列及X和Yn-1的一个最长公共子序列。这两个公共子序列中较长者即为X和Y的一个最长公共子序列。

   由此递归结构容易看到最长公共子序列问题具有子问题重叠性质。例如,在计算X和Y的最长公共子序列时,可能要计算出X和Yn-1以及Xm-1和Y的最长公共子序列。而这两个子问题都包含一个公共子问题,即计算Xm-1和Yn-1的最长公共子序列。

   与矩阵乘积最优计算次序问题类似,我们来建立子问题的最优值的递归关系。用c[i,j]记录序列Xi和Yj的最长公共子序列的长度,其中Xi=,Yj=。当i = 0或j = 0时,空序列是Xi和Yj的最长公共子序列,故c[i,j] = 0。其他情况下,可得递归关系如下所示:

   
2.3 计算最优值
 
   直接利用上节节末的递归式,我们将很容易就能写出一个计算c[i,j]的递归算法,但其计算时间是随输入长度指数增长的。由于在所考虑的子问题空间中,总共只有O(m*n)个不同的子问题,因此,用动态规划算法自底向上地计算最优值能提高算法的效率。

   计算最长公共子序列长度的动态规划算法LCS_Length(X,Y),以序列X=和Y=作为输入。输出两个数组c[0..m ,0..n]和b[1..m ,1..n]。其中c[i,j]存储Xi与Yj的最长公共子序列的长度,b[i,j]记录指示c[i,j]的值是由哪一个子问题的解达到的,这在构造最长公共子序列时要用到。最后,X和Y的最长公共子序列的长度记录于c[m,n]中。

   伪代码如下所示。

  1. Procedure LCS_LENGTH(X,Y);LCS_LENGTH(X,Y);
  2. begin 
  3.    m:=length[X];
  4.    n:=length[Y];
  5. for i:=to m do c[i,0]:=0;
  6. for j:=to n do c[0,j]:=0;
  7. for i:=to m do 
  8.    for j:=to n do 
  9.       if x[i]=y[j] then 
  10.         begin
  11.            c[i,j]:= c[i-1,-1]+ 1; 
  12.            b[i,j]:="↖"; 
  13.         end 
  14.       else if c[-1,j]≥ c[i,-1then
  15.         begin
  16.            c[i,j]:c[i-1,j];
  17.            b[i,j]:"↑" ; 
  18.         end 
  19.       else 
  20.         begin 
  21.            c[i,j]:c[j-1];
  22.            b[i,j]:="←" 
  23.         end;
  24.    return(c,b);
  25. end
 
    由算法LCS_Length计算得到的数组b 可用于快速构造序列X=和Y=的最长公共子序列。首先从b[m,n]开始,沿着其中的箭头所指的方向在数组b中搜索。
   ·当 b[i,j]中遇到"↖"时(意味着 xi=yi是LCS的一个元素 ),表示 Xi与 Yj的最长公共子序列是由 子序列Xi-1与 Yj-1的最长公共子序列在尾部加上xi得到的子序列;
   ·当 b[i,j]中遇到"↑" 时,表示 Xi与 Yj的最长公共的最长公共子序列和Xi-1与 Yj的最长公共子序列 相同;
   ·当b[i,j]中遇到"←" 时,表示Xi与Yj的最长公共子序列和Xi与Yj-1的最长公共子序列相同;

   这种方法是按照反序来查找LCS的每一个元素的。由于每个数组单元的计算花费O(1)时间,算法LCS_Length耗时O(mn)。

2.4 构造最长公共子序列

   下面的算法LCS(b,X,i,j)实现根据b的内容打印出Xi与Yi的最长公共子序列。通过算法的调用LCS(b,X,length[X],length[Y]),便可打印出序列X和Y的最长公共子序列。

   伪代码如下所示。
  1. Procedure LCS(b,X,i,j);
  2. begin
  3.   if i=or j=then return;
  4.   if b[i,j]=”↖” then
  5.     begin
  6.       LCS(b,X,i-1,j-1);
  7.       print(x[i]); {打印x[i]}
  8.     end
  9.   else if b[i,j]=”↑” then LCS(b,X,i-1,j) 
  10.                       else LCS(b,X,i,j-1);
  11. end;

   在LCS算法中,每一次递归调用使i或j减1,因此算法的时间复杂度为O(m+n)。

   
   
   我来说明下此图(参考算法导论)。在序列X={A,B,C,B,D,A,B}和 Y={B,D,C,A,B,A}上,由LCS_LENGTH计算出的表c和b。第i行和第j列中的方块包含了c[i,j]的值以及指向b[i,j]的箭头。在c[7,6]的项4,表的右下角为X和Y的一个LCS 的长度。对于i,j>0,项c[i,j]仅依赖于是否有xi=yi,及项c[i-1,j]和c[i,j-1]的值,这几个项都在c[i,j]之前计算。为了重构一个LCS的元素,从右下角开始跟踪b[i,j]的箭头即可,这条路径标示为阴影,这条路径上的每一个“↖”对应于一个使xi=yi为一个LCS的成员的项(高亮标示)。

所以根据上述图所示的结果,程序将最终输出:“B C B A”。

   

   让我对动态规划求LCS问题,有了更加深刻的理解。感谢作者!  
   
  

   根据以上思路,写出代码如下所示。


  1. //只能打印一个最长公共子序列
  2. #include <iostream>
  3. using namespace std;
  4.  
  5.  const int X = 100, Y = 100;        //串的最大长度
  6.  char result[X+1];                    //用于保存结果
  7.  int count=0;                        //用于保存公共最长公共子串的个数

  8.  /*功能:计算最优值
  9.   *参数:
  10.   *        x:字符串x
  11.   *        y:字符串y
  12.   *        b:标志数组
  13.   *        xlen:字符串x的长度
  14.   *        ylen:字符串y的长度
  15.   *返回值:最长公共子序列的长度
  16.   *
  17.   */
  18.  int Lcs_Length(string x, string y, int b[][Y+1],int xlen,int ylen) 
  19.  {
  20.      int i = 0;
  21.      int j = 0;

  22.      int c[X+1][Y+1];
  23.      for (= 0; i<=xlen; i++) 
  24.      {
  25.          c[i][0]=0;
  26.      } 
  27.      for (= 0; i <= ylen; i++ ) 
  28.      {
  29.          c[0][i]=0;
  30.      }
  31.      for (= 1; i <= xlen; i++) 
  32.      {
  33.          
  34.          for (= 1; j <= ylen; j++) 
  35.          {
  36.              if (x[- 1] == y[- 1])
  37.              {
  38.                  c[i][j] = c[i-1][j-1]+1; 
  39.                  b[i][j] = 1;
  40.              }
  41.              else 
  42.                  if (c[i-1][j] > c[i][j-1]) 
  43.                  {
  44.                      c[i][j] = c[i-1][j]; 
  45.                      b[i][j] = 2;
  46.                  }
  47.                  else 
  48.                      if(c[i-1][j] <= c[i][j-1])
  49.                      {
  50.                          c[i][j] = c[i][j-1]; 
  51.                          b[i][j] = 3;
  52.                      }
  53.                      
  54.          }
  55.      }
  56.      cout << "计算最优值效果图如下所示:" << endl;
  57.      for(= 1; i <= xlen; i++)
  58.      {
  59.          for(= 1; j < ylen; j++)
  60.          {
  61.              cout << c[i][j] << " ";
  62.          }
  63.          cout << endl;
  64.      }
  65.      return c[xlen][ylen];
  66.  }
  67.  
  68. void Display_Lcs(int i, int j, string x, int b[][Y+1],int current_Len)
  69.  {
  70.      if (==|| j==0) 
  71.      {
  72.          return;
  73.      }
  74.      if(b[i][j]== 1) 
  75.      { 
  76.          current_Len--;
  77.          result[current_Len]=x[i- 1];
  78.          Display_Lcs(i-1, j-1, x, b, current_Len);
  79.      }
  80.      else 
  81.      {
  82.          if(b[i][j] == 2)
  83.          {
  84.              Display_Lcs(i-1, j, x, b, current_Len);
  85.          }
  86.          else 
  87.          {
  88.              if(b[i][j]==3)
  89.              {
  90.                  Display_Lcs(i, j-1, x, b, current_Len);
  91.              }
  92.              else
  93.              {
  94.                  Display_Lcs(i-1,j,x,b, current_Len);
  95.              }
  96.          }
  97.      }
  98.  }
  99.  
  100.  int main(int argc, char* argv[])
  101.  {
  102.      string x = "ABCBDAB";
  103.      string y = "BDCABA";
  104.      int xlen = x.length();
  105.      int ylen = y.length();

  106.      int b[+ 1][+ 1];

  107.      int lcs_max_len = Lcs_Length( x, y, b, xlen,ylen );
  108.      cout << lcs_max_len << endl;

  109.      Display_Lcs( xlen, ylen, x, b, lcs_max_len );
  110.      
  111.      //打印结果如下所示
  112.     for(int i = 0; i < lcs_max_len; i++)
  113.     {
  114.         cout << result[i];
  115.     }
  116.     cout << endl;
  117.      return 0;
  118.  }

     运行结果如下所示。



   由于有时并不是只有一个最长公共子序列,所以,对上面的代码进行改进,增加一个数组保存结果等....代码如下所示。

  1. //求取所有的最长公共子序列
  2. #include <iostream>
  3. using namespace std;
  4.  
  5.  const int X = 100, Y = 100;        //串的最大长度
  6.  char result[X+1];                    //用于保存结果
  7.  int count=0;                        //用于保存公共最长公共子串的个数

  8.  /*功能:计算最优值
  9.   *参数:
  10.   *        x:字符串x
  11.   *        y:字符串y
  12.   *        b:标志数组
  13.   *        xlen:字符串x的长度
  14.   *        ylen:字符串y的长度
  15.   *返回值:最长公共子序列的长度
  16.   *
  17.   */
  18.  int Lcs_Length(string x, string y, int b[][Y+1],int xlen,int ylen) 
  19.  {
  20.      int i = 0;
  21.      int j = 0;

  22.      int c[X+1][Y+1];
  23.      for (= 0; i<=xlen; i++) 
  24.      {
  25.          c[i][0]=0;
  26.      } 
  27.      for (= 0; i <= ylen; i++ ) 
  28.      {
  29.          c[0][i]=0;
  30.      }
  31.      for (= 1; i <= xlen; i++) 
  32.      {
  33.          
  34.          for (= 1; j <= ylen; j++) 
  35.          {
  36.              if (x[- 1] == y[- 1])
  37.              {
  38.                  c[i][j] = c[i-1][j-1]+1; 
  39.                  b[i][j] = 1;
  40.              }
  41.              else 
  42.                  if (c[i-1][j] > c[i][j-1]) 
  43.                  {
  44.                      c[i][j] = c[i-1][j]; 
  45.                      b[i][j] = 2;
  46.                  }
  47.                  else 
  48.                      if(c[i-1][j] < c[i][j-1])
  49.                      {
  50.                          c[i][j] = c[i][j-1]; 
  51.                          b[i][j] = 3;
  52.                      }
  53.                      else
  54.                      {
  55.                          c[i][j] = c[i][j-1]; //或者c[i][j]=c[i-1][j];
  56.                          b[i][j] = 4;
  57.                      }
  58.          }
  59.      }
  60.      cout << "计算最优值效果图如下所示:" << endl;
  61.      for(= 1; i <= xlen; i++)
  62.      {
  63.          for(= 1; j < ylen; j++)
  64.          {
  65.              cout << c[i][j] << " ";
  66.          }
  67.          cout << endl;
  68.      }
  69.      return c[xlen][ylen];
  70.  }
  71.  
  72.  /*功能:计算最长公共子序列
  73.   *参数:
  74.   *        xlen:字符串x的长度
  75.   *        ylen:字符串y的长度
  76.   *        x    :字符串x
  77.   *        b:标志数组
  78.   *        current_len:当前长度
  79.   *        lcs_max_len:最长公共子序列长度
  80.   *
  81.   */
  82.  void Display_Lcs(int i, int j, string x, int b[][Y+1],int current_len,int lcs_max_len) 
  83.  {
  84.      if (==|| j==0) 
  85.      {
  86.          for(int s=0; s < lcs_max_len; s++)
  87.          {
  88.              cout << result[s];
  89.          }
  90.          cout<<endl;
  91.          count++;
  92.          return;
  93.      }
  94.      if(b[i][j]== 1) 
  95.      { 
  96.          current_len--;
  97.          result[current_len]=x[i- 1];
  98.          Display_Lcs(i-1, j-1, x, b,current_len,lcs_max_len);
  99.      }
  100.      else 
  101.      {
  102.          if(b[i][j] == 2)
  103.          {
  104.              Display_Lcs(i-1, j, x, b,current_len,lcs_max_len);
  105.          }
  106.          else 
  107.          {
  108.              if(b[i][j]==3)
  109.              {
  110.                  Display_Lcs(i, j-1, x, b,current_len,lcs_max_len);
  111.              }
  112.              else
  113.              {
  114.                  Display_Lcs(i,j-1,x,b,current_len,lcs_max_len);
  115.                  Display_Lcs(i-1,j,x,b,current_len,lcs_max_len);
  116.              }
  117.          }
  118.      }
  119.  }
  120.  
  121.  int main(int argc, char* argv[])
  122.  {
  123.      string x = "ABCBDAB";
  124.      string y = "BDCABA";
  125.      int xlen = x.length();
  126.      int ylen = y.length();

  127.      int b[+ 1][+ 1];

  128.      int lcs_max_len = Lcs_Length( x, y, b, xlen,ylen );
  129.      cout << lcs_max_len << endl;

  130.      Display_Lcs( xlen, ylen, x, b, lcs_max_len, lcs_max_len );
  131.      cout << "共有:" << count << "种";

  132.   
  133.      return 0;
  134.  }

   运行结果如下图所示。



   如有问题,欢迎指出,谢谢!!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值