【coding】动态规划

bilibili 看到一个博主讲的比较好的动态规划内容,自己对应整理了一下学习笔记,以便巩固学习

在这里插入图片描述

题目一:Fibonacci

代码简单实现

/**
 * Creation         :       2018.11.18 17:03
 * Last Reversion   :       2018.11.18 17:08
 * Author           :       Lingyong Smile {smilelingyong@163.com}
 * File Type        :       cpp
 * -----------------------------------------------------------------
 * Fibonacci
 *      Everyone knows the Fibonacci sequence. Now you need to enter an integer n.
 *      Please output the nth item of the Fibonacci sequence (starting at 0 and 0th item is 0).
 *      0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, ...
 * -----------------------------------------------------------------
 * Crop right @ 2018 Lingyong Smile {smilelingyong@163.com}
 */

#include <iostream>
using namespace std;

/**
 * 递归方式
 */ 
int FibonacciRecursive(int n) {
    if (n == 0)
        return 0;
    else if (n == 1)
        return 1;
    return FibonacciRecursive(n - 2) + FibonacciRecursive(n - 1);
}

/**
 * 非递归:动态规划
 */ 
int Fibonacci(int n) {
    if (n == 0) return 0;
    if (n == 1) return 1;
    int a = 0;
    int b = 1;
    int c;
    for (int i = 2; i <= n; i++) {
        c = a + b;
        a = b;
        b = c;
    }
    return c;
}

int main() {
    int n;
    cout << "Please input a int number:";
    while (cin >> n) {
        // Test Fibonacci function
        cout << "Fibonacci(" << n << ") is : " << Fibonacci(n) << endl;

        // Test Fibonacci Recursive function
        cout << "FibonacciRecursive(" << n << ") is : " << FibonacciRecursive(n) << endl;
        cout << "Please input a int number:";
    }
    // system("pause");
    return 0;
}

题目二:最多能赚几块钱?

横轴代表时间,灰色的长方条代表从某一时刻开始的任务,长方条上的数字代表完成这个任务能够赚几块钱,比如最上面的第1个任务,表示从1点开始,4点结束,完成这个任务可以获得5元钱。其中有一个限制,开始了某一任务后,只有做完这个任务才能做下一个,比如开始做了任务7,则不能做任务4、5、6、8,因为有时间冲突。现在有一个员工,如何才能赚最多的钱,最多能赚几块钱?在这里插入图片描述

从上往下分析,得出递推公式(选不选)

其中 O P T ( i ) OPT(i) OPT(i) 表示,当我要考虑前 i i i 个任务时,它的最优解是什么,即最多能赚几块钱?
例如:当考虑前8个任务时 O P T ( 8 ) OPT(8) OPT(8),对于任务8有两种状态,选和不选。

  • 选第8个任务,首先可以赚4块钱,再加上 O P T ( 5 ) OPT(5) OPT(5) ,即加上前面5个任务的最优解,最多能赚几块钱。(因为选了任务8则和任务6和7有时间冲突)
  • 不选第8个任务,则就是前面7个任务的最优解 O P T ( 7 ) OPT(7) OPT(7)

然后选这两个状态结果最大的,作为前8个任务的最优解 O P T ( 8 ) OPT(8) OPT(8) ,即得到如下递归公式:
O P T ( 8 ) = m a x { 4 + O P T ( 5 )      , 选 任 务 8 O P T ( 7 )      , 不 选 任 务 8 OPT(8) = max \left\{ \begin{aligned} &amp;&amp; 4+ OPT(5) \ \ \ \ &amp;, 选任务8 \\ &amp;&amp; OPT(7) \ \ \ \ &amp;,不选任务8 \end{aligned}\right. OPT(8)=max{4+OPT(5)    OPT(7)    ,8,8

整理一下递推公式
O P T ( i ) = m a x { v i + O P T ( p r e v ( i ) )      , 选 任 务 i O P T ( i − 1 )      , 不 选 任 务 i OPT(i) = max \left\{ \begin{aligned} &amp;&amp; v_i+ OPT(prev(i)) \ \ \ \ &amp;, 选任务i \\ &amp;&amp; OPT(i-1) \ \ \ \ &amp;,不选任务i \end{aligned}\right. OPT(i)=max{vi+OPT(prev(i))    OPT(i1)    ,i,i

其中 v i v_i vi 表示第 i i i 个任务的奖励工资, p r e v ( i ) prev(i) prev(i) 表示如果要做第 i i i 个任务,则它前面顶多还能做前几个。例如: p r e v ( 8 ) = 5 prev(8) = 5 prev(8)=5 表示如果要做第8个任务,则前面顶多还能做前5个,即加上考虑前5个任务的最优解 O P T ( p r e v ( 8 ) ) OPT(prev(8)) OPT(prev(8))
在这里插入图片描述
首先计算 p r e v prev prev 数组,比如 i = 1 i = 1 i=1 时, p r e v ( 1 ) = 0 prev(1) = 0 prev(1)=0 表示如果做任务1,则前面能做的为0个,以此类推。我们最终要算的是前面8个任务的最优解,即 O P T ( 8 ) OPT(8) OPT(8) ,根据递归式可以得到如上状结构,可以发现:(1)首先这是一个找最优解问题。(2)大问题的最优解依赖于子问题的最优解。(3)大问题分解成子问题,子问题间有重复子问题。 这样的问题就可以使用动态规划思想来做。从上往下分析问题,从下往上求解问题。从 O P T ( 1 ) OPT(1) OPT(1) 开始分析计算并保存对应结果。
O P T ( 1 ) = v 1 = 5 OPT(1) = v_1 = 5 OPT(1)=v1=5
O P T ( 2 ) OPT(2) OPT(2) 分析:如果不选任务2,则是 O P T ( 1 ) = 5 OPT(1) = 5 OPT(1)=5 ;如果选任务2,则是 O P T ( p r e v ( 2 ) ) + 任 务 2 价 值 = 0 + 1 OPT(prev(2)) + 任务2价值 = 0 + 1 OPT(prev(2))+2=0+1 。最后选这两个状态的最大值作为前个任务2的最优解,所以 O P T ( 2 ) = 5 OPT(2) = 5 OPT(2)=5。表示如果只有前面两个任务的时候,最多能挣5块钱。
在这里插入图片描述
O P T ( 4 ) OPT(4) OPT(4) 分析:如果不选任务4,则是 O P T ( 3 ) = 8 OPT(3) = 8 OPT(3)=8 ;如果选任务4,则是 O P T ( p r e v ( 4 ) ) + 任 务 4 价 值 = O P T ( 1 ) + v 4 = 5 + 4 = 9 OPT(prev(4)) + 任务4价值= OPT(1) + v_4 = 5 + 4 = 9 OPT(prev(4))+4=OPT(1)+v4=5+4=9 。最后选这两个状态的最大值作为前4个任务的最优解,所以 O P T ( 4 ) = 9 OPT(4) = 9 OPT(4)=9。表示如果只有前面4个任务的时候,最多能挣9块钱。
在这里插入图片描述
后面以此类推,直到分析完前8个任务。
在这里插入图片描述

代码简单实现

/**
 * Creation         :       2019.04.05 14:50
 * Last Reversion   :       2019.03.05 15:22
 * Author           :       Lingyong Smile {smilelingyong@163.com}
 * File Type        :       cpp
 * -----------------------------------------------------------------
 * 最多能赚几块钱(动态规划) 结合笔记题目查看
 * -----------------------------------------------------------------
 * Crop right @ 2019 Lingyong Smile {smilelingyong@163.com}
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define max(a, b) (((a) > (b)) ? (a) : (b))
#define min(a, b) (((a) < (b)) ? (a) : (b))

int v[9] = {0, 5, 1, 8, 4, 6, 3, 2, 4};     // 注意这里前面多补了一个0,是为了方便和笔记对应
int prev[9] = {0, 0, 0, 0, 1, 0, 2, 3, 5};  // 这里的prev数组是自己预先算好了的

/**
 * 递归形式
 */ 
int OPTRecursive(int n) {
    if (n == 0)
        return 0;
    if (n == 1)
        return 5;
    return max(OPTRecursive(n - 1) , v[n] + OPTRecursive(prev[n]));
}

/**
 * 非递归形式:(动态规划)
 */ 
int OPT(int n) {
    if (n == 0)
        return 0;
    if (n == 1)
        return 5;
    int *OPT = (int*)malloc(sizeof(int) * (n + 1));     // 这里多开辟了一个int空间大小,是为了和公式下标对应
    OPT[0] = 0;
    OPT[1] = 5;
    for (int i = 2; i <= n; i++) {
        OPT[i] = max(OPT[i-1], v[i] + OPT[prev[i]]);
    }
    int res = OPT[n];
    free(OPT);
    return res;
}

int main() {
    int n;
    printf("Please input a int number (1~8):\n");
    while (~scanf("%d", &n) && n >= 1 && n <= 8) {
        printf("%d\n", OPT(n));
    }
    return 0;
}

题目三:最大和

在如下一堆数字中选出一些数字,如何让数字之和最大?
限定条件:选出的数字不能是相邻的。

从上往下分析,得出递归公式(选不选)

在这里插入图片描述
O P T ( 6 ) OPT(6) OPT(6) :表示到下标为6 这个位置的最佳方案(最优解)是什么?
从上往下开始分析:对于 O P T ( 6 ) OPT(6) OPT(6) 我们有两种状态,选3,和不选3。即有如上递归式。

  • a r r [ 6 ] arr[6] arr[6],此时的最佳方案是 O P T ( 4 ) + a r r [ 6 ] OPT(4) + arr[6] OPT(4)+arr[6] 。这里不能选 a r r [ 5 ] arr[5] arr[5] ,因为 a r r [ 5 ] arr[5] arr[5] a r r [ 6 ] arr[6] arr[6] 是相邻的。
  • 不选 a r r [ 6 ] arr[6] arr[6],此时最佳方案是 O P T ( 5 ) OPT(5) OPT(5)

然后选这两个状态结果最大的,作为到下标为6这个位置的最优解 O P T ( 6 ) OPT(6) OPT(6)
在这里插入图片描述
进而,我们可以得到这样的递归方程:

O P T ( i ) = m a x { O P T ( i − 2 ) + a r r [ i ]      , 选 a r r [ i ] O P T ( i − 1 )      , 不 选 a r r [ i ] OPT(i) = max \left\{ \begin{aligned} &amp;&amp; OPT(i-2) + arr[i] \ \ \ \ &amp;, 选arr[i] \\ &amp;&amp; OPT(i-1) \ \ \ \ &amp;,不选arr[i] \end{aligned}\right. OPT(i)=max{OPT(i2)+arr[i]    OPT(i1)    ,arr[i],arr[i]

分析递归出口

O P T ( 0 ) = a r r [ 0 ] O P T ( 1 ) = m a x ( a r r [ 0 ] , a r r [ 1 ] ) OPT(0) = arr[0] \\ OPT(1) = max(arr[0], arr[1]) OPT(0)=arr[0]OPT(1)=max(arr[0],arr[1])

代码简单实现

/**
 * Creation         :       2019.04.06 11:10
 * Last Reversion   :       2019.04.06 11:23
 * Author           :       Lingyong Smile {smilelingyong@163.com}
 * File Type        :       cpp
 * -----------------------------------------------------------------
 * 题目描述:
 *    在如下一堆数字中选出一些数字,如何让数字之和最大?
 * 限定条件:选出的数字不能是相邻的。
 * arr[7] = {1, 2, 4, 1, 7, 8, 3}
 * -----------------------------------------------------------------
 * Crop right @ 2019 Lingyong Smile {smilelingyong@163.com}
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define max(a, b) (((a) > (b)) ? (a) : (b))
#define min(a, b) (((a) < (b)) ? (a) : (b))

int arr[7] = {1, 2, 4, 1, 7, 8, 3};

/**
 * 递归形式,不好存在很多重叠子问题
 */ 
int OPTRec(int n) {
    if (n == 0)
        return arr[0];
    else if (n == 1)
        return max(arr[0], arr[1]);
    else
        return max(OPTRec(n - 2) + arr[n], OPTRec(n - 1));
}

/**
 * 非递归形式:动态规划
 */ 
int OPT(int n) {
    if (n == 0)
        return arr[0];
    else if (n == 1)
        return max(arr[0], arr[1]);
    
    int *OPT = (int*)malloc(sizeof(int) * (n + 1));     // 注意这里开辟的数组大小,n为下标,从0开始
    OPT[0] = arr[0];
    OPT[1] = max(arr[0], arr[1]);
    for (int i = 2; i <= n; i++) {
        OPT[i] = max(OPT[i-2] + arr[i], OPT[i-1]);
    }
    int res = OPT[n];
    free(OPT);
    return res;
}

int main() {
    int n;
    printf("Please input a int number (0~7):\n");
    while (~scanf("%d", &n) && n >= 0 && n <= 7) {
        printf("%d\n", OPT(n));
    }
    return 0;
}

题目四:是否存在和为指定值

在如下一堆数字中选出一些数字求和,使其等于数字 S S S。如果存在这样的方案则返回 t r u e true true,否则返回 f a l s e false false

从上往下分析,得出递归公式(选不选)

在这里插入图片描述
分析:我们定义 S u b s e t Subset Subset 表示子集, S u b s e t ( a r r [ 5 ] , 9 ) Subset(arr[5], 9) Subset(arr[5],9) 表示对前面5个数字取数求和为9,应该怎么分配。从上往下分析,当 i = 5 i = 5 i=5 时,我们有两种状态:

  • a r r [ 5 ] arr[5] arr[5] ,此时方案为 S u b s e t ( a r r [ 4 ] , 7 ) Subset(arr[4], 7) Subset(arr[4],7) ,选了 a r r [ 5 ] arr[5] arr[5] 之后,只需要前面4个数字取数之和为7就可以。
  • 不选 a r r [ 5 ] arr[5] arr[5] ,此时方案为 S u b s e t ( a r r [ 4 ] , 9 ) Subset(arr[4], 9) Subset(arr[4],9) ,前面4个数字取数之和为9。

最后只要这两种方案有一个为 t r u e true true,则满足要求,所以中间用 o r or or

分析递归出口

在这里插入图片描述

  • S = = 0 S == 0 S==0 时, 比如 S u b s e t ( a r r [ 2 ] , 0 ) Subset(arr[2], 0) Subset(arr[2],0) ,表示,2后面的数字已经存方案可以使得取数求和等于 S S S ,即这个时候已满足要求,返回 t r u e true true
  • i = = 0 , S   ! = 0 i == 0, S \ != 0 i==0,S !=0 时,即表示处理到第一个数字,只有当 a r r [ i ] = = S arr[i] == S arr[i]==S 才会返回 t r u e true true ,否则返回 f a l s e false false 。比如,当我们给的数组只有一个元素是,只有当 a r r [ 0 ] = = S arr[0] == S arr[0]==S 时才为 t r u e true true
    在这里插入图片描述
  • a r r [ i ] &gt; S arr[i] &gt; S arr[i]>S 时,这时候我们肯定不能选 a r r [ i ] arr[i] arr[i] ,即此时返回 S u b s e t ( a r r [ i − 1 ] , S ) Subset(arr[i-1], S) Subset(arr[i1],S)
    在这里插入图片描述
    整理一下递归公式如下:
    在这里插入图片描述

动态规划解法

即非递归方式,我们需要借助二维数组,保存我们的中间过程(中间的这些重叠子问题)。如下设计我们的二维数组:
在这里插入图片描述
其中,最左边列是 a r r [ ] arr[ ] arr[] 数组,右边是设计一个二维数组,行坐标表示下标 i i i ,纵坐标表示 S S S ,比如图中的绿色方框表示 S u b s e t ( a r r [ 2 ] , 3 ) Subset(arr[2], 3) Subset(arr[2],3) 此时,由于 a [ 2 ] = 4 &gt; S = 3 a[2] = 4 &gt; S = 3 a[2]=4>S=3 ,所以只能不选 a r r [ 2 ] arr[2] arr[2] ,即得到当前方案 S u b s e t ( a r r [ 1 ] , 3 ) Subset(arr[1], 3) Subset(arr[1],3) 。明白含义之后,我们来定义我们的出口条件:

  • i = 0 i = 0 i=0 时,只有 a r r [ 0 ] = S arr[0] = S arr[0]=S 时返回 T r u e True True ,其余都为 F a l s e False False 。即我们写好了 i = 0 i = 0 i=0 这一行的出口条件
  • S = 0 S = 0 S=0 时,说明已经找到了取数方案,直接返回 T r u e True True 。即我们写好了 S = 0 S = 0 S=0 这一列的出口条件。

然后按照从左往右的顺序,把每一行填好,最后返回最后一个结果即可。
在这里插入图片描述
对于 s u b s e t [ 0 , 0 ] subset[0, 0] subset[0,0] 我们规定为 F a l s e False False

代码简单实现

/**
 * Creation         :       2019.04.06 11:30
 * Last Reversion   :       2019.04.06 15:39:
 * Author           :       Lingyong Smile {smilelingyong@163.com}
 * File Type        :       cpp
 * -----------------------------------------------------------------
 * 题目描述:
 *    在如下一堆数字中选出一些数字求和,能够等于数字n。如果存在这样的方案则
 * 返回true,否则返回false。
 * int arr[] = {3, 34, 4, 12, 5, 2};
 * 测试用例:
 输入:
9
10
11
12
13
 输出:
1
1
1
1
0
 * -----------------------------------------------------------------
 * Crop right @ 2019 Lingyong Smile {smilelingyong@163.com}
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define N_SIZE 100
#define M_SIZE 100

int arr[] = {3, 34, 4, 12, 5, 2};
int subset[N_SIZE][M_SIZE];

/**
 * 递归形式
 */ 
int RecSubset(int arr[], int i, int S) {
    if (i == 0)                 // 这里的两个出口和调换了一下顺序(与笔记想笔记),因为比如当数组只有一个元素arr[] = {4}, S = 5,此时因该返回False才对。
        return (arr[0] == S);
    else if(S == 0)
        return 1;
    else 
        return RecSubset(arr, i-1, S-arr[i]) || RecSubset(arr, i-1, S);
}

/**
 * 非递归形式:动态规划
 */ 
int DPSubset(int arr[], int len, int S) {
    for (int i = 0; i < len; i++) {     // 将S == 0 列都初始化为True
        subset[i][0] = 1;
    }
    for (int s = 0; s <= S; s++) {      // 将 i == 0 行,除了arr[0]列初始化为True,其余都初始化为False
        subset[0][s] = 0;
    }
    subset[0][arr[0]] = 1;

    for (int i = 1; i < len; i++) {
        for (int s = 1; s <= S; s++) {
            if (arr[i] > s)
                subset[i][s] = subset[i-1][s];
            else {
                subset[i][s] = (subset[i-1][s-arr[i]] || subset[i-1][s]);
            }
        }
    }
    return subset[len-1][S];
}

int main() {
    if (RecSubset(arr, 5, 13))
        printf("True\n");
    else
        printf("False\n");
    if (DPSubset(arr, 6, 13))
        printf("True\n");
    else
        printf("False\n");
    return 0;
}

Todo

  • 01背包
  • 完全背包
  • 多重背包

Reference

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值