最长上升子序列

1、概念

       子串子序列的概念,以字符子串和字符子序列为例:

     (1)字符子串指的是字符串中连续的n个字符,如abcdefg中,ab,cde,fg等都属于它的字串。

     (2)字符子序列指的是字符串中不一定连续但先后顺序一致的n个字符,即可以去掉字符串中的部分字符,但不可改变其前后顺序。如abcdefg中,acdg,bdf属于它的子序列,而bac,dbfg则不是,因为它们与字符串的字符顺序不一致。

       最长上升子序列(Longest Increasing Subsequence),简称LIS,也有些情况求的是最长非降序子序列,二者区别就是序列中是否可以有相等的数。

       对于给定的一个序列(a1, a2, …, aN),可以从中得到一些上升的子序列(ai1, ai2, …, aiK),这里1 <= i1 < i2 < … < iK <= N,但必须按照从前到后的顺序。

       比如,对于序列(1, 7, 3, 5, 9, 4, 8),可以得到一些上升的子序列,如(1, 7, 9), (3, 4, 8), (1, 3, 5, 8)等等,而这些子序列中最长的(如子序列(1, 3, 5, 8) ),它的长度为4,因此该序列的最长上升子序列长度为4。

      子序列公共子序列以及最长公共子序列都不唯一。

      对于固定的数组,虽然LIS序列不一定唯一,但LIS的长度是唯一的。比如给出序列 ( 1, 7, 3, 5, 9, 4, 8),易得最长上升子序列长度为4,这是确定的,但序列可以为 ( 1, 3, 5, 8 ), 也可以为 ( 1, 3, 5, 9 )。

2、求解

        a、如何求解一个序列的最长上升子序列长度?

        动态规划的一个特点就是当前解可以由上一个阶段的解推出,由此,把我们要求的问题简化成一个更小的子问题。子问题具有相同的求解方式,只不过是规模小了而已。最长上升子序列就符合这一特性。我们要求n个数的最长上升子序列,可以求前n-1个数的最长上升子序列,再跟第n个数进行判断。求前n-1个数的最长上升子序列,可以通过求前n-2个数的最长上升子序列……直到求前1个数的最长上升子序列,此时LIS当然为1。

        求 2 7 1 5 6 4 3 8 9 的最长上升子序列:定义DP(i) (i∈[0,8])来表示前i个数以D[i]结尾的最长上升子序列长度。

 

       b、如何输出最长上升子序列?

       方法:我们在计算最长上升子序列的长度时,我们先记录最后一个且最靠后的最长上升子序列的位置,之后从这个位置由后向前遍历记录下每次最长上升子序列长度变化的位置i存入数组中,然后输出这个数组即可。

        以上面的例子为例:

3、代码验证

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

#define max(a,b) (a>b?a:b) 

int MyLIS(int * D, int Length)
{
	int _MaxLength = 0;
	int *_Dp = new int[Length];	//动态申请内存
	
	for (int i = 0; i < Length; i++)
	{
		_Dp[i] = 1;
	}
	int *_Pos = new int[Length];	//动态申请内存,倒序存储一个最长上升子序列的位置

	for (int i = 0; i < Length; i++)
	{
		for (int j = 0; j < i; j++)
		{
			if (D[i] > D[j])
			{
				_Dp[i] = max(_Dp[i], _Dp[j]+1);
				if (_Dp[i] >= _MaxLength)//_Pos[0]存储最后一个且最靠后的最长上升子序列的位置
				{
					_Pos[0] = i;
				}
				_MaxLength = max(_MaxLength, _Dp[i]);//第 i 项之前的最长子序列长度
			}
		}
	}
	cout << endl<< "Dp:" << endl;
	for (int i = 0; i < Length; i++)
	{
		cout << _Dp[i]<< " " ;
	}
	cout << endl;
	cout << "最长子序列长度:" << _MaxLength << endl;
	cout << "最后一个且最靠后的最长上升子序列的位置:" << _Pos[0] << endl;

	//输出一个最长子序列的倒序位置
	int count = 0;
	for (int i = _Pos[count]-1; i >= 0; i--)
	{
		if (_Dp[_Pos[count]] - 1 == _Dp[i])
		{
			count++;
			_Pos[count] = i;
			if (count >= _MaxLength)//最长上升子序列的最后一个元素位置
			{
				break;
			}
		}
	}
	cout << "输出最长上升子序列位置:"  << endl;
	for (int i = 0; i < _MaxLength; i++)//输出最长上升子序列位置
	{
		cout << _Pos[i] << " ";
	}
	cout << endl << "输出最长上升子序列:" << endl;
	for (int i = _MaxLength - 1; i >= 0; i--)//输出最长上升子序列
	{
		cout << D[_Pos[i]] << " ";
	}

	delete[] _Dp;//释放内存
	delete[] _Pos;//释放内存

	return _MaxLength;
}
int main()
{
	int D[20] = { 2 ,7 ,1 ,5 ,6 ,4 ,3 ,8 ,9 };
	int n=9;
	cout << "输入数据个数:"<<n<<endl<< "数据:" << endl;
	for (int i = 0; i < n; i++)
	{
		cout << D[i] << ' ';
	}
	MyLIS(D, n);
	return 0;
}

4、运行结果

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值