算法导论--------------LCS问题(最长公共子系列)

1、基本概念

   一个给定序列的子序列就是该给定序列中去掉零个或者多个元素的序列。形式化来讲就是:给定一个序列X={x1,x2,……,xm},另外一个序列Z={z1、z2、……,zk},如果存在X的一个严格递增小标序列<i1,i2……,ik>,使得对所有j=1,2,……k,有xij
zj,则Z是X的子序列。例如:Z={B,C,D,B}是X={A,B,C,B,D,A,B}的一个子序列,相应的小标为<2,3,5,7>。从定义可以看出子序列直接的元素不一定是相邻的。

公共子序列:给定两个序列X和Y,如果Z既是X的一个子序列又是Y的一个子序列,则称序列Z是X和Y的公共子序列。例如:X={A,B,C,B,D,A,B},Y={B,D,C,A,B,A},则序列{B,C,A}是X和Y的一个公共子序列,但不不是最长公共子序列。

最长公共子序列(LCS)问题描述:给定两个序列X={x1,x2,……,xm}和Y={y1,y2,……,yn},找出X和Y的最长公共子序列。

2、动态规划解决过程

1)描述一个最长公共子序列

 
 如果序列比较短,可以采用蛮力法枚举出X的所有子序列,然后检查是否是Y的子序列,并记录所发现的最长子序列。如果序列比较长,这种方法需要指数级时间,不切实际。

  
LCS的最优子结构定理:设X={x1,x2,……,xm}和Y={y1,y2,……,yn}为两个序列,并设Z={z1、z2、……,zk}为X和Y的任意一个LCS,则:

(1)如果xm=yn,那么zk=xm=yn,而且Zk-1是Xm-1和Yn-1的一个LCS。

  
(2)如果xm≠yn,那么zk≠xm蕴含Z是是Xm-1和Yn的一个LCS。

  
(3)如果xm≠yn,那么zk≠yn蕴含Z是是Xm和Yn-1的一个LCS。

  
定理说明两个序列的一个LCS也包含两个序列的前缀的一个LCS,即LCS问题具有最优子结构性质。

2)一个递归解

  
根据LCS的子结构可知,要找序列X和Y的LCS,根据xm与yn是否相等进行判断的,如果xm=yn则产生一个子问题,否则产生两个子问题。设C[i,j]为序列Xi和Yj的一个LCS的长度。如果i=0或者j=0,即一个序列的长度为0,则LCS的长度为0。LCS问题的最优子结构的递归式如下所示:

3)计算LCS的长度

  
采用动态规划自底向上计算解。书中给出了求解过程LCS_LENGTH,以两个序列为输入。将计算序列的长度保存到一个二维数组C[M][N]中,另外引入一个二维数组B[M][N]用来保存最优解的构造过程。M和N分别表示两个序列的长度。该过程的伪代码如下所示

注意我的代码中继续使用vector的vector来表示二维数组:

//最长公共子序列问题:
//给定两个两个序列X = <x1, x2, ....., xm>和Y = <y1, y2, ...., yn>, 希望找出X和Y的最大长度的公共子序列,可以使用动态规划来解决
//定理15.1(LCS的最优子结构)
//设X = <x1, x2, ....., xm>和Y = <y1, y2, ...., yn>为两个序列,并设Z = { z1, z2, ...., zk }为X和Y的任意一个LCS
//(1)如果xm = yn, 那么zk = xm = yn, 而且Zk - 1是Xm - 1与Yn - 1的一个LCS
//(2)如果xm != yn,那么Zk ! = xm蕴含Z是Xm - 1和Y的一个LCS
//(3)如果xm != yn, 那么Zk ! = yn,蕴含Z是X和Yn - 1的一个LCS

//定义c[i][j]为序列Xi与Yi的一个LCS长度,则有下面递归表达式
// 当i = 0 或 j = 0的时候 c[i][j]=0;
//若i, j > 0 且xi = yi的时候  c[i][j] = c[i - 1][j - 1] + 1
//若i, j > 0 且 xi != yi   c[i][j]= max(c[i][j - 1], c[i - 1][j])
//根据上面的递归式可以写出自底向上的动态规划代码

//author:ugly_chen(stallman)  time:2014.12.15  0:51
//if you have any problems , pls ask me and leave some words, loughing.......
#include<iostream>
#include<string>
#include<vector>
#include<algorithm>
using namespace std;

pair<vector<vector<int>>,vector<vector<int>>>
LCS_Length(string x, string y)
{
	int m = x.size();
	int n = y.size();
	vector<vector<int>> c(m+1, vector<int>(n+1, 0));
	vector<vector<int>> b(m+1, vector<int>(n+1, 0));

	for (int i = 1; i <= m; ++i)
	{
		c[i][0] = 0;
	}
	for (int j = 1; j <= n; ++j)
	{
		c[0][j] = 0;
	}

	for (int i = 1; i <= m; ++i)
	{
		for (int j = 1; j <= n; ++j)
		{
			if (x[i - 1] == y[j - 1])
			{
				c[i][j] = c[i - 1][j - 1] + 1;
				b[i][j] = 1;//这里我使用1表示箭头指向左上,
			}
			else if (c[i - 1][j] >= c[i][j - 1])
			{
				c[i][j] = c[i - 1][j];
				b[i][j] = 2;//这里我使用2表示箭头指向上
			}
			else
			{
				c[i][j] = c[i][j - 1];
				b[i][j] = 3;//这里我使用3表示箭头指向左
			}
		}
	}

	return make_pair(c, b);
}

void print_LCS(vector<vector<int>>b, string x, int i, int j)
{
	if (i == 0 || j == 0)
		return;
	if (b[i][j] == 1)
	{
		print_LCS(b, x, i - 1, j - 1);
		cout << x[i - 1]<<" ";
	}
	else if (b[i][j] == 2)
		print_LCS(b, x, i - 1, j);
	else
		print_LCS(b, x, i, j - 1);
}

int main()
{
	string x = "ABCBDAB";
	string y = "BDCABA";
	int i = x.size();
	int j = y.size();

	vector<vector<int>> c = LCS_Length(x, y).first;
	vector<vector<int>> b = LCS_Length(x, y).second;

	cout << "the LCS is: " << c[x.size()][y.size()] << endl;
	print_LCS(b, x, i, j);

	return 0;
}
时间: 2024-11-05 16:00:20

算法导论--------------LCS问题(最长公共子系列)的相关文章

算法导论--动态规划(最长公共子序列)

最长公共子序列问题(LCS) 给定两个序列X=?x1,x2,x3...xm?和Y=?y1,y2,y3...xn?,求X和Y的最长公共子序列. 例如:X=?A,B,C,B,D,A,B?,和Y=?B,D,C,A,B,A?,的最长公共子序列为?B,C,B,A?,长度为4: 对于此问题,可以采用暴力求解的方式来比对,即穷举出X的所有子序列,用每个子序列与y做一 一比较.假如X序列共有m个元素,对每个元素可以决定选或不选,则X的子序列个数共有2m个,可见与长度m呈指数阶,这种方法效率会很低. 动态规划 前

算法导论之动态规划(最长公共子序列和最优二叉查找树)

动态规划师通过组合子问题的解而解决整个问题,将问题划分成子问题,递归地求解各子问题,然后合并子问题的解而得到原问题的解.和分治算法思想一致,不同的是分治算法适合独立的子问题,而对于非独立的子问题,即各子问题中包含公共的子子问题,若采用分治法会重复求解,动态规划将子问题结果保存在一张表中,避免重复子问题重复求解. 动态规划在多值中选择一个最优解,其算法设计一般分为4个步骤:描述最优解的结构:递归定义最优解的值:按自底向上的方式计算最优解的值:由计算出的结果构造一个最优解. 1)装配线调度 求解最快

动态规划--之--最长公共子字符串

package 动态规划;import java.util.Scanner;public class LogestCommonZiXuLie { public static void main(String[] args)     {      Scanner scan = new Scanner(System.in);      while(scan.hasNextLine())        {          String str = scan.nextLine();         

使用后缀数组寻找最长公共子字符串JavaScript版

后缀数组很久很久以前就出现了,具体的概念读者自行搜索,小菜仅略知一二,不便讨论. 本文通过寻找两个字符串的最长公共子字符串,演示了后缀数组的经典应用. 首先需要说明,小菜实现的这个后缀数组算法,并非标准,只是借鉴了其中的思想. 小菜实现的算法,有两个版本,第一个是空间换时间,第二个是时间换空间. 空间换时间版本 1 /* 2 利用后缀数组获取两个字符串最长公共子字符串 3 空间换时间版本 4 @params 5 s1 String,要分析的字符串 6 s2 String,要分析的字符串 7 no

uva 10066 The Twin Towers (最长公共子)

uva 10066 The Twin Towers 标题效果:最长公共子. 解题思路:最长公共子. #include<stdio.h> #include<string.h> #include<stdlib.h> #include<algorithm> using namespace std; int a[105], b[105], dp[105][105]; int main() { int n, m, Case = 1; while (scanf(&quo

2000:最长公共子上升序列

2000:最长公共子上升序列 查看 提交 统计 提问 总时间限制:  10000ms 内存限制:  65536kB 描述 给定两个整数序列,写一个程序求它们的最长上升公共子序列.当以下条件满足的时候,我们将长度为N的序列S1 , S2 , . . . , SN 称为长度为M的序列A1 , A2 , . . . , AM的上升子序列: 存在 1 <= i1 < i2 < . . . < iN <= M ,使得对所有 1 <= j <=N,均有Sj = Aij,且对于

LCS 求最长公共子序列

最长公共子序列不需要字符连续出现和字串不同 //LCS 求最长公共子串模板题  Common Subsequence 描述 A subsequence of a given sequence is the given sequence with some elements (possible none) left out. Given a sequence X = <x1, x2, ..., xm> another sequence Z = <z1, z2, ..., zk> is

最长公共子上升序列(LCIS)

最长公共子上升序列 AC_Code 1 #include <iostream> 2 #include <cstdio> 3 #include <string> 4 #include <cstring> 5 #include <string> 6 #include <cmath> 7 #include <cstdlib> 8 #include <algorithm> 9 using namespace std;

LCS(最长公共子序列)动规算法正确性证明

今天在看代码源文件求diff的原理的时候看到了LCS算法.这个算法应该不陌生,动规的经典算法.具体算法做啥了我就不说了,不知道的可以直接看<算法导论>动态规划那一章.既然看到了就想回忆下,当想到算法正确性的时候,发现这个算法的正确性证明并不好做.于是想了一段时间,里面有几个细节很trick,容易陷进去.想了几轮,现在把证明贴出来,有异议的可以留言一起交流. 先把一些符号和约定说明下: 假设有两个数组,A和B.A[i]为A的第i个元素,A(i)为有A的第一个元素到第i个元素所组成的前缀.m(i,