动态规划

基本思想

动态规划算法通常用于求解具有某种最优性质的问题。在这类问题中, 可能会有很多可行解。每一个解都对应于一个值,我们希望找到具有最优值的解。动规划算法与分治法类似,其基本思想也是将待求解问题分解为若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解。与分治法不同的是,适用于动态规划算法求解的问题,经分解得到的子问题往往不是互相独立的。若用分治法来解这类问题,则分解得到的子问题数目太多,有些子问题被重复计算很多次。如果我们能保存已解决子问题的答案,而在需要时再找出已求得的答案,这样就可以避免大量的重复计算,节省时间。我们可以用一个表来记录所有已解决的子问题的答案。不管该子问题以后是否被用到,只要它被计算过,就将其结果填入表中。这就是动态规划算法的基本思路。具体的动态规划算法多种多样,但它们具有相同的填表格式。

与分治法最大的差别是:适用于动态规划求解的问题,经分解后得到的子问题不是互相独立的(即下一个子阶段的求解是建立在上一个子阶段的解的基础上,进行进一步的求解)

应用场景

适用于动态规划的问题必须满足最优化原理、无后效性和重叠性。

(1) 最优化原理(最优子结构性质):一个最优化策略具有这样的性质,不论过去状态和决策如何,对前面的决策所形成的状态而言,余下的决策必须构成最优策略。简而言之,一个最优化策略的子策略总是最优的。一个问题满足最优化原理又称其具有最优子结构性质。

(2) 无后效性:将各阶段按照一定的次序排列好之后,对于某个给定的阶段状态,它以前各阶段的状态无法直接影响它未来的决策,而只能通过当前的这个状态。换句话说,每个状态都是过去历史的一个完整总结。这就是无后向性,又称无后效性。

(3) 子问题的重叠性:动态规划将原来具有指数级时间复杂度的搜索算法改进成了具有多项式时间复杂度的算法。其中的关键在于解决冗余,这就是动态规划算法的根本目的。动态规划实质上是一种以空间换时间的技术,它在实现的过程中,不得不存储产生过程中的各种状态,所以它的空间复杂度要大于其他算法。

 

具体问题

01 背包问题:

有编号分别为a,b,c,d,e的五件物品,它们的重量分别是4, 6, 2, 2, 5, 1,它们的价值分别是8, 10, 6, 3, 7, 2,每件物品数量只有一个,现在给你个承重为12的背包,如何让背包里装入的物品具有最大的价值总和?

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;

void dynamic(int N, int C, int* weight, int* value)
{
    int* arr = new int[C+1];
    int* prearr = new int[C+1];
    int* maxvalue = new int[N];

    for (int i=1; i<=C; i++)
    {
        prearr[i] = 0;
    }

    for (int n=0; n<N; n++)
    {
        for (int c=1; c<=C; c++)
        {
            if (weight[n] > c)
            {
                arr[c] = prearr[c];
            }
            else
            {
                arr[c] = max(prearr[c], prearr[c-weight[n]]+value[n]);
            }
        }
        maxvalue[n] = arr[C];
        for (int i=1; i<=C; i++)
        {
            prearr[i] = arr[i];
            printf("%3d  ", prearr[i]);

        }
        cout << endl;
    }
    for (int i=1; i<=C; i++)
    {
        printf("%3d  ", prearr[i]);
    }
    cout << endl;

    for (int i=N-1; i>0; i--)
    {
        if (maxvalue[i] != maxvalue[i-1])
        {
            cout << i << '	';
        }
    }
    if (maxvalue[0] != maxvalue[1])
    {
        cout << 0 << endl;
    }

    delete[] arr;
    delete[] prearr;
    delete[] maxvalue;
}

int main()
{
    int weight[] = {4, 6, 2, 2, 5, 1};
    int value[] = {8, 10, 6, 3, 7, 2};
    dynamic(6, 12,  weight, value);

    return 0;
}

运行结果:

  0    0    0    8    8    8    8    8    8    8    8    8  
  0    0    0    8    8   10   10   10   10   18   18   18  
  0    6    6    8    8   14   14   16   16   18   18   24  
  0    6    6    9    9   14   14   17   17   19   19   24  
  0    6    6    9    9   14   14   17   17   19   21   24  
  2    6    8    9   11   14   16   17   19   19   21   24  
  2    6    8    9   11   14   16   17   19   19   21   24  
2       1       0
走台阶问题

有一座高度是10级台阶的楼梯,从下往上走,每跨一步只能向上1级或者2级台阶。一共有多少种走法。

分析:

如果只差最后一步走到第10级台阶,这时候,会出现两种情况:

  1. 从第9级走1步到第10级

  2. 从第8级走2步到第10级

如果我们已经知道0到9级走法有X种,0到8级走法有Y种,那么0到10级走法 X+Y 种

我们把走到第n级表示为 F(n),则

      F(10) = F(9) + F(8)

当只有1级台阶和两级台阶,走法分别为 1 和 2

可得:

  F(1) = 1

  F(2) = 2

  F(n) = F(n-1) + F(n-2) (n>=3)

动态规划中包含三个重要概念:最优子结构,边界,状态转移方程

其中,F(9) 和 F(8) 就是 F(10) 的最优子结构,F(1) 和 F(2) 是问题的边界

F(n) = F(n-1) + F(n-2) 是状态转移方程

该祭出表格了:

台阶数 1 2 3 4 5 6 7 8 9 10
走法数 1 2 3 5 8 13 21 ...    

参考博客:

https://blog.csdn.net/wangbaochu/article/details/53099953

https://blog.csdn.net/na_beginning/article/details/62884939

原文地址:https://www.cnblogs.com/zuofaqi/p/9194871.html