算法-动态规划

栏目: 编程工具 · 发布时间: 8年前

内容简介:算法-动态规划

什么是动态规划?

动态规划(Dynamic Programming,所以我们简称动态规划为DP)是运筹学的一个分支,是求解决策过程(decision process)最优化的数学方法。20世纪50年代初美国数学家R.E.Bellman等人在研究多阶段决策过程(multistep decision process)的优化问题时,提出了著名的最优化原理(principle of optimality),把多阶段过程转化为一系列单阶段问题,利用各阶段之间的关系,逐个求解,创立了解决这类过程优化问题的新方法——动态规划。1957年出版了他的名著《Dynamic Programming》,这是该领域的第一本著作。

   动态规划算法通常基于一个递推公式及一个或多个初始状态。当前子问题的解将由上一次子问题的解推出。使用动态规划来解题只需要多项式时间复杂度,因此它比回溯法、暴力法等要快许多。

   说了这么多术语,想必大家都很头疼,现在让我们通过一个例子来了解一下DP的基本原理。

首先,我们要找到某个状态的最优解,然后在它的帮助下,找到下一个状态的最优解。这句话暂时理解不了没关系,请看下面的例子:

如果我们有面值为1元、3元和5元的硬币若干枚,如何用最少的硬币凑够11元?

我们凭直观感觉告诉自己,先选面值最大,因此最多选2枚5元的硬币,现在是10元了,还差一元,接下来我们挑选第二大的3元硬币,发现不行(10+3=13超了),因此我们继续选第三大的硬币也就是1元硬币,选一个就可以(10+1=11),所以总共用了3枚硬币凑够了11元。这就是贪心法,每次选最大的。但是我们将面值改为2元,3元和5元的硬币,再用贪心法就不行了。为什么呢?按照贪心思路,我们同样先取2枚最大5元硬币,现在10元了,还差一元,接下来选第二大的,发现不行,再选第三大的,还是不行,这时用贪心方法永远凑不出11元,但是你仔细看看,其实我们可以凑出11元的,2枚3元硬币和1枚五元硬币就行了,这是人经过思考判断出来了的,但是怎么让计算机算出来呢?这就要用动态规划的思想:

首先我们思考一个问题,如何用最少的硬币凑够i元(i<11)?为什么要这么问呢?两个原因:1.当我们遇到一个大问题时,总是习惯把问题的规模变小,这样便于分析讨论。 2.这个规模变小后的问题和原来的问题是同质的,除了规模变小,其它的都是一样的,本质上它还是同一个问题(规模变小后的问题其实是原问题的子问题)。

好了,让我们从最小的i开始吧。当i=0,即我们需要多少个硬币来凑够0元。由于1,3,5都大于0,即没有比0小的币值,因此凑够0元我们最少需要0个硬币。 (这个分析很傻是不是?别着急,这个思路有利于我们理清动态规划究竟在做些什么。) 这时候我们发现用一个标记来表示这句“凑够0元我们最少需要0个硬币。”会比较方便,如果一直用纯文字来表述,不出一会儿你就会觉得很绕了。那么,我们用d(i)=j来表示凑够i元最少需要j个硬币。于是我们已经得到了d(0)=0,表示凑够0元最小需要0个硬币。当i=1时,只有面值为1元的硬币可用,因此我们拿起一个面值为1的硬币,接下来只需要凑够0元即可,而这个是已经知道答案的,即d(0)=0。所以,d(1)=d(1-1)+1=d(0)+1=0+1=1。当i=2时,仍然只有面值为1的硬币可用,于是我拿起一个面值为1的硬币,接下来我只需要再凑够2-1=1元即可(记得要用最小的硬币数量),而这个答案也已经知道了。所以d(2)=d(2-1)+1=d(1)+1=1+1=2。一直到这里,你都可能会觉得,好无聊,感觉像做小学生的题目似的。因为我们一直都只能操作面值为1的硬币!耐心点,让我们看看i=3时的情况。当i=3时,我们能用的硬币就有两种了:1元的和3元的( 5元的仍然没用,因为你需要凑的数目是3元!5元太多了亲)。既然能用的硬币有两种,我就有两种方案。如果我拿了一个1元的硬币,我的目标就变为了:凑够3-1=2元需要的最少硬币数量。即d(3)=d(3-1)+1=d(2)+1=2+1=3。这个方案说的是,我拿3个1元的硬币;第二种方案是我拿起一个3元的硬币,我的目标就变成:凑够3-3=0元需要的最少硬币数量。即d(3)=d(3-3)+1=d(0)+1=0+1=1. 这个方案说的是,我拿1个3元的硬币。好了,这两种方案哪种更优呢?记得我们可是要用最少的硬币数量来凑够3元的。所以,选择d(3)=1,怎么来的呢?具体是这样得到的:d(3)=min{d(3-1)+1, d(3-3)+1}。

OK,码了这么多字讲具体的东西,让我们来点抽象的。从以上的文字中,我们要抽出动态规划里非常重要的两个概念:状态和状态转移方程。

上文中d(i)表示凑够i元需要的最少硬币数量,我们将它定义为该问题的”状态”,这个状态是怎么找出来的呢?根据子问题定义状态。你找到子问题,状态也就浮出水面了。最终我们要求解的问题,可以用这个状态来表示:d(11),即凑够11元最少需要多少个硬币。那状态转移方程是什么呢?既然我们用d(i)表示状态,那么状态转移方程自然包含d(i),上文中包含状态d(i)的方程是:d(3)=min{d(3-1)+1, d(3-3)+1}。没错,它就是状态转移方程,描述状态之间是如何转移的。当然,我们要对它抽象一下,

d(i)=min{ d(i-vj)+1 },其中i-vj >=0,vj表示第j个硬币的面值;

有了状态和状态转移方程,这个问题基本上也就解决了。当然了,Talk is cheap,show me the code!

int main()  
    {  
        int a[3] = {1,3,5},sum = 11,cent = 0,dp[12];  
        dp[0] = 0;  
        for(int i = 1; i <= sum; i++) dp[i] = i;//我们假设存在1元的硬币那么i元最多只需要i枚1元硬币,当然最好设置dp[i]等于无穷大  

        for(int i = 1; i <= sum; i++){  
            for(int j = 0; j < 3; j++){  
                if(i >= a[j] && dp[i - a[j]] + 1 < dp[i]){  
                    dp[i] = dp[i- a[j] ] + 1;  
                }  
            }  
        }  
        cout<<dp[sum]<<endl;  
        return 0;  
    }

下图是当i从0到11时的解:

算法-动态规划

从上图可以得出,要凑够11元至少需要3枚硬币。

此外,通过追踪我们是如何从前一个状态值得到当前状态值的,可以找到每一次我们用的是什么面值的硬币。比如,从上面的图我们可以看出,最终结果d(11)=d(10)+1(面值为1),而d(10)=d(5)+1(面值为5),最后d(5)=d(0)+1 (面值为5)。所以我们凑够11元最少需要的3枚硬币是:1元、5元、5元。

通过硬币问题我们初识DP的原理,其实可以说贪心问题是DP问题的特例,现在我们通过几道题目加深对DP问题的理解

数塔问题是动态规划经典的题目,下面来初步讲解下

将一个由N行数字组成的三角形,如图所以,设计一个算法,计算出三角形的由顶至底的一条路径,使该路径经过的数字总和最大。

学弟学妹们你们之前学过DFS和BFS,第一眼看过去这题应该用DFS解决,没错,DFS也可以,但是我们观察下n行总共有(1 + 2 + 3 + 4+…+n) = (1+n)*n/2个节点,在递归求解的过程中很多节点被重复访问了,这就导致时间大大增加,必然超时

比如用递归的话,18这个节点被访问了两次

算法-动态规划

但是如果用DP的话这个节点可以只访问一次

算法-动态规划

好了,现在我们用DP解决这道问题

算法-动态规划

将上图转化一下:

算法-动态规划

假设上图用map[][]数组保存。

令f[i][j]表示从顶点(1, 1)到顶点(i, j)的最大值。

则可以得到状态转移方程:

f[i][j] = max(f[i+1][j], f[i+1][j+1]) + map[i][j]

此题既适合自顶而下的方法做,也适合自底而上的方法,

当用自顶而下的方法做时,最后要在在最后一列数中找出最大值,

而用自底而上的方法做时,f[1][1]即为最大值。

所以我们将图2根据状态转移方程可以得到图3:

算法-动态规划

最大值是30.

代码如下:

#include <cstdio>    
    #include <iostream>    
    #include <algorithm>    
    #include <cstring>    
    using namespace std;    
    int a[2000][2000];    
    int main()    
    {    
        int t,n,i,j;    
         while(~scanf("%d",&n))    
         {   
             for(i=0; i<n; i++)    
                 for(j=0; j<=i; j++)    
                     scanf("%d",&a[i][j]);    
           for(i=n-1; i>0; i--)    
                 for(j=0; j<i; j++)    
                     a[i-1][j]+=max(a[i][j],a[i][j+1]);    
            printf("%d\n",a[0][0]);    
         }    
         return 0;    
     }

出自: http://blog.csdn.net/u013445530/article/details/45645307


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

PHP for the World Wide Web, Second Edition (Visual QuickStart Gu

PHP for the World Wide Web, Second Edition (Visual QuickStart Gu

Larry Ullman / Peachpit Press / 2004-02-02 / USD 29.99

So you know HTML, even JavaScript, but the idea of learning an actual programming language like PHP terrifies you? Well, stop quaking and get going with this easy task-based guide! Aimed at beginning ......一起来看看 《PHP for the World Wide Web, Second Edition (Visual QuickStart Gu》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具