动态规划——最长上升子序列(LIS)

最长上升子序列(LIS)

问题背景:
有一个数列,从其中挑选一些数字但保证他们之间的前后关系不变组成一个新数列,那么称这个新的数列是原数列的一个子数列。
最长上升子序列问题就是保证跳出来的子序列是严格递增的,一般是求出这个子序列的长度。
这里我先给出一种暴力搜索的方法(没有任何优化):

#include<iostream>
#include<algorithm>
using namespace std;
int s, n, a[20], ans=0, f[100] = {0};
int dfs(int x)
{
	int s=0;
	for(int i = x+1;i <= n; i++)
	if(a[x] < a[i])
	s = max(s, dfs(i));
	s++;
	return s;
}
int main()
{
	cin >> n;
	for(int i = 1; i <= n; i++)
	cin >> a[i];
	for(int i = 1; i <= n; i++)
	ans = max (ans, dfs(i));
	cout << ans;
 } 

说明一下,这里的int s=0很重要,每次使用的s必须是新的,不然会对前面s有影响,出现最长只有2的情况。
这种方法是记录以每一个元素开头的最长上升子序列
这种方法太过于复杂,在搜索时有很多重复的地方
比如:1 2 3 4 5
第一遍搜索时我们目的是要找到以1位开头的最长上升子序列 就已经分别搜索过以1 2 3 4 5开头的最长上升子序列
到了第二遍搜索时,我们要找的是以2开头的最长上升子序列,这是在进行重复的操作。我们可以使用一种记忆化搜索的方法来记录我们已经搜索过以某元素开头的最长上升子序列。
把dfs函数改一下就可以得到:

#include<iostream>
#include<algorithm>
using namespace std;
int s, n, a[20], ans=0, f[100];
int dfs(int x)
{
	if(f[x] > 0)
	return f[x]; 
	for(int i = x + 1; i <= n; i++)
	if(a[x] < a[i])
	f[x] = max(f[x], dfs(i));
	f[x]++;
	return f[x];
}
int main()
{
	cin >> n;
	for(int i = 1; i <= n; i++)
	cin >> a[i];
	for(int i = 1; i <= n; i++)
	ans = max (ans, dfs(i));
	cout << ans;
 } 

f[x]就表示以第x个元素开头的最长上升子序列,这样就可以减少很多重复的操作。
下面介绍一下LIS的dp写法(虽然记忆化搜索也是dp)
我们这样定义f[i],f[i]表示以第i个元素为末尾的最长上升子序列长度,这样可以写成状态转移方程:
If(a[i] < a[j] && i < j)
f[j] = max(f[j], f[i] + 1)

先给出代码我们再分析:

#include<iostream>
#include<algorithm>
using namespace std;
int f[101], a[101];
int main()
{
	int n, ans = 0;
	cin >> n;
	for(int i = 1; i <= n; i++)
	cin >> a[i];
	for(int i = 1; i <= n; i++)
	{
		f[i] = 1;
		for(int j = 1; j < i; j++)
		{
			if(a[j] < a[i])
			f[i] = max(f[i], f[j] + 1);
		}
		ans = max(ans, f[i]);
	}
	cout <<"最长上升子序列长度:"<< ans;
	return 0;
 } 

这样说可能比较抽象,我们用具体的数据来分析一下:
比如一个数列 9 7 3 2 5 8 7 10
i = 1
f[1] = 1不用说
i = 2
因为9 > 7所以以第二个数7为终点的最长上升子序列是1
f[2] = 1
i = 3
与I = 2的情况是一样的

i = 5
进入转移状态
f[5] = max(f[5], f[4] +1) = max(1, 2) = 2

i = 7
先是j=3,4进入转移状态
f[7] = max(f[7], f[3,4] + 1 ) = 2
然后到j=5又进入转移状态
f[7] = max(f[7], f[5] + 1) = max(1, 2 + 1) = 3
这样是不是与记忆化搜索有点像,记录了f[5]供f[7]用。
这里的ans就是找出f[i]中最大的,即最长上升子序列,这样是不是很好理解了。
在这里我给出每一个f[i]的值:
第二行是输入的数据,第三行是对应以该节点为终点的最大上升子序列长度
第二行是输入的数据,第三行是对应以该节点为终点的最大上升子序列长度
注:此文章问原创,开源,可转载但请注明出处!

猜你喜欢

转载自blog.csdn.net/yezi_coder/article/details/103534006