程序 = 数据结构+算法。所以,不了解算法的程序猿不是一个合格的程序猿,对算法痴迷是程序员的基本职业修养。哈哈,按照惯例,先扯下淡。
下面来一起看下什么是动态规划,以下绿色字来自百度百科:
动态规划(dynamic programming)是运筹学的一个分支,是求解决策过程(decision process)最优化的数学方法。20世纪50年代初美国数学家R.E.Bellman等人在研究多阶段决策过程(multistep decision process)的优化问题时,提出了著名的最优化原理(principle of optimality),把多阶段过程转化为一系列单阶段问题,利用各阶段之间的关系,逐个求解,创立了解决这类过程优化问题的新方法——动态规划。1957年出版了他的名著Dynamic Programming,这是该领域的第一本著作。
动态规划问世以来,在经济管理、生产调度、工程技术和最优控制等方面得到了广泛的应用。例如最短路线、库存管理、资源分配、设备更新、排序、装载等问题,用动态规划方法比用其它方法求解更为方便。
虽然动态规划主要用于求解以时间划分阶段的动态过程的优化问题,但是一些与时间无关的静态规划(如线性规划、非线性规划),只要人为地引进时间因素,把它视为多阶段决策过程,也可以用动态规划方法方便地求解。
动态规划程序设计是对解最优化问题的一种途径、一种方法,而不是一种特殊算法。不象搜索或数值计算那样,具有一个标准的数学表达式和明确清晰的解题方法。动态规划程序设计往往是针对一种最优化问题,由于各种问题的性质不同,确定最优解的条件也互不相同,因而动态规划的设计方法对不同的问题,有各具特色的解题方法,而不存在一种万能的动态规划算法,可以解决各类最优化问题。因此读者在学习时,除了要对基本概念和方法正确理解外,必须具体问题具体分析处理,以丰富的想象力去建立模型,用创造性的技巧去求解。我们也可以通过对若干有代表性的问题的动态规划算法进行分析、讨论,逐渐学会并掌握这一设计方法。
看完了吧,有点儿晕了吧,正常,谁看都晕,我们来通过实战感受一把吧:
如下图所示一个金字塔,请选择一条路使得从塔顶和路走到塔底(不得回头哦,跟人生一样,没有后悔药卖的)一路所过数字之和最大。
如何解决?
可能的最简单的想法是从上到下,每一步都选下一步可选方案中的最优方案方案不就行了么。这就是所谓的贪婪算法的基本思想。但是这样的思路真的可以得到正确的答案么?最终得到的路线一定是最优的路线么?
结论是末必。要知道每一步的局部最优未必是全局最优哦。就好比高中时,我们每个同学的梦想都是可以考上名牌大学,这当时在大家看来是下步路最优的选择了。然而,十年后再回聚首,就会发现当年没考上大学的同学未必与上过大学的同学混得差哦。三十年河西,三十年河东。这就是局部最优未必是全局最优最好的诠释了,好好回味一下。
回到问题,注意到我们的题目毕竟跟我们的人生选择还是不一样的。我们所做的每一次人生选择大部分都是按照贪婪算法来的,原因就在于人生选择是一次性,只能不停的沿着一条路往前走到底,我们的可视范围仅限于我们下一步的选择。当然少数特别有选见的人,会看得更远一些,为了长远利益放弃当前利益,就如当年比尔盖茨放弃大学文凭辍学办公司一样。而对于我们这个金字塔的题目,哈哈,太easy了,完全不用那么究竟,因为我们是可以遍历的哦。所有的路都遍历一遍,熟优熟劣自然一目了然。
但是.....如何遍历,这又是一问题。
相信了解二叉树的你,看到题目中的图,提起遍历第一时间想到的就是二叉树递归遍历了吧。但是需要提醒你的是,有两个问题是需要考虑的哦,一个是复杂度,相对于图中的节点数n,初步估计约为o(2^(n^(1/2))),这也够受了的。另一个是调用栈,这个塔有多少层,调用栈就有多么的深,这也不是闹着玩的,小塔还搞得定,高塔就彻底完了。
怎么办?继续分析!
注意到,要想自上而下到达塔中的某个点,要么我们从塔中该点的上一层下来就只有两个选择。例如,想要到达10这个节点,那么只有要么从18下来,要么从9下来。也就是说最优路径要么经过18,要么经过9。我们把10看作是雇员,18和9看作是10的潜在老板,至于10要选择给哪个老板打工,那就看谁能提供更多的money了,至于这个money,18和9这两个老板是怎么搞来的,这才不是雇员10关心的呢,雇员只认money,这就是市场。也就是所谓的无后效性。(这里的money,就是我们之前提出的所经过的链路之上的所有数字之和,你懂的)。
接下来,问题转化了,雇员10最多可以拿到多少money,取决于了18和9两个老板手里可以最多可以拿到多少money。而18,9相同于自己的上层老板而言,自己又于雇员了。同同样的分析过程。问题再次向上转化。
整过过程就是这样了,正过来想,那我们只需要从上到下,依次计算出每个老板手里最多可以拿到多少资源就可以了。计算出了第n层塔中各个节点的最大链路和,第n+1层的也就马上计算出来了。
示例代码如下:
#include <stdio.h>
#define MAX_SIZE (5)
#define MAX_OF(A,B) ((A)>(B)?(A):(B))
int main()
{
int node[MAX_SIZE][MAX_SIZE] = {
{9},
{12,15},
{10,6,8},
{2,18,9,5},
{19,7,10,4,16}
};
int money[MAX_SIZE][MAX_SIZE] = {0};
int MaxMoney = 0;
int i,j;
money[0][0] = node[0][0];
for(i = 1;i<MAX_SIZE;i++)
{
money[i][0] = money[i-1][0] + node[i][0];
for(j = 1;j<i;j++)
{
money[i][j] = MAX_OF(money[i-1][j-1],money[i-1][j]) + node[i][j];
}
money[i][i] = money[i-1][i-1] + node[i][i];
}
MaxMoney = money[MAX_SIZE-1][0];
for(i = 0;i<MAX_SIZE;i++)
{
MaxMoney = MAX_OF(money[MAX_SIZE-1][i],MaxMoney);
}
printf("Max link Value: %d
",MaxMoney);
return 0;
}
最终计算结果为:59
至于如何把具体链路打出来,留给大家拍砖顶贴了。
以上就是所谓的动态规划思想,简单吧,再悟一下。
算法最重要的是思想。思想是定的,应用的具体方法是活的,活学活用,才是王道。
掌声响起来,哈哈。
作者:张亮
往期文章精选
javascript基础修炼(13)——记一道有趣的JS脑洞练习题