动态规划法解最长升序子序列的分析与C++实现

说明:仅仅分享思想和代码设计方法,代码是用于验证和对照(对照是为了方便你检查你写的哪里有问题),我强烈建议你自己动手实现,我明确反对你直接用我的代码去写报告或其它目的。

补充说明:基于了电路布线的思想,可参考电路布线。

  1. 最长升序子序列问题

1.1原问题的解:对于一个N个数的升序子序列,假设它为集合B,那B里的元素应该满足是升序的且属于N,且不存在长度比B更长的序列。

1.2 算法:因为要求最长升序子序列,既要找一个子序列及其在序列中的位置,采用动态规划的算法。空间换取时间,但不能明换。必须先对序列进行一个排序找出其相对大小。目的是减少空间的消耗。

假定用size(i,j)进行动态规划。i代表当前出发位置,也表示当前已经最大升序子序列已经考虑到的位置。j代表考虑序列规模。它的规模肯是小于原序列的。也就是size(i,j)只看序列前j个位置,然后从第0个位置到第i个位置所加入的最大升序子序列的边数。

我们的目标就是判断第i个位置的元素能否加入子序列,因为自底向下,当我们考虑到size(n,n)时规模最大,所有位置也都判断了,再回溯原问题的解就出来了。

将序列分为如图两部分,刚开始从位置0出发,根据0在原序列中的排名,因为是升序的,找寻数组,所以找如果它的排名比位置0大,就对相应位置+1。表示只考虑到第0个位置时,问题规模j所能找到的最大子序列长度。接着考虑第一个元素,判断第一个元素相对大小,对于第一个元素前面的元素,size(i,j)=size(i-1,j),因为i对应的排名位置在这后面,所以考虑这个元素时,和考虑上一个元素时,结果是一样的。对于第1个元素排名后面的元素,因为规模已经大到,可以包含这个元素了,这时候要考虑一个问题,这个元素到底能否加入已找到最大升序子序列中,因为规模已经可以包括它了,所以这时候就要假设它加入和它不加入。

如果它加入则后面,则对于当前规模j来说,你需要考虑的时如果它前面有元素,且排名在它后面,且已经在最大升序子序列里的情况。它跟当前元素不兼容,怎么办。很简单,如果冲突的只有一个元素,表示冲突那个元素可以和当前元素二择一,因为最大升序

子序列解不止一个。如果前面有多个元素已经加入当前规模的升序子序列,那么这个元素铁定是不能加入的,因为如果加入,前面的就不能加入了,最大升序子序列就变短了。

最后将名次排名映射回原序列得到结果。

回溯:由size矩阵开始回溯。回溯的过程就是从原序列自右向左找最大升序子序列的过程。先找到最右边第一条加入的数。判断的方法是size(i-1,j),size(i,j)不一样,这说明在同等问题规模下,i这条边是构成最大升序子序列的一个可行解,接着由于你找到了i这条边,你就必须找i这条边名次左边的位置,因为右边的位置那个数,必然就和这个数冲突了。冲突是有可能加入最大升序子序列的,因为可能当前冲突序列是1,即二择一的情况。但比较难找,所以采用优先选择最右边的数的方法。

1.3分析

解决这个问题有一些贪心算法,或者回溯法的解法。但从动态规划这个角度来说,

这个算法从本质上来说和课本的电路布线没有区别,都是用动态规划的方法求一个子集。然后这个子集是升序的问题。现在以升序数组的重新描述了这个算法。复杂度主要体现在两部分。一部分是对原序列排序找相对大小,这个方法的复杂度和简单排序是一样的复杂度渐进确界O(N^2)。然后是找最长升序子序列的问题。这从数列第一个开始找起,每次都与更新size的一行。每次更行一行用时位O(N),总复杂度位O(N^2)。回溯:操作size矩阵复杂度位O(N^2),映射复杂度为O(N),故算法的总时间复杂度为O(N^2).

1.4实现(采用一个规模为20的数列验证)

void msn()
{
	cout << "所测试数据为规模大小为20(为方便观察,还是认为限制随机数为1到1000)" << endl;
	std::srand(time(NULL));
	clock_t star, end;
	int length, *c, **size, max, dida,*newc,rank;
	stack<int> result,resultpos;
	length = 20;
	c = new int[length + 1];
	newc = new int[length + 1];
	size = new int*[length + 1];
	for (int i = 1; i <= length; ++i)
		size[i] = new int[length + 1];
	cout << "原序列为:"  << endl;
	for (int i = 1; i < length + 1; ++i)
	{
		c[i] = rand()%1000+1 ;
		cout << c[i] << "  ";
	}
	cout << endl;
	for (int i = 1; i < length + 1; ++i)
	{
		rank = 1;
		for (int j = 1; j < length + 1; ++j)
		{
			if (c[j] < c[i])
				++rank;
		}
		for (int j = i+1; j < i; ++j)
		{
			if (c[j] < c[i])
				++rank;
		}
		newc[i] = rank;
	}
	cout << "排名后序列为:" << endl;
	for (int i = 1; i < length + 1; ++i)
	{
		cout << newc[i] << "  ";
	}
	for (int i = 1; i < newc[1]; ++i)
		size[1][i] = 0;
	for (int i = newc[1]; i <= length; ++i)
		size[1][i] = 1;
	for (int i = 2; i <= length; ++i)
	{
		for (int j = 1; j < newc[i]; ++j)
		{
			size[i][j] = size[i - 1][j];
		}
		for (int j = newc[i]; j <= length; ++j)
		{
			size[i][j] = size[i - 1][j] < (size[i - 1][newc[i] - 1] + 1) ? (size[i - 1][newc[i] - 1] + 1) : size[i - 1][j];
		}
	}
	cout << endl;
	cout << "size矩阵值为:" << endl;
	for (int i = 1; i <= length; ++i)
	{
		for (int j = 1; j <= length; ++j)
			cout << size[i][j] << ' ';
		cout << endl;
	}
	max = length;
	for (int i = length; i > 1; --i)
	{
		if (size[i][max] != size[i - 1][max])
		{
			max = newc[i] - 1;
			result.push(c[i]);
			resultpos.push(i);
		}
		//newcout << max << endl;
	}
	if (max >= newc[1])
	{
		result.push(c[1]);
		resultpos.push(1);
	}
	cout << "最长升序子序列为:";
	while (!result.empty())
	{
		cout << result.top() << ' ';
		result.pop();
	}
	cout << "对应下标为:";
	while (!resultpos.empty())
	{
		cout << resultpos.top() << ' ';
		resultpos.pop();
	}
	cout << endl;
	delete[]c;
	for (int i = 1; i <= length; ++i)
		delete[]size[i];
	delete[]size;
}
int egg(int n, int k)
{
	if (n == 1)
		return k;
	if (k == 0)
		return 0;
	return egg(n - 1, k - 1) + egg(n, k - 1) + 1;
}
int eggs(int n, int k)
{
	if (n == 1&&k!=0)
	{
		if (find(sss.begin(), sss.end(), k) == sss.end())
		{
			sss.push_back(k);
			a += k + 1;
			if (a < 50)
				cout << a << ' ';
		}
		return k;
	}
	if (k == 0)
		return 0;
	return eggs(n - 1, k - 1) + eggs(n, k - 1) + 1;
}
int main()
{
	msn();
}

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值