动态规划之最长公共子序列

问题分析:

求两个数组的公共子序列。

考虑最长公共子序列问题如何分解成子问题,设A=“a0,a1,…,am-1”,B=“b0,b1,…,bm-1”,并Z=“z0,z1,…,zk-1”为它们的最长公共子序列。不难证明有以下性质:

(1) 如果am-1=bn-1,则zk-1=am-1=bn-1,且“z0,z1,…,zk-2”是“a0,a1,…,am-2”和“b0,b1,…,bn-2”的一个最长公共子序列;

(2) 如果am-1!=bn-1,则若zk-1!=am-1,蕴涵“z0,z1,…,zk-1”是“a0,a1,…,am-2”和“b0,b1,…,bn-1”的一个最长公共子序列;

(3) 如果am-1!=bn-1,则若zk-1!=bn-1,蕴涵“z0,z1,…,zk-1”是“a0,a1,…,am-1”和“b0,b1,…,bn-2”的一个最长公共子序列。

这样,在找A和B的公共子序列时,如有am-1=bn-1,则进一步解决一个子问题,找“a0,a1,…,am-2”和“b0,b1,…,bm-2”的一个最长公共子序列;如果am-1!=bn-1,则要解决两个子问题,找出“a0,a1,…,am-2”和“b0,b1,…,bn-1”的一个最长公共子序列和找出“a0,a1,…,am-1”和“b0,b1,…,bn-2”的一个最长公共子序列,再取两者中较长者作为A和B的最长公共子序列。

求解:

引进一个二维数组c[][],用c[i][j]记录X[i]与Y[j] 的LCS 的长度,b[i][j]记录c[i][j]是通过哪一个子问题的值求得的,以决定搜索的方向。
我们是自底向上进行递推计算,那么在计算c[i,j]之前,c[i-1][j-1],c[i-1][j]与c[i][j-1]均已计算出来。此时我们根据X[i] = Y[j]还是X[i] != Y[j],就可以计算出c[i][j]。

问题的递归式写成:


recursive formula

回溯输出最长公共子序列过程:

flow

代码如下:

共有三种回溯输出公共自序列的方法。

//动态规划题目
//最长公共子序列
#include<stdio.h>
#include<malloc.h>
#include<assert.h>

void Fun(int *p, int *q, int **r, int lengthp, int lengthq);
int main()
{
	int lengthp;
	int lengthq;
	int *p, *q;
	int **r;  //保存最长公共子序列的长度
	int i;
	printf("请输入两个序列的长度\n");
	scanf("%d %d", &lengthp, &lengthq);
	
	p = (int *)malloc(sizeof(int) * lengthp);
	assert(p != NULL);

	printf("输入序列p的值\n");
	for(i = 0; i < lengthp; i++)
		scanf("%d", p+i);
	
	q = (int *)malloc(sizeof(int) * lengthq);
	assert(q != NULL);

	printf("输入序列q的值\n");
	for(i = 0; i < lengthq; ++i)
		scanf("%d", q+i);

	r = (int **)malloc(sizeof(int *) * (lengthp+1));
	assert(r != NULL);
	for(i = 0; i < lengthp+1; ++i)
	{
		r[i] = (int *)malloc(sizeof(int) * (lengthq+1));
		assert(r[i] != NULL);
	}
	Fun(p, q, r, lengthp, lengthq);
	free(p);
	free(q);
	for(i = 0; i < lengthp; i++)
		free(r[i]);
	return 0;
}

void Fun(int *p, int *q, int **r, int lengthp, int lengthq)
{
	void Print(int *p, int *q, int **Res, int lengthp, int lengthq);
	void Print1(int *p, int *q, int **Res, int lengthp, int lengthq, int max);
	void Print2(int *p, int *q, int **Res, int lengthp, int lengthq, int max);
	int **Res; //保存子序列的输出方向
	int i, j;
	int max = 0; //最长公共子序列的长度
	Res = (int **)malloc(sizeof(int *) * (lengthp+1));
	assert(Res != NULL);
	for(i = 0; i < lengthp+1; ++i)
	{
		Res[i] = (int *)malloc(sizeof(int) * (lengthq+1));
		assert(Res[i] != NULL);
	}

	for(i = 0; i < lengthp+1; ++i)
		for(j = 0; j < lengthq+1; ++j)
			Res[i][j] = 0;

	for(i = 0; i < lengthp+1; ++i)
		r[i][0] = 0;
	for(i = 0; i < lengthq+1; ++i)
		r[0][i] = 0;

	for(i = 1; i < lengthp+1; ++i)
	{
		for(j = 1; j < lengthq + 1; ++j)
		{
			if(p[i-1] == q[j-1])
			{
				r[i][j] = r[i-1][j-1] + 1;
				Res[i][j] = 0; //搜索左上角
			}
			else if(r[i-1][j] > r[i][j-1])
			{
				r[i][j] = r[i-1][j];
				Res[i][j] = -1;//上方
			}
			else
			{
				r[i][j] = r[i][j-1];
				Res[i][j] = 1;//左面
			}
		}
	}

	for(i = 0; i < lengthp+1; ++i) //观察结果
	{
		for(j =0; j < lengthq+1; ++j)
			printf("%d ", r[i][j]);
		printf("\n");
	}
	printf("\n");

	for(i = 0; i < lengthp+1; ++i)//观察结果
	{
		for(j =0; j < lengthq+1; ++j)
			printf("%2d ", Res[i][j]);
		printf("\n");
	}
	printf("\n");
		

	for(i = 0; i < lengthp+1; ++i)
		for(j = 0; j < lengthq+1; ++j)
			if(max < r[i][j])
				max = r[i][j];
	printf("最长公共子序列的长度为%d ", max);
	//Print(p, q, Res, lengthp, lengthq);
	//Print1(p, q, r, lengthp, lengthq, max);
	Print2(p, q, Res, lengthp, lengthq, max);

	for(i =0; i < lengthp+1; ++i)
		free(Res[i]);
}

void Print(int *p, int *q, int **Res, int lengthp, int lengthq)
{
	if(lengthp < 1 || lengthq < 1)
		return;
	if(Res[lengthp][lengthq] == 0)
	{
		Print(p, q, Res, lengthp-1, lengthq-1);
		assert(p[lengthp-1] == q[lengthq-1]);
		printf("%d ", p[lengthp-1]);
	}
	else if(Res[lengthp][lengthq] == -1)
	{
		Print(p, q, Res, lengthp-1, lengthq);
	}
	else if(Res[lengthp][lengthq] == 1)
	{
		Print(p, q, Res, lengthp, lengthq-1);
	}
}

void Print1(int *p, int *q, int **Res, int lengthp, int lengthq, int max)
{
	int *temp;
	int i, j;
	int m = max;
	temp = (int *)malloc(sizeof(int) * m);
	assert(p != NULL);
	i = lengthp;
	j = lengthq;
	while(i >0 && j > 0 && m >= 0)
	{
		if(p[i-1] == q[j-1])
		{
			temp[m-1] = p[i-1];
			i--;
			j--;
			m--;
		}
		else if(Res[i-1][j] > Res[i][j-1])
		{
			i--;
		}
		else 
		{
			j--;
		}
	}
	for(i = 0; i < max; ++i)
		printf("%d ", temp[i]);
	free(temp);
} 

void Print2(int *p, int *q, int **Res, int lengthp, int lengthq, int max)
{
		int *temp;
	int i, j;
	int m = max;
	temp = (int *)malloc(sizeof(int) * m);
	assert(p != NULL);
	i = lengthp;
	j = lengthq;
	while(i >0 && j > 0 && m >= 0)
	{
		if(Res[i][j] == 0 && p[i-1] == q[j-1])
		{
			temp[m-1] = p[i-1];
			i--;
			j--;
			m--;
		}
		else if(Res[i][j] == -1)
		{
			i--;
		}
		else if(Res[i][j] == 1)
		{
			j--;
		}
	}
	for(i = 0; i < max; ++i)
		printf("%d ", temp[i]);
	free(temp);
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值