• 动态规划


    动态规划是一种算法思想,将原始问题拆分成规模更小且与原始问题性质相同的子问题,利用子问题的解得到原始问题的解。

    动态规划的适用条件之一是存在重叠子问题。动态规划的实现方式可以是自顶向下或自底向上。

        1)使用自顶向下实现时,通常使用递归实现,但这种方式通常会重复计算相同的子问题,导致时间复杂度很高。

        2)为了充分利用重叠子问题的性质,需要存储已经计算得到的子问题的答案,这样下次在遇到相同子问题的时候就可以直接得到答案

           而不需要重复计算。这就可以使用自底向上,通常使用迭代实现,此时可以确保每个子问题只会被计算一次而不会被重复计算。

    语言是苍白的,下面来看几个例子:

    1. 斐波那契数列

       数列形如:$1,1,2,3,5,8,...$,满足如下的递归式

    $$fib(n) = left{egin{matrix}
    1, & n = 1 ; or ; n = 2\
    fib(n-1) + fib(n-2), & other
    end{matrix} ight.$$

       我们用树的结构来描述这个问题是如何拆分成一系列子问题的,或者说用树的形式来描述一下这个递推程序。

       以数列:$1,1,2,3,5,8,13$ 为例,其递归树如下:

           

       从图中可以看出,存在很多重叠的子问题,比如红色方框内的 $fib(5)$,虽然其中一条分支已经计算过了,但另一条分支仍会计算。

       自上而下的实现方式如下,采用的是递归:

    int fib(int n)
    {  
        if(n > 1) {
            return fib(n - 1) + fib(n - 2);
        }  
        else {
            return n; // n = 0, 1 时递归终止 
        }
    }

       自下而上的实现方式如下,采用的是迭代:

    #define MAXN 100
    
    int fib(int n)
    {  
        int F[MAXN] = {0};
        F[0] = 0;
        F[1] = 1;
    
        for(int i = 2; i <= n; i++) {
            F[i] = F[i - 1] + F[i - 2];
        }
        
        return F[n];
    }  
    

       因为递归太耗堆栈了,效率不高,所以能用迭代的话,还是尽量使用迭代。

       迭代就相当于是记忆化搜索,将子问题的解记录下来,需要的时候直接从内存中取。

    2. 多任务最大收益问题

             

       上面这张图中,横轴代表时间,每一个小长方形表示一个任务,一共有 $8$ 个任务,长方形上的红色数字代表任务的收入。

       每个任务不能同时发生,比如做第 $8$ 个任务的时候,就没办法做第 $7,6$ 这两个任务。

       问题是:怎么样选择任务能获得最多的收益?

       可以从单个任务考虑,针对第 $i$ 个任务,只会有两种可能:选或不选。设 $opt(i)$ 表示:在任务 $1-i$ 中能获得最多收益的最优解。

       设 $prev(i)$ 为前一个不与 $i$ 重叠的任务(如上右图),$v_{i}$ 为选择任务 $i$ 能获得的收益,则

           1)最优解中包含第 $i$ 个任务:$opt(i) = v_{i} + opt(prev(i))$。

           2)最优解中不包含第 $i$ 个任务:$opt(i) = opt(i - 1)$。

       在任务 $1-i$ 中做选择,只会有上面这两种情况。这个问题的递推式如下:

          

       观察红框部分,很明显这里出现了重叠子问题。这里使用自底向上的方法,从 $opt(1),opt(2),...$ 逐步计算到 $opt(8)$。

    3. 不相邻子数列最大和

       比如一个数列:$4,1,1,9,1$,现在要在这个数列里面选出一些数字,这些数字不能有相邻的数,问:选哪些数字能使它们的和最大?

       以下面数组进行举例:

          

       设 $opt(i)$ 表示下标从 $0-i$ 这组数据求解的最佳方案,则上面的问题就是要求 $opt(6)$。

       还是将每个数单独考虑,对于第 $i$ 个数,它只有两种可能,即选或不选。

           1)选择第 $i$ 个数,则 $opt(i) = arr[i] + opt(i - 2)$。

           2)不选第 $i$ 个数,则 $opt(i) = opt(i - 1)$。

       可以做出其递推树如下:

          

       可以看出,存在重叠的子问题。

       先来看一下递归的代码:

    int Opt(int arr[], int i)
    {
    	if (i == 0) {
    		return arr[0];
    	}
    	else if (i == 1) {
    		return max(arr[0], arr[1]);
    	}
    	else {
    		return max(Opt(arr, i - 2) + arr[i], Opt(arr, i - 1));
    	}
    } 
    

        但是用递归会产生很多的重叠子问题,计算效率低,所以接下来用自底向上实现:

    #define MAXN 100
     
    int Opt(int arr[], int i)
    {
    	int dp[MAXN] = {0};
    	
    	dp[0] = arr[0];
    	dp[1] = max(arr[0], arr[1]);
    	
    	for(int j = 2; j <= i; ++j) {
    		dp[j] = max(dp[j - 2] + arr[j], dp[j - 1]);
    	} 
    	return dp[i];
    }  
    
  • 相关阅读:
    JS知识点
    JQuery知识点
    常见简单算法
    Html知识点
    Java基础_基本语法
    Java基础_Java概述
    VBA基础——循环语句
    VBA基础知识———常用语句
    VBA基础概念
    安全、结构良好的jQuery结构模板
  • 原文地址:https://www.cnblogs.com/yanghh/p/13699773.html
Copyright © 2020-2023  润新知