算法知识之最长公共子序列问题(动态规划)

2023-10-26

最近朋友让帮做个关于动态规划的最长公共子序列的问题,翻看以前的笔记并完成该题后,顺便写这样一篇文章,希望对大家有所帮助,同时也帮助自己回顾该知识点.

一.最长公共子序列的定义

子序列:若给定序列X={x1,x2,…,xm},则另一序列Z={z1,z2,…,zk},X的子序列是指存在一个严格递增下标序列{i1,i2,…,ik}使得对于所有j=1,2,…,k有:zj=xij.
公共子序列:给定2个序列XY,当另一序列Z既是X的子序列又是Y的子序列时,称Z是序列XY公共子序列.
最长公共子序列:给定2个序列X={x1,x2,,xm}Y={y1,y2,,yn},找出XY的最长公共子序列.
如:序列ABCDEF和ADFGH的最长公共子序列为ADF

注意:最长公共子串(Longest Common Substirng)和最长公共子序列(Longest Common Subsequence,简称LCS)的区别为是最长公共子串的串是一个连续的部分,而最长公共子序列则是从不改变序列的顺序,而从序列中去掉任意的元素而获得新的序列;通俗的说就是子串中字符的位置必须是连续的而子序列则可以不必连续.

二.最优子结构性质

设序列X={x1,x2,…,xm}和Y={y1,y2,…,yn}的最长公共子序列为Z={z1,z2,…,zk} ,则
    (1)若xm=yn,则zk=xm=yn,且z1,z2,…, zk-1是否为x1,x2,…,xm-1和y1,y2,…,yn-1的最长公共子序列.
    (2)若xm≠yn且zk≠xm,则Z是x1,x2,…,xm-1和Y的最长公共子序列.
    (3)若xm≠yn且zk≠yn,则Z是X和y1,y2,…,yn-1的最长公共子序列.

由此可见,2个序列的最长公共子序列包含了这2个序列的前缀的最长公共子序列.因此,最长公共子序列问题具有最优子结构性质.当问题具有最优子结构性质和子问题重叠性质时就可以用动态规划算法解决该问题.

三.动态规划方法分析

由最长公共子序列问题的最优子结构性质建立子问题最优值的递归关系.用c[i][j]记录序列和的最长公共子序列的长度.其中,Xi={x1,x2,…,xi},Yj={y1,y2,…,yj}.当i=0或j=0时,空序列是Xi和Yj的最长公共子序列.故此时C[i][j]=0.其它情况下,由最优子结构性质可建立递归关系如下:

其对应的核心代码如下:

//参数:x字符串长度为m y字符串长度为n
void LCSLength(char x[], char y[],int m,int n)
{  
       /* 计算最长公共子序列的长度 */
       int L[m][n],i,j;
       for (i = 0; i <= m; i++) L[i][0] = 0;
       for (i = 0; i <= n; i++) L[0][i] = 0;
       for (i = 1; i <= m; i++)
       {
          for (j = 1; j <= n; j++) 
	  {
             if (x[i]==y[j]) 
                  L[i][j]=L[i-1][j-1]+1;
             else if (L[i-1][j]>= L[i][j-1])
                  L[i][j]= L[i-1][j]; 
             else 
                  L[i][j]= L[i][j-1];
          }
       } 
       return L[m][n];
}

例如:输入字符串“bdcaba”和"abcbdab",求它们的最长公共子序列长度.在《算法设计与分析》课程中我们老师讲述的方法通常是使用动态规划填充表格方法解决.初始时,X字符串的长度为m,Y字符串的长度为n.c[m,n]二位数组如上面递归关系递归,最后的c[m,n]为最大数字即最长公共子序列的长度.

其中从表中找出最长公共子序列的方法

 

     (1) 从(m,n)到(0,0)

 

    (2) 若当前格与左边一格相同,则画" 一";若当前格与上边一格相同,则画"|";上两者都不符合,从当前格到左上格化斜线箭头"\";
    (3) 从当前格向箭头方向前进一格,对此格进行(2)
    (4) 从(m,n)到(0,0)的不同路径中,斜线箭头"\"相对应的格的元素构成最长公共子序列.如图bcbd、bcdb、badb.

四.问题的提出与解决

1.问题

题目:求两个字符串的最长公共子序列的长度.
输入:第一行字符串S1,第二行字符串 S2 (1<=字符串长度<=1000).输出:数字M,为最长公共子序列长度.测试用例如下:
输入
BDCABA
ABCBDAB

输出
4
输入
ABKLMNABCDI
ABCDEFGHIJKLMNOPQRSTUVWXYZ
输出
6

 

2.代码

 

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int *pln1 , *pln2;
char a[10010] , b[10010];
int main()
{
	int i , j , lena , lenb ;
	gets(a);
	gets(b);
	lena = strlen(a);
	lenb = strlen(b);
	pln1 = (int*)calloc( lenb + 1 , sizeof(int) );
	memset( pln1 , 0 , sizeof(pln1) );
	pln2 = (int*)calloc( lenb + 1 , sizeof(int) );
	memset( pln2 , 0 , sizeof(pln2) );
	for( i = 1 ; i <= lena ; i++ )
	{
		for( j = 1 ; j <= lenb ; j++ )
		{
			if( a[i-1] == b[j-1] )
			{
				pln2[j] = pln1[j-1] + 1;
			}
			else
			if( pln1[j] >= pln2[j-1] )
			{
				pln2[j] = pln1[j];
			}
			else
			{
				pln2[j] = pln2[j-1];
			}
		}
		free(pln1);
		pln1 = pln2;
		pln2 = (int*)calloc( lenb + 1 , sizeof(int) );
		memset( pln2 , 0 , sizeof(pln2) );
	}
	printf( "%d\n" , pln1[lenb] );
	return 0;
}

五.问题的升华与解决

1.升华问题

输入:输入文件中的第1行是一个正整数T(0<T<=10),表示有T组测试数据.接下来是每组测试数据的描述,每组测试数据有3行.测试数据的第1行有2个正整数m、n,中间用一个空格隔开(0<m,n<50);第2、3行是长度分别为m、n的2个序列X和Y,每个序列的元素间用一个空格隔开.序列中每个元素由字母、数字等构成.输入直到文件结束

输出:对输入中的每组测试数据,先输出Case #表示第几组数据,在输出最长公共子序列,输出所有的最长公共子序列,并输出动态规划表格c表和b表.(测试用例见结果图)

2.代码

这里涉及到一个新的问题:就是使用上面所叙述的填充表格来实现动态规划,其中c[m,n]记录的是当前序列的最长子序列长度;还需要引用一个吧b[m,n]表来寻找所有最长公共子序列,并把结果存入到result[]数组中.其中最重要的代码就是两个实现的函数,如下:

 

第一个LSCLength函数是求最长公共子序列长度的函数,并在该函数中填充c[m][n]和b[m][n].

 

//函数:计算最优值
//参数:m字符串X长 n字符串Y长 X字符串 Y字符串 b标志数组寻找所有字符串用
int LSCLength( int m, int n, char *X, char *Y, int b[][100] )
{
	/*计算最长公共子序列的长度*/
	int num[100][100];
	int i,j;
	int sum;
	
	/*清零*/
	for( i=0 ; i<=m ; i++ )
	{
		for( j=0 ; j<=n ; j++ )
		{
			num[i][j]=0;
			b[i][j]=0;
		}
	} 
	
	/* 递归结构-动态规划并输出 */
	for( i=1 ; i<=m ; i++ )
	{		
		for( j=1 ; j<=n ; j++ )
		{
			if( X[i]==Y[j] ) {
				num[i][j]=num[i-1][j-1]+1;
				b[i][j]=1;
			}
			
			else if( num[i-1][j]>num[i][j-1] ) {
				num[i][j]=num[i-1][j];
				b[i][j]=2;
			}
			
			else if( num[i-1][j]<num[i][j-1] ){
				num[i][j]=num[i][j-1];
				b[i][j]=3;
			}
			else {
				num[i][j]=num[i][j-1];
				b[i][j]=4;
			}
		}	
	}
	
	sum = num[m][n];
	printf("最长公共子序列的长度:%d\n",sum);
		
	//输出c[m][n]表
	printf("\n");
	for(i=0;i<=m;i++)
	{
		for(j=0;j<=n;j++)
		{
			printf("%d ",num[i][j]);
		}
		printf("\n");
	}
	//输出b[m][n]表
	printf("\n");
	for(i=0;i<=m;i++)
	{
		for(j=0;j<=n;j++)
		{
			printf("%d ",b[i][j]);
		}
		printf("\n");
	}
	
	return sum;
}

第二个DisplayLSC函数是通过b[m][n]递归计算所有最长公共子序列,并存储至result数组中.

//定义全局变量用于保存结果result 结果个数保存为count
char result[100];
int count=0;

//函数:计算所有最长公共子序列
//参数:m字符串X的长度 n字符串Y的长度 b标志数组 current_len当前长度 max_len最长公共子序列长度
void DisplayLSC(int i,int j,char *X,int b[][100],int current_len,int max_len)
{
	int s;

	//采用递归的算法求解所有长度
	if(i==0 || j==0)                 //为0时输出结果并返回
	{
		for(s=0;s<max_len;s++)
		{
			printf("%c ",result[s]);
		}
		printf("\n");
		count++;
		return;
	}

	if(b[i][j]==1)
	{
		current_len--;
		result[current_len]=X[i];
		DisplayLSC(i-1,j-1,X,b,current_len,max_len);
	}
	else
	{
		if(b[i][j]==2)
		{
			DisplayLSC(i-1,j,X,b,current_len,max_len);
		}
		else
		{
			if(b[i][j]==3)
			{
				DisplayLSC(i,j-1,X,b,current_len,max_len);
			}
			else
			{
				DisplayLSC(i,j-1,X,b,current_len,max_len);
				DisplayLSC(i-1,j,X,b,current_len,max_len);
			}
		}
	}
}

3.结果

最后输出的结果如下图所示:

      

希望该文章对大家有所帮组,同时该文章参考了王晓东的《计算机算法设计与分析》,并引用了自己学校的PPT动态规划内容.同时感谢梦醒潇湘love博主的文章,希望大家也可以去见解该文章.
http://blog.chinaunix.net/uid-26548237-id-3374211.html
文章主要是对自己以前学过的知识的巩固以记录,如果有错误或不足之处,希望大家海涵.
(By:Eastmount 2013-11-5 中午3点http://blog.csdn.net/eastmount/)

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

算法知识之最长公共子序列问题(动态规划) 的相关文章

  • RSA算法计算

    RSA算法简单计算 5个公式 n p q n p 1 q 1 求 n e d mod n 1 求e d其中之一 c m e mod n 加密 m c d mod n 解密 字符说明 p q为两个素数 n为p q乘积 为欧拉函数 n 为小于或
  • Awt+Swing+Mysql实现超市商品交易管理系统(含全部代码)

    目录 成果展示 数据库表格准备 绘制窗体以及组件 主窗体 登录面板 上架商品面板 下架商品面板 操作商品面板 数据面板 展示表格 关键技术与思路 与数据库建立连接 对数据库数据进行增删改 查询数据 验证状态 切换面板与点击触发事件 全部代码

随机推荐

  • 单片机多字节串口接收(转)

    转自 http bbs ednchina com BLOG ARTICLE 3007162 HTM 工作了一年多 写了不少单片机串口程序 感觉串口多字节接收部分的逻辑相对于配置寄存器跟串口回复来说 是有点难度的 寄存器配置基本上都是死的 串
  • linux如何查看内存?

    linux查看内存的方法 1 通过 proc meminfo 方法查看内存 2 使用free命令查看内存 3 使用ps命令显示各个进程的内存使用情况 4 通过top命令显示每个进程的内存实时使用率 1 查看RAM使用情况最简单的方法是通过
  • unity 模拟相机云台效果-物体指定轴不受父节点影响

    物体指定轴的世界坐标旋转值不随父节点改变 using System Collections using System Collections Generic using UnityEngine
  • error: #40: expected an identifier

    错误指向stm32f10x h typedef enum FALSE 0 TRUE FALSE bool 原因是在其他文件中重复 define了FALSE 的值 将其注释掉即可
  • 三面(技术+HR面试)网易,分享我的面试经验!(已拿offer)

    前言 Java后端面试标准其实不复杂 第一能干活 第二Java基础要好 第三最好熟悉些分布式框架 其实 很多面试者能力其实不差 但面试时没准备或不会说 这样的人可能在进团队干活后确实能达到期望 但可能就无法通过面试 但面试官总是只根据面试情
  • DRM驱动代码分析:图层参数更新

    前言 无业居家 闭门造车 非常欢迎大家帮忙指正 有些代码流程是看代码分析的 没有去验证是否正确 我对DRM框架的很多东西都不了解 所以有些地方会比较生硬 熟悉学习需要时间 文章一直堆在草稿箱可能会降低我的积极性 所以我还是先发布了文章 后面
  • JavaSE之注释规范、文档注释及注解

    Java中的注释不会出现在可执行程序中 有三种标记注释的方式 1 单行注释 2 多行注释 3 文档注释 一 注释可以帮助我们更清晰地阅读代码 了解代码 在 阿里巴巴Java开发手册中 也对注释作了规约 注释规约如下 1 强制 类 类属性 类
  • RuntimeException

    运行时异常可以理解为 隶属于开发者的问题 代码有bug肯定要开发者自己修正啊 处理RuntimeException 不是try catch能解决的 try catch在这里使用毫无意义 编译时异常可以理解为 隶属于用户的问题 用户用的时候没
  • Java Timer定制每天特定时间执行任务

    package com segsec gisap import java util Calendar import java util Date import java util Timer import java util TimerTa
  • JVM垃圾回收器 七种经典垃圾回收器

    文章目录 垃圾回收器概述 评估GC的性能指标 吞吐量 throughput 暂停时间 pause time 七种经典的垃圾回收器 垃圾收集器组合关系 Serial回收器 串行回收 ParNew回收器 并行回收 Parallel Scaven
  • 【华为OD机试 2023】 网上商城优惠活动 / 模拟商场优惠打折II(C++ Java Javascript Python)

    华为od机试题库 华为OD机试2022 2023 C Java JS Py https blog csdn net banxia frontend category 12225173 html 华为OD机试2023最新题库 更新中 C Ja
  • 阿里云NAS文件存储基本介绍与购买使用

    文章目录 1 NAS文件存储基本概念 1 1 什么是NAS文件存储 1 2 NAS的应用场景 1 3 NAS OSS EBS的区别 2 购买NAS文件存储 2 1 开通NAS服务 2 2 创建NAS文件系统 2 3 配置NAS文件系统属性
  • Docker Harbor 私有镜像仓库的部署和管理

    目录 一 什么是Harbor 二 Harbor的特性 三 Harbor的构成 四 部署配置Docker Harbor 首先需要安装 Docker Compose 服务 部署 Harbor 服务 修改配置文件 docker配置文件添加本地仓库
  • 蚁群算法(ACO)分析总结(Matlab+C#模拟解决TSP旅行商问题)

    蚁群算法 1 1 简介 1 2 整体框架 1 3 蚁群算法的基本要素 1 3 1 信息素的正反馈机制 1 3 2 信息素的更新策略 1 3 3 算法停止准则 1 4 蚂蚁个体的建模问题 1 5 蚁群算法的重要参数 1 6 蚁群算法的基本流程
  • 怎么判断私网地址_如何判断一个IP地址是私有地址

    如何判断一个IP地址是私有地址 首先 我们得先了解什么是私有地址 本文所指的IP地址 皆是IPV4 一个IPV4地址 由四段组成 最大值为255 一个IP地址其实就是一个32位的bit串 每8位一段 所谓私有地址 就是非注册地址 只能做内网
  • char数组和指针问题

    这个问题是C 基础问题中相当折腾人的一个 死记硬背解决不了根本问题 记住还是要忘 需要仔细研究其本质 这两种方式就是数组和指针的方式 char a 6 abcde char b abcde 第一行声明了并初始化了一个char数组 第二行是声
  • 从传统软件开发到云原生转型:大数据和AI如何引领软件开发的新趋势

    文章目录 1 数据驱动的开发 2 智能化的用户体验 3 云原生的可扩展性 4 实时处理和决策 5 自动化和效率提升 6 持续集成和交付的加速 7 数据安全和隐私 8 持续学习和创新 个人主页 程序员 小侯 CSDN新晋作者 欢迎 点赞 评论
  • C语言:IP地址

    题目 include
  • C6385:从“buffer”中读取的数据无效: 可读大小为“recv()`72”个字节,但可能读取了“25”个字节。

    C 网络编程中接收结构体对象遇到的问题 想从客户端发送一个结构体对象到服务器 在网上查询后发现可以在客户端用memcpy把结构体拷贝到字符串上发送给客户端 再在客户端把字符串转化为结构体 具体代码如下 结构体 typedef struct
  • 算法知识之最长公共子序列问题(动态规划)

    最近朋友让帮做个关于动态规划的最长公共子序列的问题 翻看以前的笔记并完成该题后 顺便写这样一篇文章 希望对大家有所帮助 同时也帮助自己回顾该知识点 一 最长公共子序列的定义 子序列 若给定序列X x1 x2 xm 则另一序列Z z1 z2