【最长公共子序列】“动态规划”——《算法设计与分析(第五版)》


一、算法要求

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

1. 思路

构造最长公共子序列
由于每个数组单元的计算耗费O(1)时间,因此算法LCSLength耗时O(mn)。
由算法LCSLength计算得到的数组b可用于快速构造序列X = { x1,x2,…,xm }和Y = {y1, y2, …, yn}的最长公共子序列。

	首先从b[m][n]开始,依其值在数组b中搜索。
	当b[i][i] = 1时,表示X和Y的最长公共子序列是由Xi - 1和Yj - 1的最长公共子序列在尾部加上x1所得到的子序列。
	当b[i][i] = 2时,表示X和Y 的最长公共子序列与Xi - 1和Y; 的最长公共子序列相同。
	当b[i][i] = 3时,表示X和Y的最长公共子序列与X和Yj - 1的最长公共子序列相同。

2. 示例

在这里插入图片描述


二、完整代码

1. 主文件

main.cpp:

// Project2_1: 最长公共子序列问题
#include"Basic1.h"

int main() {
	cout << "The current comparison sequence is: \n";

	cout << "X: ";
	for (int i = 1; i < row; i++) {
		cout << setw(3) << X[i];
	}
	cout << "\nY: ";
	for (int i = 1; i < column; i++) {
		cout << setw(3) << Y[i];
	}
	cout << "\n\n";

	LCSplay(flatArray, column, row, X, Y);

}

2. 头文件

Basic1.h:

#pragma once
#ifndef __BASIC1__
#define __BASIC1__

#include<iostream>
#include<iomanip>
#include<cstdio>
#include<string>
using namespace std;

string X = "1ABCBDAB",
	   Y = "0BDCABA";

int flatArray[100][100],
	column = Y.length(),
	row = X.length();

//LCS计数及展示
void LCSplay(int f[][100], int capacityPack, int r, string x, string y)
{
	//防止数组越界,添加一下前导符


	for (int i = 1; i < column; i++) {
		for (int j = 1; j < row; j++) {
			if (Y[i] == X[j])
				flatArray[i][j] = flatArray[i - 1][j - 1] + 1;
			else
				flatArray[i][j] = max(flatArray[i - 1][j], flatArray[i][j - 1]);
		}
	}
	cout << "The length of the largest common subsequence is: "
		<< flatArray[column - 1][row - 1] << endl;

	//反演得出最小公共子序列的值
	cout << "The common subsequence is: ";
	int colInver = column - 1, rowInver = row - 1;
	while (colInver >= 1 && rowInver >= 1) {
		if (Y[colInver] == X[rowInver]) {
			cout << setw(3) << Y[colInver];
			colInver -= 1;
			rowInver -= 1;
		}
		else if (flatArray[colInver - 1][rowInver] >= flatArray[colInver][rowInver - 1]) {
			colInver -= 1;
		}
		else
			rowInver -= 1;
	}
}


// b[i][j]:记录c[i][j]的值是由哪一个子问题的解得到的;
// c[m][n]:记录问题的最优值,即X和Y的最长公共子序列的长度。
// 解决指向性的问题
void LCSLength(int m, int numStuff, char* x, char* y, int** capacityPack, int** b) {
	int i, j;
	//表格初始化
	for (i = 0; i <= m; i++)
		capacityPack[i][0] = 0;
	for (i = 0; i <= numStuff; i++)
		capacityPack[0][i] = 0;
	//判断数列规则(三向箭头的表示方法)
	for (i = 1; i <= m; i++) {
		for (j = 1; j <= numStuff; j++) {
			if (x[i] == y[j]) {
				capacityPack[i][j] = capacityPack[i - 1][j - 1] + 1;
				b[i][j] = 1;
			}
			else if (capacityPack[i - 1][j] >= capacityPack[i][j - 1]) {
				capacityPack[i][j] = capacityPack[i - 1][j];
				b[i][j] = 2;
			}
			else {
				capacityPack[i][j] = capacityPack[i][j - 1];
				b[i][j] = 3;
			}
		}
	}
}


void LCS(int i, int j, char* x, int** b) {
	if (i == 0 || j == 0)
		return;
	if (b[i][j] == 1) {
		LCS(i - 1, j - 1, x, b);
		cout << x[i];
	}
	else if (b[i][j] == 2)
		LCS(i - 1, j, x, b);
	else
		LCS(i, j - 1, x, b);
}

#endif



3. 效果展示

输入:
在这里插入图片描述

输出:
在这里插入图片描述


三、补充

LCS的最优子结构
设序列X={x1,x2,…,xm}和Y={y1,y2,…,yn}的最长公共子序列为Z={z1,z2,…,zk} ,则
(1)若xm=yn,则zk=xm=yn,且Zk-1是Xm-1和Yn-1的最长公共子序列。
(2)若xm≠yn且zk≠xm,则Z是Xm-1和Y的最长公共子序列。
(3)若xm≠yn且zk≠yn,则Z是X和Yn-1的最长公共子序列。
其中,Xm-1={x1,x2,…,xm-1};Yn-1={y1,y2,…,yn-1};Zk-1={z1,z2,…,zk-1}

在算法lcsLength和lcs中,可进一步将数组b省去。
事实上,数组元素c[i][j]的值仅由c[i-1][j-1],c[i-1][j]和c[i][j-1]这3个数组元素的值所确定。
对于给定的数组元素c[i][j],可以不借助于数组b, 而仅借助于c本身在时间内确定c[i][j]的值是由c[i-1][j-1],c[i-1][j]和c[i][j-1]中哪一个值所确定的。
如果只需要计算最长公共子序列的长度,则算法的空间需求可大大减少。
事实上,在计算c[i][j]时,只用到数组c的第i行和第i-1行。因此,用2行的数组空间就可以计算出最长公共子序列的长度。进一步的分析还可将空间需求减至O(min(m,n))。

文档供本人学习笔记使用,仅供参考。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

NI'CE'XIAN

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值