关于动态规划的理解

动态规划是个比较有趣的算法,第一次接触动态规划也是从一个比较特别的教程开始的,这里贴出原文地址http://blog.csdn.net/woshioosm/article/details/7438834
看完原文回到这里,其实我以为不少像我这种C语言刚刚入门的人,只理解到了动态规划的转移方程,以原文中的例子为例:
当mineNum = 0且people >= peopleNeeded[mineNum]时 f(people,mineNum) = gold[mineNum]
当mineNum = 0且people < peopleNeeded[mineNum]时 f(people,mineNum) = 0
当mineNum != 0时 f(people,mineNum) = f(people-peopleNeeded[mineNum], mineNum-1) + gold[mineNum]与f(people, mineNum-1)中的较大者
其中,前两个式子对应动态规划的“边界”,后一个式子对应动态规划的“最优子结构”。
菜鸟的我以为这个比较容易实现啊,因而有了下面的代码:
 1 int goldGet(int People, int mineNum)  2 {  3      if(mineNum == 0)  4  {  5           /*考虑最后一座金矿是,若是剩余开采金矿的总人数  6  多余或者等于须要的人数,则返回该金矿的金子数  7  目,不然返回0。*/
 8           if(People >= PeopleNeeded[mineNum])  9  { 10                return Gold[mineNum]; 11  } 12           else
13  { 14                return 0; 15  } 16  } 17      else
18  { 19           int m, n; 20           /*考虑第mineNum座金矿,第一种状况:开采该座金矿所能得到的最多的金子数目, 21  为该座金矿的金子数目和剩下的人开采剩下的金矿所获得的最多的金子数目的和 22  第二种状况:部开采该座金矿所得到的最多的金子数目,是目前全部的人用来开 23  采剩余的金矿得到的最多的金子数目。返回两种状况下金子数目较多的一种状况 24  的金子数目。*/
25           if(People >= PeopleNeeded[mineNum]) 26  { 27                m = Gold[mineNum] + goldGet(People - PeopleNeeded[mineNum], mineNum - 1); 28  } 29           else
30  { 31                m = goldGet(People, mineNum - 1); 32  } 33           n = goldGet(People, mineNum - 1); 34 
35           return m >= n ? m : n; 36  } 37 }
后面的关于时间复杂度和空间复杂度的优化对我像我这种境界的人来讲,这个理解有点困难,我就暂且自我安慰一下,告诉本身已经理解了动态规划,而且把文章最后的金矿问题作了,而且经过了测试数据,所有代码以下:
#include <stdio.h> #include <stdlib.h>

#define MaxNum 500

int PeopleNeeded[MaxNum]; //PeopleNeeded[i]表示第i座金矿须要的开采人数
int Gold[MaxNum]; //Gold[i]表示第i座金矿开采后获得的金子数目

int goldGet(int People, int mineNum) { if(mineNum == 0) { /*考虑最后一座金矿是,若是剩余开采金矿的总人数 多余或者等于须要的人数,则返回该金矿的金子数 目,不然返回0。*/
          if(People >= PeopleNeeded[mineNum]) { return Gold[mineNum]; } else { return 0; } } else { int m, n; /*考虑第mineNum座金矿,第一种状况:开采该座金矿所能得到的最多的金子数目, 为该座金矿的金子数目和剩下的人开采剩下的金矿所获得的最多的金子数目的和 第二种状况:部开采该座金矿所得到的最多的金子数目,是目前全部的人用来开 采剩余的金矿得到的最多的金子数目。返回两种状况下金子数目较多的一种状况 的金子数目。*/
          if(People >= PeopleNeeded[mineNum]) { m = Gold[mineNum] + goldGet(People - PeopleNeeded[mineNum], mineNum - 1); } else { m = goldGet(People, mineNum - 1); } n = goldGet(People, mineNum - 1); return m >= n ? m : n; } } int main() { int People; //开采金矿的总人数
     int GoldNum; //金矿数目
     int GoldGet; //最终可以获取的金子的总数


     int i, j; //用来进行的便利的变量
 scanf("%d %d", &GoldNum, &People); for(i = 0; i < GoldNum; i ++) { scanf("%d %d", &PeopleNeeded[i], &Gold[i]); } GoldGet = goldGet(People, GoldNum); printf("%d\n", GoldGet); return 0; }
固然事情尚未结束,由于我以为我理解了动态规划,因而跑到hihocoder上跃跃欲试(附hihocoder地址:http://hihocoder.com/,一个很不错的OJ),菜鸟的我只敢尝试01背包那种简单的题目。把代码稍微修改了一下,符合了题目的输入和输出,很兴奋的提交了,结果"Time Limit Exceeded"。很郁闷,看来我仍是没有真正的理解动态规划,还须要把优化的那部分看懂,还好hihocoder上每道题都会对用到的算法进行讲解,这回稍微弄懂了,修改了代码提交,而后AC了,这里先给出代码:
/*01背包问题*/ #include <stdio.h> #include <stdlib.h> #include <memory.h>

int max(int x, int y) { return x >=y ? x : y; } int main() { int N; //表示奖品的个数
     int M; //表示奖券数
     
     int need[500]; //need[i]表示第i个奖品须要的奖券数
     int value[500]; //value[i]表示第i个奖品的评分值
         
     int best[100000]; //best[j]表示对于i个奖品时j张奖券的最大评分值
     memset(best, 0, sizeof(best)); //初始化best的全部元素为0

     int i, j; //程序进行遍历的变量
 scanf("%d %d", &N, &M); for(i = 0; i < N; i ++) { scanf("%d %d", &need[i], &value[i]); } for(i = 0; i < N; i ++) { for(j = M - 1; j >= need[i]; j --) { best[j] = max(best[j], best[j - need[i]] + value[i]); } } printf("%d\n", best[M - 1]); return 0; }
嗯。。。这里代码貌似要断了不少,对于前面的输入输出你们都看得懂,主要的是最后一个循环,其实这个循环也比较好理解,在我贴的第一个比较low的程序里面,转移方程是用递归实现的,这里改为了双重循环,实际上是一个意思。最难理解的是:
best[j] = max(best[j], best[j - need[i]] + value[i]);
为了理解这句话须要一步步的来,将一开始比较low的递归搞成双重循环(这里不是说递归比较low,而是说我写的比较low)
for(i = N- 1; i > 0; i --) { for(j = M - 1; j >= need[i]; j --) { best[i, j] = max(best[i - 1][ j], best[i - 1][j - need[i]] + value[i]); } }
这里i > 0是由于i = 0没有讨论的必要,其实严格的来讲这个循环和前面的递归并不彻底同样,这里的时间复杂度更低一些,由于这里的最高收益是用数组进行存储的,对于某次循环中计算出了best[x][y]的值后,后面若是还须要用到就不须要再计算了,而以前的递归用的是函数,并不能存储数值,效率更低。
这里咱们能够在复习一下转移方程,
当i != 0时 bes[i][j] 是 best[i - 1][j - need[i]] + value[i]与best[i - 1][j]中的较大者
这里咱们能够发现best[Ai][Aj]依赖于best[Bi][Bj]时,确定有Ai = Bi + 1,以及Aj >= Bj。
举个简单的例子,咱们求best[5][j]的时候只与best[4][j]和best[4][j - need[5]]有关,对于前面的best[0][k],best[1][k],...best[3][k],其中k = 0,1,...M-1没有任何关系,那么咱们就没有必要用那么复杂的存储。而且j的取值是从M-1到0,考虑到Aj >= Bj的规律,计算best[i][j]的时候,best[i-1][j+1,...M-1]已经没有任何利用价值了,这些空间也是多余的,怎么办呢,其实计算best[i][j - 1]的时候best[i - 1][j]就没有价值了,那么咱们计算best[i][j]的结果就能够直接存放到best[i - 1][j]的地址上,这样的话,咱们的存储其实只须要一个大小为M的一维数组。而且是best[i][j]覆盖beat[i - 1][j],因此循环的时候i要从0到N-1,
因而循环就能够写成
for(i = 0; i < N; i ++) { for(j = M - 1; j >= need[i]; j --) { best[j] = max(best[j], best[j - need[i]] + value[i]); } }
这个就是咱们最后的结果。
相关文章
相关标签/搜索