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]。

问题的递归式写成:

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

算法分析:
由于每次调用至少向上或向左(或向上向左同时)移动一步,故最多调用(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;
}

程序的效果图如下:

第二种方法为:

/**
找出两个字符串的最长公共子序列的长度
** 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;
}

程序的效果图如下:

(0)

相关推荐

  • C语言使用广度优先搜索算法解决迷宫问题(队列)

    本文实例讲述了C语言使用广度优先搜索算法解决迷宫问题.分享给大家供大家参考,具体如下: 变量 head 和 tail 是队头和队尾指针, head 总是指向队头, tail 总是指向队尾的下一个元素.每个点的 predecessor 成员也是一个指针,指向它的前趋在 queue 数组中的位置.如下图所示: 广度优先是一种步步为营的策略,每次都从各个方向探索一步,将前线推进一步,图中的虚线就表示这个前线,队列中的元素总是由前线的点组成的,可见正是队列先进先出的性质使这个算法具有了广度优先的特点.广

  • c语言来实现贪心算法之装箱问题

    装箱问题,贪心算法求近似最优解 复制代码 代码如下: import java.util.Arrays; import java.util.Comparator; //装箱问题,贪心算法 public class Enchase {     public void test1() {         Integer[] boxs={34,6,40,2,23,12,12};         int boxCaptation=40;//箱子容量         //倒序         Arrays.

  • 基于C语言实现简单的走迷宫游戏

    本文实例讲述了C语言实现简单的走迷宫游戏的方法,代码完整,便于读者理解. 学数据结构时用"栈"写的一个走迷宫程序,实际上用到双向队列,方便在运行完毕后输出经过的点. #include <cstdio> #include <deque> #include <windows.h> using namespace std; class node { public: int x,y; int lastOpt; }; deque<node> sta

  • C语言使用深度优先搜索算法解决迷宫问题(堆栈)

    本文实例讲述了C语言使用深度优先搜索算法解决迷宫问题.分享给大家供大家参考,具体如下: 深度优先搜索 伪代码 (Pseudocode)如下: 将起点标记为已走过并压栈; while (栈非空) { 从栈顶弹出一个点p; if (p这个点是终点) break; 否则沿右.下.左.上四个方向探索相邻的点 if (和p相邻的点有路可走,并且还没走过) 将相邻的点标记为已走过并压栈,它的前趋就是p点; } if (p点是终点) { 打印p点的坐标; while (p点有前趋) { p点 = p点的前趋;

  • C语言实现的猴子分桃问题算法解决方案

    本文实例讲述了C语言实现的猴子分桃问题算法.分享给大家供大家参考,具体如下: 问题: 海滩上有一堆桃子,五只猴子来分.第一只猴子把这堆桃子凭据分为五份,多了一个,这只猴子把多的一个扔入海中,拿走了一份.第二只猴子把剩下的桃子又平均 分成五份,又多了一个,它同样把多的一个扔入海中,拿走了一份,第三.第四.第五只猴子都是这样做的,问海滩上原来最少有多少个桃子? 程序: #include<stdio.h> int divided(int n, int m) //注意该递归函数的定义 { if(n/5

  • C语言解决螺旋矩阵算法问题的代码示例

    赶集网校招就采用了螺旋输出矩阵作为程序题,要求将矩阵螺旋输出如: 图中6*6矩阵线条所示为输出顺序,如果输出正确的话应该输出1~36有序数字.  我想的是这么做的: #include <stdio.h> //#define LEN 1 //#define LEN 2 //#define LEN 3 #define LEN 4 void printClock(int a[][LEN]){//输出函数 int t; int i = 0, m = 0; int j = LEN, n = LEN; w

  • C语言通过深度优先搜索来解电梯问题和N皇后问题的示例

    N皇后问题 问题描述: 在n×n格的棋盘上放置彼此不受攻击的n个皇后.按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子.n后问题等价于再n×n的棋盘上放置n个皇后,任何2个皇后不妨在同一行或同一列或同一斜线上. 需求输入: 给定棋盘的大小n (n ≤ 13) 需求输出: 输出有多少种放置方法. #include <stdio.h> #include <math.h> #define MAX 101 int total = 0; char m[MAX][MAX

  • C语言实现图的遍历之深度优先搜索实例

    DFS(Depth-First-Search)深度优先搜索算法是图的遍历算法中非常常见的一类算法.分享给大家供大家参考.具体方法如下: #include <iostream> #include <algorithm> #include <iterator> using namespace std; #define MAX_VERTEX_NUM 10 struct Node { int adjvex; struct Node *next; int info; }; typ

  • C语言的数字游戏算法效率问题探讨实例

    最近做了这样一个题目,感觉挺有趣~题目如下: 问题描述 Winder 最近在玩一个数字游戏,该游戏是在一个n*m 的网格上进行的,每个格子上有 一个数字,代表这个格子的数值.玩家需要从网格的左上角的格子走到右下角的格子,每次 只能向右或者向下走,并且不能回头.玩家每经过一个格子可以选择分值是否加上该格子的 数值,每次游戏的初始分数都是0. Winder 想知道在每场游戏,他最多能够得到多少分值.但是,Winder 很懒,所以你必 须帮他来完成这件事. 数据输入 输入第一行两个正整数N 和M(0<

  • C语言实现的猴子吃桃问题算法解决方案

    本文实例讲述了C语言实现的猴子吃桃问题.分享给大家供大家参考,具体如下: 问题: 猴子第一天摘下N个桃子,当时就吃了一半,还不过瘾,就又吃了一个.第二天又将剩下的桃子吃掉一半,又多吃了一个.以后每天都吃前一天剩下的一半零一个.到第10天在想吃的时候就剩一个桃子了,求第一天共摘下来多少个桃子? 解析: ① 从最后一天的x=1个,倒推出前一天的个数x,需要注意的是表达式为x=2(x+1),而不是x=2x+1,注意两者之间的区别,想清楚为什么第二种不正确. ② 将该表达式作为循环9次的循环体,并在该语

  • 基于C语言实现的迷宫游戏代码

    本文实例讲述了基于C语言实现迷宫游戏的方法,代码备有较为详尽的注释,便于读者理解.通过该游戏代码可以很好的复习C语言的递归算法与流程控制等知识,相信对于学习游戏开发的朋友有一定的借鉴价值. 完整的实例代码如下: #include <graphics.h> #include <stdlib.h> #include <stdio.h> #include <conio.h> #include <dos.h> #define N 20/*迷宫的大小,可改

  • C语言 数据结构中求解迷宫问题实现方法

    C语言 数据结构中求解迷宫问题实现方法 在学习数据结构栈的这一节遇到了求迷宫这个问题,拿来分享一下~ 首先求迷宫问题通常用的是"穷举求解" 即从入口出发,顺某一方向试探,若能走通,则继续往前走,否则原路返回,换另一个方向继续试探,直至走出去. 我们可以先建立一个8*8的迷宫其中最外侧为1的是墙 int mg[M+2][N+2]={ {1,1,1,1,1,1,1,1,1,1}, {1,0,0,1,0,0,0,1,0,1}, {1,0,0,1,0,0,0,1,0,1}, {1,0,0,0,

随机推荐