数字三角形(数塔问题)


其实动态规划本身并不是一个特定的算法,是一种用途广泛的问题求解方法,一种思想,一种手段。

1.1问题描述与状态定义


有一个有非负整数组成的三角形,第一行一个数字,下面各行除了最后一行外,每行的每个数字下面左右各一个数字。
如图示:

从第一行数字开始,每次只能走左下或右下一格,直到走到最后一行,把沿途的走过的所有数字加起来。
如何能使这个和最大?

【问题复杂度分析】如果熟悉回溯法,就会立即发现这是一个动态的决策问题:每次两个选择----左下或右下。
但是如果选择用回溯法解决此问题,惯常的问题就是效率太低:一格n层的数字三角形的完整路线有2^n条,所以当n很大时完全不能靠此方法。

因此为了提高效率,需要把此问题抽象:把所有的位置(i,j)抽象为一个个不同的状态,然后定义状态(i,j)的指标函数d(i,j)为从格子(i,j)出发时能得到的最大的和(包括此格子本身)。在这个定义下,原问题的解是d(1,1)。

如下图:



 
【状态转移分析】从格子(i,j)出发有两种决策。若往左走,则走到(i+1,j)后续要求“从(i+1,j)出发后能得到的最大和“这一问题,即是d(i+1,j)。类似的,往右做之后要求解d(i,j+1)。由于这两个选择自由可选,所以,应该选择较大的。即得到了所谓的状态转移方程:

d(i,j) = a(i,j) + max { d(i+1, j), d(i, j+1)  }

最优子结构:如果往左走,最好的情况是(i,j)格子里面的值a(i,j) 与”从(i+1,j)出发后能得到的最大和"之和, 注意"最大"两个字,如果从(i,j)出发到底部这部分都不是最大的话,加上a(i,j)也必然不是最大的。这就是最优子结构。或描述全局最优解包含局部最优解。


【总结】动态规划的核心是状态和状态转移方程。

1.2 解决方法:记忆化搜索与递推


方法一:递归计算

代码如下:(注意边界)
int d(int i, int j)
{
return a[i][j] +( i == n ? 0 : d(i+1, j) >? d(i, j+1) ) ;
}

如此计算正确只是效率依然不高,问题在于重复计算。就是一些格子被两个父节点所共有,所以,在递归的时候,便会被重复计算。



方法二: 递推计算


代码如下:
(注意此时重复边界的处理)
int  i, j;
for (j=1; j<=n; j++)
d[n][j] = a[n][j] ;
for (i=n-1; i>=1; i--)
for (j=1; j<=i; j++)
{
d[i][j] = a [i][j] + d[i+1][j] >? d[i][j+1] ;
}

时间复杂度显然是O(n^2),
可以如此计算的原因在于:
i是逆序枚举的,因此在计算d[i] [j] 之前,他所需要的d[i+1][j] 和 d[i][j+1] 已经计算出来了。

提示:在多数情况下,可以采用递推法计算状态转移方程。递推的关键在于边界和计算顺序。多数情况下,递推法的时间复杂度是:状态转移方程X每个状态的决策数X决策时间。


方法三:记忆化搜索

程序分为两部分。首先用数组函数memset();将数组整体置为-1,然后写递归函数:
int d(int i, int j)
{
if(d[i][j >= 0) return d[i][j] ;
return d[i][j] = a[i][j] +( i == n ? 0 : d(i+1, j) >? d(i, j+1) ) ;
}

依然是递归函数,同时把计算结果存在数组d中。题目说各个数字均为非负数,因此如已经计算过某个d[i][j],那么期应该是非负数。
这样只需要把所有的数组元素初始化为负数,如-1,就可以知道是否计算过d[i][j].

注意:不要忘记把结果存在数组d[i][j]中。根绝c语言的”赋值语句本身有返回值“的规定,可以把保存d[i][j]的工作整合在函数的返回语句中。

上述的方法三称为记忆化,虽然不像地推算法那样显示的指明了计算的顺序,但是任然可以保证每个节点只访问了一次。

由于i和j都在1~n之间,所以所有的不同的节点之间一共有O(n^2)个。即是时间复杂度。

提示:可以用记忆化搜索的方法计算状态转移方程。采用记忆化搜索时,不必事先确定个状态的计算顺序,但需要记住每个状态是否计算过。


1.3程序实战练手

HDOJ--2084


未完待续...................
下一节:DAG上的(DP)

算法入门系列一--DP初步的更多相关文章

  1. 数据结构与算法入门系列教程-C#

    数据结构与算法入门系列教程 (一)为啥要学习数据结构与算法 曾经我也以为自己很牛逼,工作中同事也觉得我还可以,领导也看得起我,啥啥啥都好,就这样过了几年,忽然发现自己学新东西没劲.时代都变了,而我还只 ...

  2. 算法入门系列2:k近邻算法

    用官方的话来说,所谓K近邻算法(k-Nearest Neighbor,KNN),即是给定一个训练数据集,对新的输入实例,在训练数据集中找到与该实例最邻近的K个实例(也就是上面所说的K个邻居), 这K个 ...

  3. 算法入门系列1:k-means

    k-means是一种无监督学习算法,用于聚类. 下图(来自http://www.cnblogs.com/jerrylead/archive/2011/04/06/2006910.html)展示了k-m ...

  4. 数据挖掘入门系列教程(二)之分类问题OneR算法

    数据挖掘入门系列教程(二)之分类问题OneR算法 数据挖掘入门系列博客:https://www.cnblogs.com/xiaohuiduan/category/1661541.html 项目地址:G ...

  5. 数据挖掘入门系列教程(三)之scikit-learn框架基本使用(以K近邻算法为例)

    数据挖掘入门系列教程(三)之scikit-learn框架基本使用(以K近邻算法为例) 简介 scikit-learn 估计器 加载数据集 进行fit训练 设置参数 预处理 流水线 结尾 数据挖掘入门系 ...

  6. 数据挖掘入门系列教程(四点五)之Apriori算法

    目录 数据挖掘入门系列教程(四点五)之Apriori算法 频繁(项集)数据的评判标准 Apriori 算法流程 结尾 数据挖掘入门系列教程(四点五)之Apriori算法 Apriori(先验)算法关联 ...

  7. 数据挖掘入门系列教程(五)之Apriori算法Python实现

    数据挖掘入门系列教程(五)之Apriori算法Python实现 加载数据集 获得训练集 频繁项的生成 生成规则 获得support 获得confidence 获得Lift 进行验证 总结 参考 数据挖 ...

  8. 前端学习 node 快速入门 系列 —— 初步认识 node

    其他章节请看: 前端学习 node 快速入门 系列 初步认识 node node 是什么 node(或者称node.js)是 javaScript(以下简称js) 运行时的一个环境.不是一门语言. 以 ...

  9. vue 快速入门 系列 —— 初步认识 vue

    其他章节请看: vue 快速入门 系列 初步认识 vue vue 是什么 Vue (读音 /vjuː/,类似于 view) 是一套用于构建用户界面的渐进式框架. 所谓渐进式,就是你可以一步一步.有阶段 ...

随机推荐

  1. hisi平台mii网络模式和rmii网络模式的uboot制作

    MII网络uboot编译说明 一:编译生成默认的uboot1. 进入到uboot目录a. cd /home/satan/Hi3518_SDK_V1.0.7.0/osdrv/uboot2. 新建临时文件 ...

  2. Delphi RICHEDIT中插入图象

    unit InsRich;interfaceuses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms ...

  3. HDU5790 Prefix 字典树+主席树

    分析:这个题和spoj的d_query是一个题,那个是求一段区间里有多少个不同的数字,这里是统计有多少个不同的前缀 用字典树进行判重,(和查询不同的数字一样)对于每个不同的前缀,只保留它最后一次出现的 ...

  4. HDU 5278 PowMod 数论公式推导

    题意:中文题自己看吧 分析:这题分两步 第一步:利用已知公式求出k: 第二步:求出k然后使用欧拉降幂公式即可,欧拉降幂公式不需要互质(第二步就是BZOJ3884原题了) 求k的话就需要构造了(引入官方 ...

  5. BLOCK专题

    >>定义并使用一个block    返回值(^名字)(参数列表) =^返回值类型(参数列表){表达式};  其中返回值和参数列表可以神略 ,最简单的block是  ^{xxxx}; voi ...

  6. 【C# C++】C#中调用msvcr100.dll中的_beginthreadex函数

    msvcr100.dll是VS2010的C运行时库DLL, _beginthreadex开启子线程的函数就在这个DLL里面实现 unsigned long _beginthreadex(    voi ...

  7. MFC字体与文本输出

    字体 成员函数 1.CFont( ); 构造一个CFont对象.此对象在使用之前应该先使用CreateFont.CreateFontIndirect.CreatePointFont或CreatePoi ...

  8. Python面向对象1

    一.类和对向 面向过程和面向对象的编程 面向过程的编程:函数式编程,C程序等 面向对象的编程:C++,JAVA,Python等 类和对象:是面向对象中的2个重要概念 类:是事物的抽象,比如汽车: 对象 ...

  9. Navicat 远程连接SQL Server 2014 Express 报08001错误

    场景:Navicat 远程连接SQL Server 2014 Express 报08001错误,经查验防火墙端口1434,1433已经打开 过程:1. 一开始觉得是连接名称问题,使用IP地址或者主机名 ...

  10. 《Java数据结构与算法》笔记-CH2无序数组

    /** * 本章目标: * 1.自制数组类 * 2.有序数组:按关键字升降序排列:二分法查找 * 3.分析有序数组.大O表示法 */ /** * 自制数组类 书中有的地方有错误,本程序以修改 */ c ...