动态规划

来源:互联网 发布:简述数据和信息的区别 编辑:程序博客网 时间:2024/04/30 10:35

概述

动态规划在查找有很多重叠子问题的情况的最优解时有效。它将问题重新组合成子问题。为了避免多次解决这些子问题,它们的结果都逐渐被计算并被保存,从简单的问题直到整个问题都被解决。因此,动态规划保存递归时的结果,因而不会在解决同样的问题时花费时间。

动态规划只能应用于有最优子结构的问题。最优子结构的意思是局部最优解能决定全局最优解(对有些问题这个要求并不能完全满足,故有时需要引入一定的近似)。简单地说,问题能够分解成子问题来解决。

[编辑]步骤

  1. 最优子结构性质。如果问题的最优解所包含的子问题的解也是最优的,我们就称该问题具有最优子结构性质(即满足最优化原理)。最优子结构性质为动态规划算法解决问题提供了重要线索。
  2. 子问题重叠性质。子问题重叠性质是指在用递归算法自顶向下对问题进行求解时,每次产生的子问题并不总是新问题,有些子问题会被重复计算多次。动态规划算法正是利用了这种子问题的重叠性质,对每一个子问题只计算一次,然后将其计算结果保存在一个表格中,当再次需要计算已经计算过的子问题时,只是在表格中简单地查看一下结果,从而获得较高的效率。

[编辑]

当有一个背包的容量为10,共有3个物品,体积分别是3、3、5,价值分别是6、6、9,那么你的方法取到的是前两个 物品,总价值是12,但明显最大值是后两个物品组成的15。而不是选择性价比最高的体积3价值6的物品,因为消耗(体积)是固定的,取了2个体积3的物品,剩下3体积的容量,无法再装其他物品(体积5).
例如http://www.cnblogs.com/SDJL/archive/2008/08/22/1274312.html提到的通过金矿模型介绍动态规划
简单描述问题,10000个人,10个金矿(编号0到9),每个金矿产出固定的不同数量的金子,需要固定的不同的人数(达到人数,要求必产出固定金子;否则无金子产出)

动态规划,具有“最优子结构”、“子问题重叠”、“边界”和“子问题独立”,当你发现你正在思考的问题具备这四个性质的话,那么恭喜你,你基本上已经找到了动态规划的方法。


答案是:

 当mineNum = 0且people >= peopleNeeded[mineNum]时 f(people,mineNum) = gold[mineNum]

当mineNum = 0且people < peopleNeeded[mineNum]时 f(people,mineNum) = 0

当mineNum != 0时 f(people,mineNum) =MAX[  f(people-peopleNeeded[mineNum], mineNum-1) + gold[mineNum]与f(people, mineNum-1) ]

前两个式子对应动态规划的“边界”,后一个式子对应动态规划的“最优子结构”请读者弄明白后再继续往下看。


组合数:如果用组合方法的话一共要考虑210次方种情况,也就是1024种情况

动态规划:每个问题的人数可以从0取到10000,而金矿数可以从0取到10,所以最多大约有10000 * 10 等于100000个不同的问题


非正式地,我们可以很容易得到动态规划所需时间,如果共有questionCount个相同的子问题,而每一个问题需要面对chooseCount种选择时,我们所需时间就为questionCount * chooseCount个常数。在金矿模型中,子问题最多有大概people * n (其中people是用于开采金矿的总人数,n是金矿的总数),因此questionCount = people * n,而就像国王需要考虑是采用左部下的结果还是采用右部下的结果一样,每个问题面对两个选择,因此chooseCount = 2,所以程序运行时间为 T = O(questionCount * chooseCount) =O(people * n),别忘了实际上需要的时间小于这个值(转者注:不知道怎么计算时间复杂度),根据所遇到的具体情况有所不同。


/*
=========程序信息========
对应题目:01背包之金矿模型
使用语言:c++
使用编译器:Visual Studio 2005.NET
使用算法:动态规划
算法运行时间:O(people * n) [people是人数,n是金矿数]
作者:贵州大学05级 刘永辉 
昵称:SDJL
编写时间:2008年8月
联系QQ:44561907
E-Mail:44561907@qq.com
获得更多文章请访问我的博客:www.cnblogs.com/sdjl
如果发现BUG或有写得不好的地方请发邮件告诉我:)
转载请保留此部分信息:)
*/

#include 
"stdafx.h"
#include 
<iostream>
#include 
<fstream>
using namespace std;

const int max_n = 100;//程序支持的最多金矿数
const int max_people = 10000;//程序支持的最多人数

int n;//金矿数
int peopleTotal;//可以用于挖金子的人数
int peopleNeed[max_n];//每座金矿需要的人数
int gold[max_n];//每座金矿能够挖出来的金子数
int maxGold[max_people][max_n];//maxGold[i][j]保存了i个人挖前j个金矿能够得到的最大金子数,等于-1时表示未知

//初始化数据 
void init()
{
    ifstream inputFile(
"beibao.in");
    inputFile
>>peopleTotal>>n;
    
for(int i=0; i<n; i++)
        inputFile
>>peopleNeed[i]>>gold[i];
    inputFile.close();
            
    
for(int i=0; i<=peopleTotal; i++)
        
for(int j=0; j<n; j++)
            maxGold[i][j] 
= -1;//等于-1时表示未知 [对应动态规划中的“做备忘录”]
        
}

//获得在仅有people个人和前mineNum个金矿时能够得到的最大金子数,注意“前多少个”也是从0开始编号的
int GetMaxGold(int people, int mineNum)
{
    
//申明返回的最大金子数
    int retMaxGold;

    
//如果这个问题曾经计算过  [对应动态规划中的“做备忘录”]
    if(maxGold[people][mineNum] != -1)
    {
        
//获得保存起来的值
        retMaxGold = maxGold[people][mineNum];
    }
    
else if(mineNum == 0)//如果仅有一个金矿时 [对应动态规划中的“边界”]
    {
        
//当给出的人数足够开采这座金矿
        if(people >= peopleNeed[mineNum])
        {    
            
//得到的最大值就是这座金矿的金子数
            retMaxGold = gold[mineNum];
        }
        
else//否则这唯一的一座金矿也不能开采
        {
            
//得到的最大值为0个金子
            retMaxGold = 0;
        }
    }
    
else if(people >= peopleNeed[mineNum])//如果给出的人够开采这座金矿 [对应动态规划中的“最优子结构”]
    {
        
//考虑开采与不开采两种情况,取最大值
        retMaxGold = max(GetMaxGold(people - peopleNeed[mineNum],mineNum -1+ gold[mineNum],
                                        GetMaxGold(people,mineNum 
- 1));
    }
    
else//否则给出的人不够开采这座金矿 [对应动态规划中的“最优子结构”]
    {
        
//仅考虑不开采的情况
        retMaxGold  = GetMaxGold(people,mineNum - 1);
    }
    
    
//做备忘录    
    maxGold[people][mineNum] = retMaxGold;
    
return retMaxGold;
}

int _tmain(int argc, _TCHAR* argv[])
{
    
//初始化数据
    init();
    
//输出给定peopleTotal个人和n个金矿能够获得的最大金子数,再次提醒编号从0开始,所以最后一个金矿编号为n-1
    cout<<GetMaxGold(peopleTotal,n-1);
    system(
"pause");
    
return 0;
}

 题目一:买书

       有一书店引进了一套书,共有3卷,每卷书定价是60元,书店为了搞促销,推出一个活动,活动如下:

      

       如果单独购买其中一卷,那么可以打9.5折。

       如果同时购买两卷不同的,那么可以打9折。

       如果同时购买三卷不同的,那么可以打8.5折。

      

       如果小明希望购买第1卷x本,第2卷y本,第3卷z本,那么至少需要多少钱呢?(x、y、z为三个已知整数)。

 

       当然,这道题完全可以不用动态规划来解,但是现在我们是要学习动态规划,因此请想想如何用动态规划来做?

 

       答案:

 

       1、过程为一次一次的购买,每一次购买也许只买一本(这有三种方案),或者买两本(这也有三种方案),或者三本一起买(这有一种方案),最后直到买完所有需要的书。

       2、最后一步我必然会在7种购买方案中选择一种,因此我要在7种购买方案中选择一个最佳情况。

       3、子问题是,我选择了某个方案后,如何使得购买剩余的书能用最少的钱?并且这个选择不会使得剩余的书为负数。母问题和子问题都是给定三卷书的购买量,求最少需要用的钱,所以有“子问题重叠”,问题中三个购买量设置为参数,分别为i、j、k。

       4、的确符合。

       5、边界是一次购买就可以买完所有的书,处理方式请读者自己考虑。

       6、每次选择最多有7种方案,并且不会同时实施其中多种,因此方案的选择互不影响,所以有“子问题独立”。

       7、我可以用minMoney[i][j][k]来保存购买第1卷i本,第2卷j本,第3卷k本时所需的最少金钱。

       8、共有x * y * z 个问题,每个问题面对7种选择,时间为:O( x * y * z * 7) =  O( x * y * z )。

       9、用函数MinMoney(i,j,k)来表示购买第1卷i本,第2卷j本,第3卷k本时所需的最少金钱,那么有:

              MinMoney(i,j,k)=min(s1,s2,s3,s4,s5,s6,s7),其中s1,s2,s3,s4,s5,s6,s7分别为对应的7种方案使用的最少金钱:

              s1 = 60 * 0.95 + MinMoney(i-1,j,k)

              s2 = 60 * 0.95 + MinMoney(i,j-1,k)

              s3 = 60 * 0.95 + MinMoney(i,j,k-1)

              s4 = (60 + 60) * 0.9 + MinMoney(i-1,j-1,k)

              s5 = (60 + 60) * 0.9 + MinMoney(i-1,j,k-1)

              s6 = (60 + 60) * 0.9 + MinMoney(i-1,j,k-1)

              s7 = (60 + 60 + 60) * 0.85 + MinMoney(i-1,j-1,k-1)