zoukankan      html  css  js  c++  java
  • 动态规划--最长单调子序列问题

    1.问题描述:

    求一个正整数序列的最长单调自增子序列,子序列不要求是连续的。例如

    Input:5

    5 2 4 3 1

    Output:2

    2. 算法复杂度是O(N*N)

    确定状态转移方程,设f[i]是以a[i]为结尾的最大值的子序列的长度,那么[max { f[i]} ]的最大值就是要的结果。

    所以转移方程为:

    [f(i) = max { f(x)|x < i,{a_i} > {a_x}}  + 1]

    所以代码可以为:

    void main(void)
    {
    	int arr[] = {10,4,20,10,15,13};
    	int dist[6];
    	int path[6];
    	int num = 6;
    	int i = 0;
    
    	for(i = 0; i < 6; i++)
    	{
    		dist[i] = 0;
    		//-1表示前面没有元素了,用于回头求解的时候定界
    		path[i] = -1;
    	}
    
    	for(i = 0; i < num; i++)
    	{
    		int temp = 0;
    		int index = -1;
    
    		for(int j = i - 1; j >= 0; j--)
    		{
    			if(arr[i] > arr[j] && dist[j] > temp)
    			{
    				temp = dist[j];
    				index = j;
    			}//if
    		}//for
    
    		dist[i] = temp + 1;
    		path[i] = index;
    
    	}//for
    
    	//找到最大的那个值
    	int max = 0;
    	int maxIndex = -1;
    	for(int m = 0; m < num; m++)
    	{
    		if(dist[m] > max)
    		{
    			max = dist[m];
    			maxIndex = m;
    		}
    	}//for
    	printf("最长单曾子序列的长度是%d.
    ", max);
    
    	while(path[maxIndex] != -1)
    	{
    		printf("%d->", arr[maxIndex]);
    		maxIndex = path[maxIndex];
    	}//while
    	printf("%d
    ", arr[maxIndex]);
    
    }
    

      很显然时间复杂度是O(N*N),那么有没有更快的算法呢?按照正常的思路更快的复杂度应该就是O(N*logN),那么就要涉及到二分了。

    3. 算法复杂度是O(N*logN)

    建立一个辅助数组c[n],c[i]=j存储的是子序列长度为i的序列最后一个值j(实际上子序列长度为i的子序列有多个,要的是子序列最后一个值最小的)。

    这时要遍历要处理的数组arr[n]。

    for(i=0;i<n;i++)
    {
      j=find(c,n+1,arr[i]);//find是一个二分查找
      c[j]=arr[i];
      dist[i]=j;
    }

    请看一下上面的例子实际执行的情况:C数组变化的情况

    -1 5
    
    -1 2
    
    -1 2 4
    
    -1 1 2
    
    -1 1 4
    
    -1 1 3

    arr数组遍历是从前往后的,处理arr[i-1]时arr[i]以及后面的值肯定还没有处理,前面的值都处理过了,看c数组,每个arr数组中的值和c数组中值进行比较,找到合适的位置插入(若插入到c数组的末尾,那么就属于最长递增子序列长度加1,实际上c数组的长度就是最后的最长单调递增子序列的长度。),否则这就替换掉了c数组中原来位置存储的值,这种替换是有意义的,主要是为了后来的arr数组中的值计算dist用(dist[i]中保存的是以arr[i]为最后一个元素的最长单调递增子序列。)好处是若arr[i] <arr[j],dist[i]=dist[j],那么在c中肯定要保存arr[i]呀!!(注意c数组的下标代表的是子序列的长度,c数组中的值也是按递增顺序排列的。这才可能用二分呢,亲)。和O(N*N)的主要区别就是巧妙的借用了c数组,本题的关键就是理解c数组的意义。可以手动模拟一下算法执行的步骤,重要模拟c和b数组的变化情况。c数组是以-1位开头的有序的递增数列。

    下面给出完整的算法

    #include <stdio.h>
    
    #define MAX 100
    void fill(int a[], int len);
    int find(int a[], int cLastIndex, int x);
    
    int main()
    {
    	int arr[MAX] = {0};
    	int dist[MAX] = {0};
    	int c[MAX];
    	int num;	//原始数字序列的长度
    
    	int i = 0;
    
    	freopen("in.txt", "r", stdin);
    	freopen("out.txt", "w", stdout);
    
    	printf("读取数字序列长度...
    ");
    	scanf("%d", &num);
    	printf("读取数字序列...
    ");
    	for(i = 0; i < num; i++)
    	{
    		scanf("%d", &arr[i]);
    	}//for
    
    	//初始化c数组,长度是arr数组+1
    	fill(c, num + 1);
    
    	c[0] = -1;	//使得递增数列的最小值为-1
    
    	
    	for(i = 0; i < num; i++)
    	{
    		int j = find(c, i + 1, arr[i]);
    		c[j] = arr[i];
    		dist[i] = j;
    	}//for
    
    	//c数组中保存的就是结果的数字序列,打印一下
    	i = 1;
    	while(c[i] != 100)
    	{
    		printf("%d ", c[i]);
    		i++;
    	}
    
    
    	fclose(stdin);
    	fclose(stdout);
    	return 0;
    
    }
    
    void fill(int a[], int len)
    {
    	for(int i = 0; i < len; i++)
    	{
    		a[i] = 100;	////这就是一个初始化,无所谓,目的就是是这个数列递增
    	}
    }
    
    //在数组a中,寻找数字x
    //如果存在返回其下表,如果不存在返回其该插入的位置,会覆盖掉原来在该位置的数字
    int find(int a[], int cLastIndex, int x)
    {
    	int left = 0;
    	int right = cLastIndex;
    	int mid = (left + right) / 2;
    
    	while(left <= right)
    	{
    		if(x > a[mid])
    		{
    			left = mid + 1;
    		}
    		else if(x < a[mid])
    		{
    			right = mid - 1;
    		}
    		else
    		{
    			return mid;
    		}
    
    		mid = (left + right) / 2;
    	}//while
    
    	return left;
    }//find()
    

      通过上面的代码可以看到,其实dist数组根本没有用到,只用c数组就能求解问题,其实这种算法已经不是动态规划了,虽然它的时间复杂度比动态规划的要好。这个算法的根本思想就是想到一个c数组,这个有序递增的数组用来存储最长的子序列。确实是一种很不错的手法。

    测试数据:

    6
    10 4 20 10 15 13

    运行结果:

    读取数字序列长度...
    读取数字序列...
    4 10 13 
  • 相关阅读:
    tomcat安装
    卸载重安firefox
    Metasploit笔记之信息收集命令
    postgresql-9.0.18-1-linux.run启动
    ubuntu 安装自启动管理
    MySQL数据库”mysql SQL Error:1146,SQLState:42S02 “解决方法
    PE笔记之节表
    标准类型String(学习中)
    链表实现(打印元素的实现)
    C++中new和delete来创建和释放动态数组
  • 原文地址:https://www.cnblogs.com/stemon/p/4596883.html
Copyright © 2011-2022 走看看