动态规划问题详例



 一、基本概念

    动态规划过程是:每次决策依赖于当前状态,又随即引起状态的转移。一个决策序列就是在变化的状态中产生出来的,所以,这种多阶段最优化决策解决问题的过程就称为动态规划。

二、基本思想与策略

    基本思想与分治法类似,也是将待求解的问题分解为若干个子问题(阶段),按顺序求解子阶段,前一子问题的解,为后一子问题的求解提供了有用的信息。在求解任一子问题时,列出各种可能的局部解,通过决策保留那些有可能达到最优的局部解,丢弃其他局部解。依次解决各子问题,最后一个子问题就是初始问题的解。

    由于动态规划解决的问题多数有重叠子问题这个特点,为减少重复计算,对每一个子问题只解一次,将其不同阶段的不同状态保存在一个二维数组中。

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

 


三、适用的情况

能采用动态规划求解的问题的一般要具有3个性质:

    (1) 最优化原理:如果问题的最优解所包含的子问题的解也是最优的,就称该问题具有最优子结构,即满足最优化原理。

    (2) 无后效性:即某阶段状态一旦确定,就不受这个状态以后决策的影响。也就是说,某状态以后的过程不会影响以前的状态,只与当前状态有关。

   (3)有重叠子问题:即子问题之间是不独立的,一个子问题在下一阶段决策中可能被多次使用到。(该性质并不是动态规划适用的必要条件,但是如果没有这条性质,动态规划算法同其他算法相比就不具备优势

 


四、求解的基本步骤

     动态规划所处理的问题是一个多阶段决策问题,一般由初始状态开始,通过对中间阶段决策的选择,达到结束状态。这些决策形成了一个决策序列,同时确定了完成整个过程的一条活动路线(通常是求最优的活动路线)。如图所示。动态规划的设计都有着一定的模式,一般要经历以下几个步骤。

    初始状态→│决策1│→│决策2│→…→│决策n│→结束状态

                      图1 动态规划决策过程示意图

    (1)划分阶段:按照问题的时间或空间特征,把问题分为若干个阶段。在划分阶段时,注意划分后的阶段一定要是有序的或者是可排序的,否则问题就无法求解。

    (2)确定状态和状态变量:将问题发展到各个阶段时所处于的各种客观情况用不同的状态表示出来。当然,状态的选择要满足无后效性。

    (3)确定决策并写出状态转移方程:因为决策和状态转移有着天然的联系,状态转移就是根据上一阶段的状态和决策来导出本阶段的状态。所以如果确定了决策,状态转移方程也就可写出。但事实上常常是反过来做,根据相邻两个阶段的状态之间的关系来确定决策方法和状态转移方程

    (4)寻找边界条件:给出的状态转移方程是一个递推式,需要一个递推的终止条件或边界条件。

    一般,只要解决问题的阶段状态状态转移决策确定了,就可以写出状态转移方程(包括边界条件)。

实际应用中可以按以下几个简化的步骤进行设计:

    (1)分析最优解的性质,并刻画其结构特征。

    (2)递归的定义最优解。

    (3)以自底向上或自顶向下的记忆化方式(备忘录法)计算出最优值

    (4)根据计算最优值时得到的信息,构造问题的最优解

 


五、算法实现的说明

    动态规划的主要难点在于理论上的设计,也就是上面4个步骤的确定,一旦设计完成,实现部分就会非常简单。

     使用动态规划求解问题,最重要的就是确定动态规划三要素

    (1)问题的阶段 (2)每个阶段的状态

    (3)从前一个阶段转化到后一个阶段之间的递推关系

     递推关系必须是从次小的问题开始到较大的问题之间的转化,从这个角度来说,动态规划往往可以用递归程序来实现,不过因为递推可以充分利用前面保存的子问题的解来减少重复计算,所以对于大规模问题来说,有递归不可比拟的优势,这也是动态规划算法的核心之处

    确定了动态规划的这三要素,整个求解过程就可以用一个最优决策表来描述最优决策表是一个二维表,其中行表示决策的阶段,列表示问题状态,表格需要填写的数据一般对应此问题的在某个阶段某个状态下的最优值(如最短路径,最长公共子序列,最大价值等),填表的过程就是根据递推关系,从1行1列开始,以行或者列优先的顺序,依次填写表格,最后根据整个表格的数据通过简单的取舍或者运算求得问题的最优解。

          f(n,m)=max{f(n-1,m), f(n-1,m-w[n])+P(n,m)}

 


六、动态规划算法基本框架
复制代码
代码
   
   
1 for (j = 1 ; j <= m; j = j + 1 ) // 第一个阶段 2   xn[j] = 初始值; 3 4   for (i = n - 1 ; i >= 1 ; i = i - 1 ) // 其他n-1个阶段 5   for (j = 1 ; j >= f(i); j = j + 1 ) // f(i)与i有关的表达式 6 xi[j] = j = max(或min){g(xi - 1 [j1:j2]), ......, g(xi - 1 [jk:jk + 1 ])}; 8 9 t = g(x1[j1:j2]); // 由子问题的最优解求解整个问题的最优解的方案 10 11 print(x1[j1]); 12 13 for (i = 2 ; i <= n - 1 ; i = i + 1 15 { 17 t = t - xi - 1 [ji]; 18 19 for (j = 1 ; j >= f(i); j = j + 1 ) 21 if (t = xi[ji]) 23 break ; 25 }
复制代码

 

       例一:最小硬币问题


问题描述:

设有n 种不同面值的硬币,各硬币的面值存于数组T[1:n]中。现要用这些面值的硬币来找钱。可以使用的各种面值的硬币个数存于数组Coins[1:n]中。对任意钱数0≤m≤20001,设计一个用最少硬币找钱m的方法。

编程任务:

对于给定的1≤n≤10,硬币面值数组T和可以使用的各种面值的硬币个数数组Coins,以及钱数m,0≤m≤20001,编程计算找钱m的最少硬币数。

数据输入:

由文件input.txt 提供输入数据,文件的第一行中只有1 个整数给出n的值,第2 行起每行2 个数,分别是T[j]和Coins[j]。最后1 行是要找的钱数m。

结果输出:
程序运行结束时,将计算出的最少硬币数输出到文件output.txt中。问题无解时输出-1。
输入文件示例 输出文件示例
input.txt 
3
1 3
2 3
5 3
18

output.txt

5


问题分析:

因为题目对每种面值的硬币数目有限制,所以不能使用贪心算法可能得不到正确的结果。此处可以采用动态规划或者深度优先搜索的方法。下面分别介绍两种方法。

1.深度优先搜索方法

对于给定的钱数m,和给定的货币种类n,第n种货币的选择有0,1,..,min{m/t[n],coins[n]}。假设第n种货币数量选定了为k,那么剩下的钱数为m-k,剩下的货币种类为n-1,此时进行递归即可。

深度优先搜索方法的时间复杂度为coins[1]*coins[1]*..*coins[n],但实际上可以再搜索过程中进行一些剪枝操作,实际上可以大大降低时间复杂度。

2.动态规划算法

设s[i][k]表示对于钱数k,选择前i种货币种类所需要的最少硬笔数,则s[i][k]=min{s[i-1][k-j*t[i]]+j},0=<j<=min{coins[i],k/t[i]}


解题报告:

#include<iostream>
#include<fstream>
#include<stdlib.h>
#include<cstring>
using namespace std;
#define INF 1<<30
int minCoin(int coins[],int t[],int n,int m)
{
    int i=0;
    int k=0;
    int j=0;
    int **s=(int **)malloc(sizeof(int *)*(n+1));
    for(i=0; i<=n; i++)
    {
        s[i]=(int *)malloc(sizeof(int)*(m+1));//二维数组元素s[i][j]的初始值为-1,表示对于钱数j,前i种硬币不能找零
        memset(s[i],-1,sizeof(int)*(m+1));
    }
    for(i=0; i<=n; i++)
    {//对于钱数为0,s[i][0]=0;
        s[i][0]=0;
    }

    int min=INF;
    for(i=1; i<=n; i++)
    {
        for(k=i; k<=m; k++)
        {
            for(j=0; j<=coins[i-1]&&j<=k/t[i-1]; j++)//如果s[i-1][k-j*t[i-1]]<0表示如果第i种货币使用j个,那么剩下的i-1种货币用来找零钱数k-j*t[i-1]不成功
            {
                if(s[i-1][k-j*t[i-1]]+j<min&&s[i-1][k-j*t[i-1]]>=0)
                    min=s[i-1][k-j*t[i-1]]+j;
            }
            if(min!=INF)
                s[i][k]=min;
            min=INF;
        }
    }

    return s[n][m];
}
int main()
{
    ifstream in("input.txt");
    int n,m;
    in>>n;
    int *coins=new int[n];
    int *t=new int[n];
    for(int i=0; i<n; i++)
    {
        in>>t[i]>>coins[i];
    }
    in>>m;
    int res=minCoin(coins,t,n,m);
    cout<<res<<endl;
    return 0;
}


例二:

独立任务最优调度问题


问题描述:
用2 台处理机A 和B 处理n 个作业。设第i 个作业交给机器A 处理时需要时间ai,若由机器B 来处理,则需要时间bi 。由于各作业的特点和机器的性能关系,很可能对于某些i,有ai>=bi ,而对于某些j,j≠i,有aj<bj。既不能将一个作业分开由2 台机器处理,也没有一台机器能同时处理2 个作业。
设计一个动态规划算法,使得这2 台机器处理完这n个作业的时间最短(从任何一台机器开工到最后一台机器停工的总时间)。
研究一个实例:
(a1,a2,a3,a4,a5,a6)=(2,5,7,10,5,2);(b1,b2,b3,b4,b5,b6)=(3,8,4,11,3,4)。
编程任务:
对于给定的2 台处理机A 和B处理n 个作业,找出一个最优调度方案,使2台机器处理完这n 个作业的时间最短。
数据输入:
由文件input.txt提供输入数据。文件的第1行是1个正整数n, 表示要处理n个作业。接下来的2行中,每行有n 个正整数,分别表示处理机A 和B 处理第i 个作业需要的处理时间。
结果输出:
程序运行结束时,将计算出的最短处理时间输出到文件output.txt中。
输入文件示例 输出文件示例
input.txt 
6
2 5 7 10 5 2
3 8 4 11 3 4

output.txt

15


问题分析:

此题目可以使用动态规划来做。难点是如何构造动态规划算法。找出最优子结构和递推公式。

有两种构造方法:

1.t[i][j][k],表示机器A花费小于等于i的时间,机器B花费小于等于j的时间能够完成前K个任务,取值为bool类型

递推公式如下:t[i][j][k]=t[i-a[k]][j][k-1]|t[i][j-b[k]][k-1],这种构造方法需要一个三维数组,空间和时间复杂度都相对较高

2.t[k][i],表示完成前K个任务,机器A花费小于等于i的时间的前提下,机器B所需要的最小时间。

递推公式如下:t[k][i]=min{t[k-1][i-a[k]],t[k-1][i]+b[k]},这种方法时间和空间复杂度相较于第一种方法较低。


解题报告:

#include<iostream>
#include<cstring>
#include<malloc.h>
using namespace std;
int task_schedule(int *a,int *b,int n)
{
    int i,j;
    int sa=0;
    for(i=0; i<n; i++)
        sa+=a[i];
    int **t=(int **)malloc(sizeof(int*)*(n+1));
    for(j=0; j<=n; j++)
    {
        t[j]=(int *)malloc(sizeof(int)*(sa+1));
        memset(t[j],0,sizeof(int)*(sa+1));
    }
    for(int k=1; k<=n; k++)
    {
        for(i=0; i<=sa; i++)
        {
            if(i<a[k-1])
                t[k][i]=t[k-1][i]+b[k-1];
            else t[k][i]=t[k-1][i-a[k-1]]>=(t[k-1][i]+b[k-1])?(t[k-1][i]+b[k-1]):t[k-1][i-a[k-1]];
        }
    }

    int min=1<<30;//相当于1X2^30,此数非常的大
    for(i=0; i<=sa; i++)
    {
        int tmp=t[n][i]>i?t[n][i]:i;
        if(tmp<min)
            min=tmp;
    }

    return min;
}

int main()
{
    int n;
    int i;
    cin>>n;
    int *a=new int[n];
    int *b=new int[n];
    for(i=0; i<n; i++)
        cin>>a[i];
    for(i=0; i<n; i++)
        cin>>b[i];
    int res=task_schedule(a,b,n);
    cout<<res<<endl;
}



总结:

算法之路长又长,开始漫长的算法探索之旅!


  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
YOLO(You Only Look Once)是一种目标检测算法,可以在单个前向传递中识别图像中的多个对象。以下是使用YOLO算法的详细步骤: 1. 数据预处理:首先需要准备训练数据集和测试数据集。对于每个图像,需要标记对象的位置和类别,并将其转换为指定的格式。 2. 模型训练:使用标记的训练数据集对YOLO模型进行训练。训练过程中需要设置一些超参数,例如学习率、批量大小和迭代次数等。 3. 模型评估:在训练完成后,需要使用测试数据集对模型进行评估。评估指标通常包括平均精度(AP)和召回率(recall)等。 4. 模型优化:根据评估结果,可以对模型进行优化,例如调整超参数、增加训练数据集等。 5. 对象检测:使用训练好的模型对新的图像进行对象检测。首先需要对图像进行预处理,例如缩放、裁剪和归一化等。然后将处理后的图像输入到模型中进行检测。检测结果包括对象的位置、类别和置信度等。 6. 后处理:对检测结果进行后处理,例如非极大值抑制(NMS)等。NMS可以去除置信度较低的重叠检测框,只保留置信度最高的检测框。 7. 可视化:将检测结果可视化,例如在图像上绘制检测框和类别标签等。 总的来说,YOLO算法可以在单个前向传递中实现对象检测,具有较高的速度和准确性。但是在复杂场景下,可能会存在一些误检和漏检的情况,需要根据具体应用场景进行优化和改进。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

潇潇雨歇_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值