11动态规划——最长公共子序列LCS问题

11动态规划——最长公共子序列LCS问题

1.问题

DEF 1:

设X, Y是两个序列,其中 X = <x1, x2, … ,xn>, Z = <z1, z2, … ,zs>. 如果存在X的元素构成的按下标严格递增序列 <xi1, xi2, … ,xik>, 使 xij = zj, j = 1, 2, …, k ,那么称 Z 是 X 的子序列,Z 含有的元素个数 k,称为子序列的长度

DEF 2:

设 X 和 Y 是两个序列,如果 Z 既是 X 的子序列,又是 Y 的子序列,则称 Z 是 X 和 Y 的公共子序列

  • 实例:序列 X=<A,B,D,C,A>, Y=<C,D,B,A,C>,
  • 则 <A,C>,<B,C>,<B,A>等都是X和Y的公共子序列。

最长公共子序列问题:

给定序列 X = <x1, x2, … ,xn>,Y = <y1, y2, … ,ym>,基于上面两个定义,求最长公共子序列(Longest Common Subsequence, LCS)。

2.解析

Xi = <x1, x2, … ,xn>,i = 1, 2, …, n;
Yj = <y1, y2, … ,ym>,j = 1, 2, …, m;
Zk = <z1, z2, … ,zs>,k = 1, 2, …, s.

如果 Zk 是 Xi 和 Yj 的最长公共子序列,则满足如下优化法则:

  1. xi = yj,那么zk = xi = yj, Zk-1 是 Xi-1 和 Yj-1 的最长公共子序列。
  2. xi ≠ yj, zk ≠ xi,那么zk 是 Xi-1 和 Yj 的最长公共子序列。
  3. xi ≠ yj, zk ≠ yj,那么zk 是 Xi 和 Yj-1 的最长公共子序列。

如果 C[i,j] = a 表示 Xi 和 Yj 的最长公共子序列长度为 a, 则可以写出递推公式:

在这里插入图片描述
Z 的生成过程具化为 C[i,j] 的求值过程。

举个栗子

X = <A, B, B, D, A>,Y = <B, C, C, D, A, C>. n=[0, 5], m=[0, 6],求 X 和 Y 的最长公共子序列。

初始化(边界条件):

在这里插入图片描述

X = <A, B, B, D, A>,Y = <B, C, C, D, A, C>.
一、计算最长公共子序列长度算法

  1. i = 1
    a) j = 1,X.A <> Y.B:C[1, 1] = max(C[1, 0], C[0, 1]) = max(0, 0) = 0, 删除y;
    b) j = 2,X.A <> Y.C:C[1, 2] = max(C[1, 1], C[0, 2]) = max(0, 0) = 0, 删除y;
    c) j = 3,X.A <> Y.C:C[1, 3] = max(C[1, 2], C[0, 3]) = max(0, 0) = 0, 删除y;
    d) j = 4,X.A <> Y.D:C[1, 4] = max(C[1, 3], C[0, 4]) = max(0, 0) = 0, 删除y;
    e) j = 5,X.A == Y.A:C[1, 5] = C[0, 4] + 1 = 1, 删除 x 和 y;
    f ) j = 6,X.A <> Y.C:C[1, 6] = max(C[1, 5], C[0, 6]) = max(1, 0) = 1, 删除y.
    注:<> 表示不等于。

  2. i = 2
    a) j = 1,X.B == Y.B:C[2, 1] = C[1, 0] + 1 = 1, 删除 x 和 y;
    b) j = 2,X.B <> Y.C:C[2, 2] = max(C[2, 1], C[1, 2]) = max(1, 0) = 1, 删除y;
    c) j = 3,X.B <> Y.C:C[2, 3] = max(C[2, 2], C[1, 3]) = max(1, 0) = 1, 删除y;
    d) j = 4,X.B <> Y.D:C[2, 4] = max(C[2, 3], C[1, 4]) = max(1, 0) = 1, 删除y;
    e) j = 5,X.B <> Y.A:C[2, 5] = max(C[2, 4], C[1, 5]) = max(1, 1) = 1, 删除y;
    f ) j = 6,X.B <> Y.C:C[2, 6] = max(C[2, 5], C[1, 6]) = max(1, 1) = 1, 删除y.
    注:若max()中的两个值相等,都先选择删除y

  3. i = 3
    a) j = 1,X.B == Y.B:C[3, 1] = C[2, 0] + 1 = 1, 删除 x 和 y;
    b) j = 2,X.B <> Y.C:C[3, 2] = max(C[3, 1], C[2, 2]) = max(1, 1) = 1, 删除y;
    c) j = 3,X.B <> Y.C:C[3, 3] = max(C[3, 2], C[2, 3]) = max(1, 1) = 1, 删除y;
    d) j = 4,X.B <> Y.D:C[3, 4] = max(C[3, 3], C[2, 4]) = max(1, 1) = 1, 删除y;
    e) j = 5,X.B <> Y.A:C[3, 5] = max(C[3, 4], C[2, 5]) = max(1, 1) = 1, 删除y;
    f ) j = 6,X.B <> Y.C:C[3, 6] = max(C[3, 5], C[2, 6]) = max(1, 1) = 1, 删除y.

  4. i = 4
    a) j = 1,X.D <> Y.B:C[4, 1] = max(C[4, 0], C[3, 1]) = max(0, 1) = 1, 删除x;
    b) j = 2,X.D <> Y.C:C[4, 2] = max(C[4, 1], C[3, 2]) = max(1, 1) = 1, 删除y;
    c) j = 3,X.D <> Y.C:C[4, 3] = max(C[4, 2], C[3, 3]) = max(1, 1) = 1, 删除y;
    d) j = 4,X.D == Y.D:C[4, 4] = C[3, 3] + 1 = 2, 删除 x 和 y;
    e) j = 5,X.D <> Y.A:C[4, 5] = max(C[4, 4], C[3, 5]) = max(2, 1) = 2, 删除y;
    f ) j = 6,X.D <> Y.C:C[4, 6] = max(C[4, 5], C[3, 6]) = max(2, 1) = 2, 删除y.

  5. i = 5
    a) j = 1,X.A <> Y.B:C[5, 1] = max(C[5, 0], C[4, 1]) = max(0, 1) = 1, 删除x;
    b) j = 2,X.A <> Y.C:C[5, 2] = max(C[5, 1], C[4, 2]) = max(1, 1) = 2, 删除y;
    c) j = 3,X.A <> Y.C:C[5, 3] = max(C[5, 2], C[4, 3]) = max(2, 1) = 2, 删除y;
    d) j = 4,X.A <> Y.D:C[5, 4] = max(C[5, 3], C[4, 4]) = max(2, 2) = 2, 删除y;
    e) j = 5,X.A == Y.A:C[5, 5] = C[4, 4] + 1 = 3, 删除 x 和 y;
    f ) j = 6,X.A <> Y.C:C[5, 6] = max(C[5, 5], C[4, 6]) = max(3, 2) = 3, 删除y.

于是得到C[i, j]表:
在这里插入图片描述
此外,从C[5, 6] 开始,“删除 x” 对应表格向上移一格,“删除y”对应表格向左移一格,“删除x和y”对应表格向左移一格再向上移一格,我们得到记录删除对象的B[i, j]表:

在这里插入图片描述

二、输出最长子序列元素算法
X = <A, B, B, D, A>,Y = <B, C, C, D, A, C>.

查表得:

  1. X = 5,Y = 6
    B[5, 6] “2:删除y”
    X = <A, B, B, D, A> => X = <A, B, B, D, A>
    Y = <B, C, C, D, A, C> => Y = <B, C, C, D, A>

  2. X = 5,Y = 5
    B[5, 5] “3:删除x、y”
    X = <A, B, B, D, A> => X = <A, B, B, D>
    Y = <B, C, C, D, A> => Y = <B, C, C, D>
    入栈 A

  3. X = 4,Y = 4
    B[4, 4] “3:删除x、y”
    X = <A, B, B, D> => X = <A, B, B>
    Y = <B, C, C, D> => Y = <B, C, C>
    入栈 D

  4. X = 3,Y = 3
    B[3, 3] “2:删除y”
    X = <A, B, B> => X = <A, B, B>
    Y = <B, C, C> => Y = <B, C>

  5. X = 3,Y = 2
    B[3, 2] “2:删除y”
    X = <A, B, B> => X = <A, B, B>
    Y = <B, C> => Y = < B >

  6. X = 3,Y = 1
    B[3, 1] “2:删除x、y”
    X = <A, B, B> => X = <A, B>
    Y = < B > => Y = < >
    入栈 B

至此,序列Y已无元素,输出最长子序列元素算法结束。

再通过出栈依次打印Z元素,即在控制台上输出 X 和 Y 的最长公共子序列 Z <B, D, A>.

由于当 X.xi <> Y.yj ,且 C[i, j-1] == C[i-1, j] 时,该算法总是选择优先删除y,也就是将 C[i, j-1] 的值赋给 C[i, j],所以总会得到一个确定的输出序列。但事实上,选择删除y还是删除x都是允许的,这表明最长公共子序列不一定唯一(元素不同),该算法最多输出了其中的一种可能。但我们必须清楚,最长公共子序列的长度是唯一确定的。

3.设计

//计算最长公共子序列长度算法
C[0,j]=C[i,0]=0, 1<=i<=n,1<=j<=m
for i=1 to n do
	for j=1 to m do
		if xi=yj then
			C[i,j] ← C[i-1,j-1]+1;
			B[i,j]3;//删除x、y
		else then
			if C[i,j-1]>=C[i-1,j] then
				C[i,j] ← C[i,j-1];
				B[i,j]2;//删除y
			else then
				C[i,j] ← C[i-1,j];
				B[i,j]1;//删除x
	end for j
end for i

//f(B,i,j)输出最长子序列元素算法
if i=0 or j=0 then
	return;
if B[i,j]=3 then //删除两个
	输出Xi;//或者入栈
	f(B,i-1,j-1);
else if B[i,j]=2 then //删除y
	f(B,i,j-1);
else then 
	f(B,i-1,j); //删除x

4.分析

证明动态规划的优化法则:

假设Z1序列任一个子序列 C1 本身一定不是相对于子序列的初始和结束状态的最优
决策序列。
那么根据假设,必然存在另外一个子序列 C2 优于一个子序列 C1,并且两个子序列相对于子序列的初始和结束状态一致。
将 Z1 中原子序列 C1 替换为 C2,获得 Z2,因为 C2 优于 C1,必然 Z2 优于 Z1,这与 Z1是最优决策序列矛盾(已知),因此假设错误,得证。

双重循环,LCS()=O(nm).

5.源码

点击前往Git查看

#include<iostream>
#include<cstdlib>
#include<stack>

#define N 7//N=序列X的长度+2
#define M 8//M=序列Y的长度+2

using namespace std;

char sequence_X[N] = "ABBDA";//X序列
char sequence_Y[M] = "BCCDAC";//Y序列

//C[1][2]表示X长度为1、Y长度为2时两者最长公共子序列长度
int length_C[N][M] = { 0 };

//三个结果:1表示删除x,2表示y,3表示两者都删
//B[1][2]=1 表示X长度为1、Y长度为2时得到两者最长公共子序列长度,
//要从前一步从右往左删除一个x元素
int info_B[N][M] = { 0 };

//栈,保存最长公共子序列元素,以便正向输出
stack<char> sequence_R;

//最长公共子序列长度算法
void LCS() {

	for (int i = 1; i < N-1; i++) {
		for (int j = 1; j < M-1; j++) {
			//字符相同
			if (sequence_X[i-1] == sequence_Y[j-1]) {
				//都删
				length_C[i][j] = length_C[i - 1][j - 1] + 1;
				info_B[i][j] = 3;
			}
			//字符不同
			else {
				if (length_C[i][j - 1] > length_C[i - 1][j]) {
					//删Y
					length_C[i][j] = length_C[i][j - 1];
					info_B[i][j] = 2;
				}
				else {
					//删X
					length_C[i][j] = length_C[i - 1][j];
					info_B[i][j] = 1;
				}
			}
		}
	}

}

//元素保存到栈
void collectInfo(int start,int end) {
	if (start == 0 || end == 0) {
		return;
	}
	//都删,X和Y各减一
	if (info_B[start][end] == 3) {
		//cout << sequence_X[start - 1]<<" ";
		//按子序列从后往前输出,先将输出结果保存在栈sequence_R中
		sequence_R.push(sequence_X[start - 1]);
		collectInfo(start - 1, end-1);
	}
	//删X,X减一
	else if (info_B[start][end] == 1) {
		collectInfo(start - 1, end);
	}
	//删Y,Y减一
	else {
		collectInfo(start , end-1);
	}
}

//从前往后输出最长公共子序列,即出栈
void printSequence() {
	int num = sequence_R.size();
	cout << "序列X为:" << sequence_X << endl;
	cout << "序列Y为:" << sequence_Y << endl;
	cout << "X与Y的最长公共子序列为:";
	for (int i = 0; i < num; i++) {
		cout << sequence_R.top();
		sequence_R.pop();
	}
	cout << endl;
}

int main() {
	LCS();
	collectInfo(N-2,M-2);//N=序列X的长度+2,M=序列Y的长度+2
	printSequence();
	return 0;
}

在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值