C语言求解最长公共子字符串问题及相关的算法分析

   2016-06-20 0
核心提示:最长公共子字符串问题即是求一个字符串在另一个字符串中出现的连续最多字符,这里我们来看一下面试中经常出现的C语言求解最长公共子字符串问题及相关的算法分析

题目:如果字符串一的所有字符按其在字符串中的顺序出现在另外一个字符串二中,则字符串一称之为字符串二的子串。注意,并不要求子串(字符串一)的字符必须连续出现在字符串二中。请编写一个函数,输入两个字符串,求它们的最长公共子序列,并打印出最长公共子序列。
例如:输入两个字符串BDCABA和ABCBDAB,字符串BCBA和BDAB都是是它们的最长公共子序列,则输出它们的长度4,并打印任意一个子序列。
分析:求最长公共子序列(Longest Common Subsequence, LCS)是一道非常经典的动态规划题,因此一些重视算法的公司像MicroStrategy都把它当作面试题。

完整介绍动态规划将需要很长的篇幅,因此我不打算在此全面讨论动态规划相关的概念,只集中对LCS直接相关内容作讨论。如果对动态规划不是很熟悉,请参考相关算法书比如算法讨论。

考虑最长公共子序列问题如何分解成子问题,设A=“a0,a1,…,am-1”,B=“b0,b1,…,bn-1”,并Z=“z0,z1,…,zk-1”为它们的最长公共子序列。不难证明有以下性质:

(1) 如果am-1==bn-1,则zk-1=am-1=bn-1,且“z0,z1,…,zk-2”是“a0,a1,…,am-2”和“b0,b1,…,bn-2”的一个最长公共子序列;

(2) 如果am-1!=bn-1,则若zk-1!=am-1时,蕴涵“z0,z1,…,zk-1”是“a0,a1,…,am-2”和“b0,b1,…,bn-1”的一个最长公共子序列;

(3) 如果am-1!=bn-1,则若zk-1!=bn-1时,蕴涵“z0,z1,…,zk-1”是“a0,a1,…,am-1”和“b0,b1,…,bn-2”的一个最长公共子序列。

这样,在找A和B的公共子序列时,如果有am-1==bn-1,则进一步解决一个子问题,找“a0,a1,…,am-2”和“b0,b1,…,bm-2”的一个最长公共子序列;如果am-1!=bn-1,则要解决两个子问题,找出“a0,a1,…,am-2”和“b0,b1,…,bn-1”的一个最长公共子序列和找出“a0,a1,…,am-1”和“b0,b1,…,bn-2”的一个最长公共子序列,再取两者中较长者作为A和B的最长公共子序列。

求解:
引进一个二维数组c[][],用c[i][j]记录X[i]与Y[j] 的LCS 的长度,b[i][j]记录c[i][j]是通过哪一个子问题的值求得的,以决定输出最长公共字串时搜索的方向。
我们是自底向上进行递推计算,那么在计算c[i,j]之前,c[i-1][j-1],c[i-1][j]与c[i][j-1]均已计算出来。此时我们根据X[i] == Y[j]还是X[i] != Y[j],就可以计算出c[i][j]。

问题的递归式写成:

C语言求解最长公共子字符串问题及相关的算法分析

回溯输出最长公共子序列过程:    

C语言求解最长公共子字符串问题及相关的算法分析

算法分析:
由于每次调用至少向上或向左(或向上向左同时)移动一步,故最多调用(m + n)次就会遇到i = 0或j = 0的情况,此时开始返回。返回时与递归调用时方向相反,步数相同,故算法时间复杂度为Θ(m + n)。

完整的实现代码如下:

/** 
找出两个字符串的最长公共子序列的长度 
** author :liuzhiwei  
** data  :2011-08-15 
**/  
#include "stdio.h" 
#include "string.h" 
#include "stdlib.h" 
int LCSLength(char* str1, char* str2, int **b) 
{ 
  int i,j,length1,length2,len; 
  length1 = strlen(str1); 
  length2 = strlen(str2); 
 
  //双指针的方法申请动态二维数组 
  int **c = new int*[length1+1];   //共有length1+1行 
  for(i = 0; i < length1+1; i++) 
    c[i] = new int[length2+1];   //共有length2+1列 
 
  for(i = 0; i < length1+1; i++) 
    c[i][0]=0;    //第0列都初始化为0 
  for(j = 0; j < length2+1; j++) 
    c[0][j]=0;    //第0行都初始化为0 
 
  for(i = 1; i < length1+1; i++) 
  { 
    for(j = 1; j < length2+1; j++) 
    { 
      if(str1[i-1]==str2[j-1])  //由于c[][]的0行0列没有使用,c[][]的第i行元素对应str1的第i-1个元素 
      { 
        c[i][j]=c[i-1][j-1]+1; 
        b[i][j]=0;     //输出公共子串时的搜索方向 
      } 
      else if(c[i-1][j]>c[i][j-1]) 
      { 
        c[i][j]=c[i-1][j]; 
        b[i][j]=1; 
      } 
      else 
      { 
        c[i][j]=c[i][j-1]; 
        b[i][j]=-1; 
      } 
    } 
  } 
  /* 
  for(i= 0; i < length1+1; i++) 
  { 
  for(j = 0; j < length2+1; j++) 
  printf("%d ",c[i][j]); 
  printf("\n"); 
  } 
  */ 
  len=c[length1][length2]; 
  for(i = 0; i < length1+1; i++)  //释放动态申请的二维数组 
    delete[] c[i]; 
  delete[] c; 
  return len; 
} 
void PrintLCS(int **b, char *str1, int i, int j) 
{ 
  if(i==0 || j==0) 
    return ; 
  if(b[i][j]==0) 
  { 
    PrintLCS(b, str1, i-1, j-1);  //从后面开始递归,所以要先递归到子串的前面,然后从前往后开始输出子串 
    printf("%c",str1[i-1]);    //c[][]的第i行元素对应str1的第i-1个元素 
  } 
  else if(b[i][j]==1) 
    PrintLCS(b, str1, i-1, j); 
  else 
    PrintLCS(b, str1, i, j-1); 
} 
 
int main(void) 
{ 
  char str1[100],str2[100]; 
  int i,length1,length2,len; 
  printf("请输入第一个字符串:"); 
  gets(str1); 
  printf("请输入第二个字符串:"); 
  gets(str2); 
  length1 = strlen(str1); 
  length2 = strlen(str2); 
  //双指针的方法申请动态二维数组 
  int **b = new int*[length1+1]; 
  for(i= 0; i < length1+1; i++) 
    b[i] = new int[length2+1]; 
  len=LCSLength(str1,str2,b); 
  printf("最长公共子序列的长度为:%d\n",len); 
  printf("最长公共子序列为:"); 
  PrintLCS(b,str1,length1,length2); 
  printf("\n"); 
  for(i = 0; i < length1+1; i++)  //释放动态申请的二维数组 
    delete[] b[i]; 
  delete[] b; 
  system("pause"); 
  return 0; 
} 

程序的效果图如下:

C语言求解最长公共子字符串问题及相关的算法分析

第二种方法为:

/** 
找出两个字符串的最长公共子序列的长度 
** author :liuzhiwei  
** data  :2011-08-15 
**/  
#include "stdio.h" 
#include "string.h" 
#include "stdlib.h" 
int LCSLength(char* str1, char* str2)  //求得两个字符串的最大公共子串长度并输出公共子串 
{ 
  int i,j,length1,length2; 
  length1 = strlen(str1); 
  length2 = strlen(str2); 
 
  //双指针的方法申请动态二维数组 
  int **c = new int*[length1+1];   //共有length1+1行 
  for(i = 0; i < length1+1; i++) 
    c[i] = new int[length2+1];   //共有length2+1列 
 
  for(i = 0; i < length1+1; i++) 
    c[i][0]=0;    //第0列都初始化为0 
  for(j = 0; j < length2+1; j++) 
    c[0][j]=0;    //第0行都初始化为0 
 
  for(i = 1; i < length1+1; i++) 
  { 
    for(j = 1; j < length2+1; j++) 
    { 
      if(str1[i-1]==str2[j-1])  //由于c[][]的0行0列没有使用,c[][]的第i行元素对应str1的第i-1个元素 
        c[i][j]=c[i-1][j-1]+1; 
      else if(c[i-1][j]>c[i][j-1]) 
        c[i][j]=c[i-1][j]; 
      else 
        c[i][j]=c[i][j-1]; 
    } 
  } 
 
  //输出公共子串 
  char s[100]; 
  int len,k; 
  len=k=c[length1][length2]; 
  s[k--]='\0'; 
  i=length1,j=length2; 
  while(i>0 && j>0) 
  { 
    if(str1[i-1]==str2[j-1]) 
    { 
      s[k--]=str1[i-1]; 
      i--; 
      j--; 
    } 
    else if(c[i-1][j]<c[i][j-1]) 
      j--; 
    else 
      i--; 
  } 
  printf("最长公共子串为:"); 
  puts(s); 
 
  for(i = 0; i < length1+1; i++)  //释放动态申请的二维数组 
    delete[] c[i]; 
  delete[] c; 
  return len; 
} 
 
int main(void) 
{ 
  char str1[100],str2[100]; 
  int length1,length2,len; 
 
  printf("请输入第一个字符串:"); 
  gets(str1); 
  printf("请输入第二个字符串:"); 
  gets(str2); 
  length1 = strlen(str1); 
  length2 = strlen(str2); 
  len=LCSLength(str1,str2); 
  printf("最长公共子串的长度为:%d\n",len); 
  system("pause"); 
  return 0; 
} 

       问题拓展:设A、B、C是三个长为n的字符串,它们取自同一常数大小的字母表。设计一个找出三个串的最长公共子序列的O(n^3)的时间算法。
       思路:跟上面的求2个字符串的公共子序列是一样的思路,只不过这里需要动态申请一个三维的数组,三个字符串的尾字符不同的时候,考虑的情况多一些而已。
/** 
找出三个字符串的最长公共子序列的长度 
** author :liuzhiwei  
** data  :2011-08-15 
**/  
#include "stdio.h" 
#include "string.h" 
#include "stdlib.h" 
int max1(int m,int n) 
{ 
  if(m>n) 
    return m; 
  else 
    return n; 
} 
int max2(int x,int y,int z,int k,int m,int n) 
{ 
  int max=-1; 
  if(x>max) 
    max=x; 
  if(y>max) 
    max=y; 
  if(z>max) 
    max=z; 
  if(k>max) 
    max=k; 
  if(m>max) 
    max=m; 
  if(n>max) 
    max=n; 
  return max; 
} 
int LCSLength(char* str1, char* str2, char* str3)  //求得三个字符串的最大公共子序列长度并输出公共子序列 
{ 
  int i,j,k,length1,length2,length3,len; 
  length1 = strlen(str1); 
  length2 = strlen(str2); 
  length3 = strlen(str3); 
 
  //申请动态三维数组 
  int ***c = new int**[length1+1];   //共有length1+1行 
  for(i = 0; i < length1+1; i++) 
  { 
    c[i] = new int*[length2+1];   //共有length2+1列 
    for(j = 0; j<length2+1; j++) 
      c[i][j] = new int[length3+1]; 
  } 
 
  for(i = 0; i < length1+1; i++) 
  { 
    for(j = 0; j < length2+1; j++) 
      c[i][j][0]=0; 
  } 
  for(i = 0; i < length2+1; i++) 
  { 
    for(j = 0; j < length3+1; j++) 
      c[0][i][j]=0; 
  } 
  for(i = 0; i < length1+1; i++) 
  { 
    for(j = 0; j < length3+1; j++) 
      c[i][0][j]=0;   
  } 
 
  for(i = 1; i < length1+1; i++) 
  { 
    for(j = 1; j < length2+1; j++) 
    { 
      for(k = 1; k < length3+1; k++) 
      { 
        if(str1[i-1]==str2[j-1] && str2[j-1]==str3[k-1]) 
          c[i][j][k]=c[i-1][j-1][k-1]+1; 
        else if(str1[i-1]==str2[j-1] && str1[i-1]!=str3[k-1]) 
          c[i][j][k]=max1(c[i][j][k-1],c[i-1][j-1][k]); 
        else if(str1[i-1]==str3[k-1] && str1[i-1]!=str2[j-1]) 
          c[i][j][k]=max1(c[i][j-1][k],c[i-1][j][k-1]); 
        else if(str2[j-1]==str3[k-1] && str1[i-1]!=str2[j-1]) 
          c[i][j][k]=max1(c[i-1][j][k],c[i][j-1][k-1]); 
        else 
        { 
          c[i][j][k]=max2(c[i-1][j][k],c[i][j-1][k],c[i][j][k-1],c[i-1][j-1][k],c[i-1][j][k-1],c[i][j-1][k-1]); 
        } 
      } 
    } 
  } 
  len=c[length1][length2][length3]; 
  for(i = 1; i < length1+1; i++)     //释放动态申请的三维数组 
  { 
    for(j = 1; j < length2+1; j++) 
      delete[] c[i][j]; 
    delete[] c[i]; 
  } 
  delete[] c; 
  return len; 
} 
 
int main(void) 
{ 
  char str1[100],str2[100],str3[100]; 
  int len; 
 
  printf("请输入第一个字符串:"); 
  gets(str1); 
  printf("请输入第二个字符串:"); 
  gets(str2); 
  printf("请输入第三个字符串:"); 
  gets(str3); 
  len=LCSLength(str1,str2,str3); 
  printf("最长公共子序列的长度为:%d\n",len); 
  system("pause"); 
  return 0; 
} 

程序的效果图如下:

C语言求解最长公共子字符串问题及相关的算法分析

 
反对 0举报 0 评论 0
 

免责声明:本文仅代表作者个人观点,与乐学笔记(本网)无关。其原创性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容、文字的真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
    本网站有部分内容均转载自其它媒体,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责,若因作品内容、知识产权、版权和其他问题,请及时提供相关证明等材料并与我们留言联系,本网站将在规定时间内给予删除等相关处理.

  • Rust应用调用C语言动态库的操作方法
    目录外部功能接口FFIUDP套接字的读超时Rust调用C语言动态库中的函数避免重复造***,使用Rust官方C语言库外部功能接口FFI虽然高级(脚本)编程语言的功能丰富,表达能力强,但对底层的一些特殊操作的支持并不完善,就需要以其他编程语言来实现。调用其他编程语
  • Delphi中获取Unix时间戳及注意事项(c语言中tim
    uses DateUtils;DateTimeToUnix(Now) 可以转换到unix时间,但是注意的是,它得到的时间比c语言中time()得到的时间大了8*60*60这是因为Now是当前时区的时间,c语言中time()是按格林威治时间计算的,北京时间比格林威治时间多了8小时DateTimeToUnix(Now)-8*60*
    02-09
  • Unicode与UTF-8互转(c语言和lua语言) python
    1. 基础1.1 ASCII码我们知道, 在计算机内部, 全部的信息终于都表示为一个二进制的字符串. 每个二进制位(bit)有0和1两种状态, 因此八个二进制位就能够组合出 256种状态, 这被称为一个字节(byte). 也就是说, 一个字节一共能够用来表示256种不同的状态, 每个状态
    02-09
  • R语言中cat函数 c语言cat命令
    R语言中cat函数 c语言cat命令
    R语言中cat函数。1、测试1cat("aa","bb")cat("aa","bb",sep = "_")  2、测试2a = 100b = 300c = "abcd"cat(a,b,c)cat(a,b,c,sep = "_") 3、测试3a = c("aaa", "bbb", "ccc")b = 1:4ca
    02-09
  • R语言之merge详解 c语言merge函数代码
    merge是R语言中用来合并数据框的函数merge函数的声明:?1234merge(x, y, by = intersect(names(x), names(y)),      by.x = by, by.y = by, all = FALSE, all.x = all, all.y = all,      sort = TRUE, suffixes = c(".x"
    02-09
  • R语言调用的C语言源代码查询 R语言 c
    R语言使用时可以调用自己写的C代码,但是有些C函数是软件包自带的,怎么查询在使用软件包 kerfdr 时,涉及到一个函数y = .C("massdist", x = as.double(xtrunc), xmass = as.double(tau[trunc]/sum(tau[trunc])), nx = nx, xlo = as.double(lo), xhi = as.dou
    02-09
  • centos安装与配置R语言 centos配置c语言环境
    Linux下安装R语言一、编译安装      由于采用编译安装,所以需要用到gcc编译环境,在编译前check文件时还会用到libXt-devel和readline-devel两个依赖,所以在编译R语言源码时先将这些工具和依赖包准备好。readline-devel 也可以不安装,不安装此包R语言编
    02-09
  • C语言利用链表实现学生成绩管理系统
    链表是一种常见的基础数据结构,结构体指针在这里得到了充分的利用。链表可以动态的进行存储分配,也就是说,链表是一个功能极为强大的数组,他可以在节点中定义多种数据类型,还可以根据需要随意增添,删除,插入节点。链表都有一个头指针,一般以head来表示
  • C语言通过三种方法实现属于你的通讯录
    目录一、基础版本1.1 通讯录的个人信息(结构体来实现)1.2通讯录名单1.3人员初始化1.4菜单1.5主函数二、功能的实现2.1、增加人数2.2、删除人数2.3、查找2.4、展示2.5、排序(这里我是通过名字)三、通讯录进阶(设置动态存储)3.1通讯录从静态改为动态3.2通
  • C++集体数据交换实现示例讲解 c语言两个数据交
    目录一、说明二、示例和代码一、说明到目前为止介绍的功能共享一对一的关系:即一个进程发送和一个进程接收。链接是通过标签建立的。本节介绍在多个进程中调用相同参数但执行不同操作的函数。对于一个进程,函数可能会发送数据,对于另一个进程,它可能会接收
点击排行