如何实现动态规划?——TWO

忙碌了一天,是时候继续动态规划的问题了,昨天写了一些对动态规划的入门理解,尽管我文笔略差,但是自我感觉打得比方还是比较形象的;同时也转载了一个讲述动态规划的博文,但是在今天通读后,感觉这篇文章优点和缺点参半。
这篇文章,优点是以一个十分形象的故事引入了动态规划,缺点却是他的故事最后把人讲蒙圈了,(也可能是个人智商问题,反正我是没有看懂为何最后要考虑到那挖矿的一万个人的每个人的分配过程,主要是讲这部分时脱离了代码去讲,一时间让初学者无法理解),给初学者徒增疑惑(有兴趣的人可以去深究,附上地址:http://www.cnblogs.com/sdjl/articles/1274312.html)。
总的来说,除了其博文的第二部分在我个人看来有些败笔(因为脱离了代码去讲实际的问题),但是整体还是个很好的文章,可以很轻松的将你带进动态规划的世界中……

我废话也该少说了,是时候进一步整理总结我自己的对动态规划的学习笔记了^_^。

首先,我们先来说说要实现动态规划,我们应该先考虑的动态规划的哪几个要点?

    要点一——由大化小,寻找最优子结构(构造状态转移方程)
    我们拿到一个动态规划的问题,首先我们应该考虑的是这道题什么是母体结构(我称这个题的整体的最初始最直接的结构为母体结构),然后我们要去思考如何将母体结构拆解,分成两个子结构(也许可以分成更多的子结构,因题意而定),拆解后,再进一步将子结构拆解,以此循环,直至不能再拆解后,我们即可得到一个结构最小的最优子结构,(用树的模型来思考),然后我们开始回升,由最底层的叶子开始上升,一步步向上合并,最终将每一部分的最优子结构合并再一起,使得最终我们再树的根部得到最优解。这里类似于排序算法中的归并排序的思想,也可以说是分治法的思想。当然说到如何拆解为子结构,通常是要去找到合适的递推公式,但是需要注意,递推公式的求解方法并不是动归的本质,这里就不详解为什么了,自己百度去,百度可以解决你的九成问题-_-#
    要点二——子结构重叠,子问题重复(用空间换时间)
    我们用树来理解拆分子结构的过程,那么,在树中的两个可能完全不相干的子树上存的数据可能一样,这就可能导致子结构重叠,子问题重复(注意是可能,并不排除数据有时候特殊)。这里我们就要来思考一下,子结构上的数据相同,这通常是什么导致的?很简单,我们都知道,子结构上存的每一个数据都是由底层结构回升归并得到的,那么我们既然子结构存的数据相同,那么说明我们子结构下的子结构很有可能相同,这就清楚了。当我们的子结构相同时,我们通过第一回动归可以得到子结构的数据,但是第二回碰到这个相同的子结构时,就会浪费时间去再次规划这个子结构,实属浪费。这就是子结构重叠,子问题重复的问题,那要问,怎么才能解决这个问题呢?相信聪明的人已经想到了,用空间换时间,再直接点就是我们需要用数组之类的来存储第一次规划好的东西,方便第二次调用。
    要点三——什么时候说明问题无法再分解为子结构(边界)
    还是用树来说,当我们碰到叶子的时候说明问题已经被细分为最小的子问题,子结构了,不能再次细分,需要开始回升归并在一起,那么这里说的叶子其实就是动归的边界问题,边界是干什么的?就是用来终止问题的进一步细分的,如果没有边界,那么我们的动归就成了死循环,永远无法从这个循环中出来,造成计算机资源伤亡惨重!
    要点四——独立的问题才是可以分开做决策的问题(只有无后效应性,才能实现动态的规划,动态的决策)
    如果我们的问题存在后效应性,那么我们的每一个决策都要受之前的决策的影响,我们的决策也就不能称为完全的自由,更不能称之为动态的,因为有后效应性就相当于牵线木偶,我们看不到后边控制的线,但是我们确实没有自己做决策的权利,要想实现每一步都是自由的做规划,那么就必须没有后效应性的存在,至于如何判断有无后效应性,我想,世界上最好的老师也只能给你讲到这里了,真正的判断上,没有固定的准则,需要自己通过观察和练习才能慢慢体会到什么才算是无后效应性,等你练到不错的程度,那么可能给你一道题,你只需要读一遍不用深思就可以知道他是否有后效应性了。
    (**************以上四点至为重要***************)
    要点五——备忘
    这里的备忘问题,就是上述的要点二的解决方案——空间换时间,我们通过数组来存下每一个初次规划的子问题,然后在二次调用时直接使用数组中备案好的数据即可,有点像记忆化搜索,其实核心就是记忆两个字,用数组去记忆最好。
    要点六——时间分析
    每个学算法的人,都离不开时间分析,即计算时间复杂度(当然有时候我们也需要考虑空间复杂度,但是在动归问题中,我们通常要就解决的是超时问题,所以这里我们通常只考虑时间复杂度)。我们需要去根据算法判断自己的动归的复杂度,并体会其中的优越性,至于具体的怎么去计算时间复杂度,这个就要靠你自己去看书了,数据结构中会详细讲解,当然学习是个关联的事,学数据结构前,建议你学点离散数学,不然会学着有点吃力,你也可以只学习数据结构中的某一部分,但是作为长久之计的说,学习是个系统的事,还是系统的学习会更好点!

感觉我又是扯了很多没用的理论,但是这其中包含了我对动归的一些深入理解和调侃,也不算是纯粹的理论知识,毕竟纯粹的理论知识是个讨人厌的东西。
可能没有几个人会仔细去看上边的总结,但是写这个的过程中,无疑是对自己的一种强化,谁叫这是我一个一个字打出来的呢?接着就该再举一道经典的动归问题来为高逼格的算法增色了,不然就会有人说我只是纸上谈兵,没学到什么真本事(实话说,就算举了这个例子,何尝不是纸上谈兵呢?这就是知识啊,对多数人枯燥,对少数人却生机盎然的东西)

TWO:01背包问题
给定N中物品和一个背包。物品i的重量是Wi,其价值位Vi ,背包的容量为C。问应该如何选择装入背包的物品,使得转入背包的物品的总价值为最大??
在选择物品的时候,对每种物品i只有两种选择,即装入背包或不装入背包。不能讲物品i装入多次,也不能只装入物品的一部分。因此,该问题被称为0-1背包问题(0即是不装,1即是装,装或是不装,那你看着办吧,��)。
问题分析
令V(i,j)表示在前i(1<=i<=n)个物品中能够装入容量为就j(1<=j<=C)的背包中的物品的最大价值,则可以得到如下的动态规划函数:     //初始化边界
(1)   V[i][0] = V[0][j] = 0           //当背包的容量为零或者物品数量为零,则最大价值为零
(2)   V[i][j] = V[i-1][j]  j < w[i]     //当第i个物品的重量超过背包的容量,那么第i个物品肯定不在背包中,所以前i个物品能装在背包中的最大价值和前i-1个物品结果一样
      V[i][j] = max{V[i-1][j], V[i-1][j-w[i]]+vi)} j > w[i]     //当第i个物品的重量小于背包容量时,需要考虑第i个是否装入,则需要取两种情况中价值最大的一种
解决方案
#include <stdio.h>
int V[200][200];     //前i个物品装入容量为j的背包中获得的最大值

int max(int a,int b)
{
    if(a>=b)
        return a;
    else 
        return b;
}

int KanpSack(int n, int *w, int *v, int *x, int C)  //x为物品的选取状态
{
    int i, j;
    //初始化
    for (i = 0; i <= n; i++)
        V[i][0] = 0;
    for (j = 0; j <= C; j++)
        V[0][j] = 0;
    //动归
    for (i = 1; i < n; i++)
    {
        for (j = 0; j <= C; j++)
        {
            if (j < w[i])
                V[i][j] = V[i - 1][j];
            else
                V[i][j] = max(V[i - 1][j], V[i-1][j - w[i]] + v[i]);
        }
    }
    //j = C; //通过上述的动归,j已经等于了C,无需再度赋值
    //确定哪些物品装入口袋中
    for (i = n -1; i >= 0; i--)
    {
        if (V[i][j] > V[i - 1][j])
        {
            x[i] = 1;
            j = j - w[i];
        }
        else
            x[i] = 0;
    }
    printf("选中的物品是:\n");
    for (i = 0; i < n; i++)
        printf("%d ", x[i]);
    printf("\n");
    return V[n - 1][C];
}

int main()
{
    int s;       //获得的最大价值
    int w[15];   //物品的重量
    int v[15];   //物品的价值
    int x[15];   //物品的选取状态
    int n = 5, i;
    int C;       //背包最大容量

    printf("请输入背包的最大容量:\n");
    scanf("%d", &C);

    printf("输入物品数:\n");
    scanf("%d, &n");
    printf("请分别输入物品的重量:\n");
    for (i = 0; i < n; i++)
        scanf("%d", &w[i]);

    printf("请分别输入物品的价值:\n");
    for (i = 0; i < n; i++)
        scanf("%d", &v[i]);

    s = KnapSack(n, w, v, x, C);

    printf("最大物品价值为:\n");
    printf("%d\n", s);

    return 0;
}
这是一个十分经典的动态规划问题,如果可以理解透彻这道题,那么你就差不多算是理解了动态规划了+_+。。。。。。
OVER!!! 
全部评论

相关推荐

牛客722552937号:新锐之星有点坑爹,特别是对男的
点赞 评论 收藏
分享
点赞 收藏 评论
分享
牛客网
牛客企业服务