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 
  • 相关阅读:
    动态传参
    函数的介绍
    文件的操作
    send email with formatted table
    minimize and close window with customed winform
    python algorithm
    something important about docker
    book list
    which language is suitable for what to do
    Find Duplicate Items in list fast
  • 原文地址:https://www.cnblogs.com/stemon/p/4596883.html
Copyright © 2011-2022 走看看