最长公共子序列问题LCS 使用动态规划和备忘录方法实现

最长公共子序列问题LCS

  1. 公共子序列
    子序列:给定一个序列X=<x1,x2…,xm>,另一个序列Z=<z1,z2…,zk>,存在一个严格递增的X的下标序列<i1,i2…ik>,满足对所有的j=1,2,…,k,xij = zj
    公共子序列:给定两个序列X和Y,Z同时是X和Y的子序列,称Z是X和Y的公共子序列。

  2. LCS的最优子结构
    令X=<x1,x2…,xm>和Y=<y1,y2,…,ym>为两个序列,Z=<z1,z2…,zk>为X和Y的任意LCS
    (1) 如果xm=yn,那么zk=xm=yn且Zk-1是Xm-1Yn-1的一个LCS
    (2) 如果xm≠yn,那么zk≠xm意味着Z是Xm-1和Y的一个LCS
    (3) 如果xm≠yn,那么zk≠yn意味着Z是X和Yn-1的一个LCS

  3. 递归解
    c[i,j]是Xi和Yj的LCS长度
    c[i,j]
    = 0 , i=0且j=0
    = c[i-1,j-1] ,i,j>0且xi=yi
    = max(c[i,j-1],c[i-1,j]), i,j>0且xi≠yj

  4. 例子
    X:ABCBDAB Y:BDCABA => LCS:BCBA

动态规划法

  1. 算法
    说明:声明了一个m+1*n+1大小的table,0行0列初始化为0,然后从左往右,从上往下地按照递归解填表。填表结束后通过Print可以递归地按同样的方法打印出所求的子序列。
LCS-LENGTH(X,Y)
	m=X.length+1
	n=Y.length+1
	let c[0..m,0..n] be new table
	for i=0 to m
		c[i,0] = 0
	for j=0 to n
		c[0,j] = 0
	for i=0 to m
		for j=1 to n
			if x[i]==y[j]
				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]
	return c

PRINT-LCS(c,X,i,j)
	if i==0 and j==0
		return
	if c[i,j]==c[i-1,j-1]+1
		PRINT-LCS(c,X,i-1,j-1)
		print x[i]
	else if c[i,j]=c[i-1,j]
		PRINT-LCS(c,X,i-1,j)
	else
		PRINT-LCS(c,X,i,j-1)

  1. 截图
    在这里插入图片描述
  2. 代码
int LCS_bottom_to_top(vector<vector<int>> & c, const vector<char> & x, const vector<char> & y)
{
	int m = x.size()+1;
	int n = y.size()+1;
	c.resize(m);
	for(int i=0;i<m;i++)
	{
		c[i].resize(n);
	}
	for(int i=0;i<m;i++)
	{
		c[i][0] = 0;
	}
	for(int j=0;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;
			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];
		}
	}
	return c[m-1][n-1];
}



void Print_LCS(const vector<vector<int>> & c,const vector<char>&x, const vector<char>&y, int i, int j)
{
	if(i==0 && j==0)
		return;
	if(x[i-1]==y[j-1])
	{
		Print_LCS(c,x,y,i-1,j-1);
		cout<<x[i-1]<<" ";
	}
	else if(c[i][j]==c[i-1][j])
		Print_LCS(c,x,y,i-1,j);
	else
		Print_LCS(c,x,y,i,j-1);
}

备忘录法

  1. 算法
    解释:
    LCS_memorized函数:用于将table初始化,0行0列都为0,其他元素都为-1,表示备忘录没有记录这些元素,初始化完成调用LCS_RECUR函数。
    LCS_RECUR函数:如果备忘录已经有记录直接返回,否则递归地查表并回填备忘录的当前位置。
LCS_MEMORIZED(c, X, Y)
	m=X.length+1
	n=Y.length+1
	let c[0..m,0..n] be new table
	for i=0 to m
		c[i,0] = 0
	for j=0 to n
		c[0,j] = 0
	for i=0 to m
		for j=0 to n
			c[i,j] = -1
	return LCS_RECUR(c,X,Y,m,n)

LCS_RECUR(c,X,Y,i,j)
	if c[I,j]-1
		return c[I,j]
	if x[i-1]==y[j-1]
		lcs = LCS_RECUR(c,x,y,i-1,j-1)+1
	else
		up = LCS_RECUR(c,x,y,i-1,j)
		left = LCS_RECUR(c,x,y,i,j-1)
		lcs = max(up, left)
	c[i,j] = lcs
	return lcs

2.截图
在这里插入图片描述
3.代码(包括动态规划法)

#include <iostream>
#include <vector>
#include <iomanip>
using namespace std;

int LCS_recur(vector<vector<int>> & c, const vector<char> & x, const vector<char> & y, int i, int j)
{
	if(c[i][j]!=-1)
		return c[i][j];
	int lcs;
	if(x[i-1]==y[j-1])
		lcs = LCS_recur(c,x,y,i-1,j-1)+1;
	else
	{
		int up = LCS_recur(c,x,y,i-1,j);
		int left = LCS_recur(c,x,y,i,j-1);
		lcs = up>=left?up:left;
	}
	c[i][j] = lcs;
	return lcs;
}

int LCS_memorized(vector<vector<int>> & c, const vector<char> & x, const vector<char> & y)
{
	int m = x.size()+1;
	int n = y.size()+1;
	c.resize(m);
	for(int i=0;i<m;i++)
	{
		c[i].resize(n);
	}
	for(int i=0;i<m;i++)
	{
		c[i][0] = 0;
	}
	for(int j=0;j<n;j++)
	{
		c[0][j] = 0;
	}
	for(int i=1;i<m;i++)
	{
		for(int j=1;j<n;j++)
		{
			c[i][j] = -1;
		}
	}
	return LCS_recur(c,x,y,x.size(),y.size());
}

int LCS_bottom_to_top(vector<vector<int>> & c, const vector<char> & x, const vector<char> & y)
{
	int m = x.size()+1;
	int n = y.size()+1;
	c.resize(m);
	for(int i=0;i<m;i++)
	{
		c[i].resize(n);
	}
	for(int i=0;i<m;i++)
	{
		c[i][0] = 0;
	}
	for(int j=0;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;
			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];
		}
	}
	return c[m-1][n-1];
}



void Print_LCS(const vector<vector<int>> & c,const vector<char>&x, const vector<char>&y, int i, int j)
{
	if(i==0 && j==0)
		return;
	if(x[i-1]==y[j-1])
	{
		Print_LCS(c,x,y,i-1,j-1);
		cout<<x[i-1]<<" ";
	}
	else if(c[i][j]==c[i-1][j])
		Print_LCS(c,x,y,i-1,j);
	else
		Print_LCS(c,x,y,i,j-1);
}
void print(const vector<vector<int>> & c)
{
	for(int i=0;i<c.size();i++)
	{
		for(int j=0;j<c[i].size();j++)
		{
			cout<<setw(3)<<left<<c[i][j]<<" ";
		}
		cout<<endl;
	}
	cout<<endl;
}
void print(const vector<char>&x )
{
	for(int i=0;i<x.size();i++)
	{
		cout<<x[i]<<" ";
	}
	cout<<endl;
}
int main(void)
{
	vector<vector<int>> c;
	vector<char> x = {'A','B','C','B','D','A','B'};
	vector<char> y = {'B','D','C','A','B','A'};
	//LCS_bottom_to_top(c,x,y);
	LCS_memorized(c,x,y);
	print(x);
	print(y);
	cout<<endl;
	print(c);
	cout<<"LCS:";
	Print_LCS(c,x,y,x.size(),y.size());
	return 0;	
}
			

对比动态规划法与备忘录法

  1. 方法比较
    动态规划法是自底向上,仅通过迭代就可以完成,观察C表的填充顺序,是从上至下从左至右依次填充。当填充完毕后可以得到任意位置(任意子序列)的LCS。
    备忘录法是自顶到底,递归地填充C表,仅会填充计算当前LCS会用到的项,所以当执行完毕后还有许多元素尚未填充,意味着只能得到部分子序列的LCS。

  2. 时间复杂度
    两种方法的时间复杂度都是O(mn),因为都需要维护C[m][n]的表。如果更细致地比较备忘录法需要填充的元素较少,填充过程的代价是O(max(m,n)),而动态规划法填充过程的代价是O(mn),但是实际上备忘录法初始化复杂度是O(mn),并且递归调用也会在常数项上增加时间代价。所以两种方法实际的执行速度差别不大。

  3. 空间复杂度:O(mn)

  • 4
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值