动态规划学习——算法原理、过程与应用

一、基本概念

动态规划(Dynamic Programming,DP)是运筹学的一个分支,是求解决策过程最优化的过程。

动态规划算法通常用于求解最优解的问题,在这类问题中有很多可行解,每个可行解都对应一个值(cost or reward),希望求出最佳解(最小 cost or 最大 reward)。动态规划的基本思想与分治法类似,但差异在于动态规划法可以解决划分的子问题不独立的情况。若在划分子问题不独立的情况下使用分治法会导致子问题数目太多,许多子问题经过重复计算。DP 中将已解决的子问题答案保存到表中,从而减少算法的时间冗余。

1.多阶段决策问题

动态规划适用于多阶段决策问题。

若一类活动过程可以分为若干个互相联系的阶段,每个阶段都需要作出决策,每个阶段的决策确定常常影响到下一阶段的决策,从而完全确定一个过程的活动路线,则称其为多阶段决策问题。

各个阶段的决策构成决策序列,称为一个策略。每个阶段都有若干决策可选择,使得有大量可选择策略。每个策略的效果不同,多阶段决策问题就是要在可选的策略中选取一个最优策略,使得在预定的标准下达到最好的效果。

2.适用条件

(1)最优化原理(最优子结构性质)

最优化策略具有如下的性质:不论过去状态和决策如何,对之前的决策形成的状态而言,剩下的所有决策必须构成最优策略。简言之,一个最优化策略的子策略总是最优的,一个问题满足最优化原理时又称其具有最优子结构性质。

即最终的大问题的最优解可以通过逐个求解小问题最优解推出。

比如规划确定起点和目的地的需要中转的航班路线时,假设要从海南 → \to 黑龙江,最优航线是海南 → \to 安徽 → \to 北京 → \to 辽宁 → \to 黑龙江,那么从安徽 → \to 黑龙江的最优航线一定是这个解的一部分,即安徽 → \to 北京 → \to 辽宁 → \to 黑龙江。那么就称最优航线确定的问题满足最优化原理。

(2)无后效性

将各阶段按照一定的次序排列好后,对于某个给定的阶段状态,给定阶段前的各阶段状态不会直接影响其未来的决策,而只能通过当前状态。即每个状态都是对过去历史的完整总结,这就是无后效性。

即如果给定某一状态,则在这一阶段以后过程的发展不受这阶段以前各段状态的影响。

同时满足以上最优化原理和无后效性时,可以使用动态规划求解问题。


二、算法步骤

  1. 通过优化问题的目标,确定动态规划数组的定义,即 dp[i] 在当前问题中具体表示什么。
  2. 找出数组元素之间关系,比如 dp[n] = dp[n-1] + dp[n-2]。
  3. 找出当前问题的初始值

三、算法例题

1.爬楼梯问题(一维动态规划数组)

在这里插入图片描述

假设这样一个情境:你准备上楼梯,一次可以走1级台阶,也可以走2级。求上一个n级的台阶总共有多少种走法。

(1)确定动态规划数组定义

要求上n级台阶总共多少种走法,不妨直接将动态规划数组 dp[i] 定义为上 i 级台阶总共的走法数量。即最终要求的结果就是 dp[n] 。

(2)数组元素间的关系

要找出数组元素之间的关系,需要将规模较大的问题划分成多个规模较小的问题。一般来说,这一步是找到用 dp[i-1]、dp[i-2] 等规模较小的动态规划数组值表示 dp[i] 这个规模较大的动态规划数组值

结合题目和上一步确定的动态规划数组的定义,dp[i] 是上 i 级台阶总共的走法数量,因为只能一次上1级或者上2级,上 i 级台阶的情况就可以分为两类:

  • 从 i - 1 级台阶走 1 级后到达第 i 级
  • 从 i - 2 级台阶走 2 级后到达第 i 级

上到第 i 级台阶的总共走法数量应当是上述两种情况走法数量的和, 即得到: d p [ i ] = d p [ i − 1 ] + d p [ i − 2 ] dp[i] = dp[i-1] + dp[i-2] dp[i]=dp[i1]+dp[i2]

(3)确定初始状态

i = 0 或 1 时, d p [ i ] = d p [ i − 1 ] + d p [ i − 2 ] dp[i] = dp[i-1] + dp[i-2] dp[i]=dp[i1]+dp[i2]出现数组下标为负数的情况,故需要单独对 i = 0 或 1 的情况进行数组赋值。

不难得知: d p [ 0 ] = 0 , d p [ 1 ] = 1 dp[0] = 0, dp[1] = 1 dp[0]=0,dp[1]=1

而验证时发现 i = 2 时,根据递推式有: d p [ 2 ] = d p [ 1 ] + d p [ 0 ] = 1 dp[2] = dp[1] + dp[0] = 1 dp[2]=dp[1]+dp[0]=1,而实际情况应当是 d p [ 2 ] = 2 dp[2] = 2 dp[2]=2,故也设定 i = 2 的初始状态: d p [ 2 ] = 2 dp[2] = 2 dp[2]=2

注意:在确定初始状态时不要遗漏,应当多检查几项的合理性。

至此,可以得到问题求解的函数代码:

int goUpstairs( int n )
{
    if(n <= 1)
    return n;
    // 创建dp数组,用于存储较小规模问题的解
    int[] dp = new int[n+1];
    // 给出初始值
    dp[0] = 0;
    dp[1] = 1;
    dp[2] = 2;
    // 通过数组元素间关系来计算 dp[i]
    for(int i = 2; i <= n; i++){
        dp[i] = dp[i-1] + dp[i-2];
    }
    // 返回最终结果
    return dp[n];
}

2.数字三角形(二维动态规划数组)

有如下一个数字三角形:


3 8
8 1 0
2 7 4 4
4 5 2 6 5

从第一行(7)出发,每个节点都可以选择向下走或向右下走,一直走到底层。试设计一种算法,计算从三角形顶端到底部的一条路径,使该路径经过的数字总和最大。

(1)确定动态规划数组定义

为方便数组索引,将数字三角形的第 1 行表示为第 0 行,要求从第 0 行出发到第 i 行、第 j 列的最大数字总和,不妨直接定义二维动态规划数组 dp[ i ][ j ] 定义为从第 0 行出发到第 i 行、第 j 列的最大数字总和。即最终要求的结果就是 dp[4][j] 。

(2)数组元素间的关系

结合题目和上一步确定的动态规划数组的定义,dp[ i ][ j ] 是从第 0 行出发到第 i 行、第 j 列的最大数字总和,因为每次只能向正下方和右下方移动,到达[ i ][ j ]位置的情况就可以分为两类:

  • 从 [ i - 1 ][ j ] 向下方移动后到达 [ i ][ j ]
  • 从 [ i - 1 ][ j - 1 ] 向右下方移动后到达 [ i ][ j ]

到达 [ i ][ j ] 位置的最大和应当是上述两种情况最大和的较大值加上当前 [ i ][ j ] 位置的值(D[i][j]), 即得到: d p [ i ] [ j ] = m a x ( d p [ i − 1 ] [ j ] , d p [ i − 1 ] [ j − 1 ] ) + D [ i ] [ j ] dp[i][j] = max(dp[i-1][j], dp[i-1][j-1]) + D[i][j] dp[i][j]=max(dp[i1][j],dp[i1][j1])+D[i][j]

(3)确定初始状态

易知 i = 0 或 j = 0 时,会出现递推式中数组下标小于 0 的情况。

i = 0 时,即当前为三角形第一行,定义此时初始条件: d p [ 0 ] [ 0 ] = D [ 0 ] [ 0 ] dp[0][0] = D[0][0] dp[0][0]=D[0][0]

j = 0 时,即当前为三角形第一列,定义此时初始条件: d p [ i ] [ 0 ] = D [ 0 ] [ 0 ] + D [ 1 ] [ 0 ] + . . . + D [ i ] [ 0 ] dp[i][0] = D[0][0] + D[1][0] + ... + D[i][0] dp[i][0]=D[0][0]+D[1][0]+...+D[i][0]

至此,可以得到问题求解的函数代码:

int minNumberInRotateArray(int n[][])
{
	int max = 0;
	int dp[][] = new int[n.length][n.length];
	dp[0][0] = n[0][0];
	for(int i=1;i<n.length;i++){
		for(int j=0;j<=i;j++){
			if(j==0)
			{
				// 若是第一列,直接与正上方数字相加
				dp[i][j] = dp[i-1][j] + n[i][j];
			}
			else
			{
				// 若不是第一列,比较其上方和左上dp,与较大者相加,放到这个位置
				dp[i][j] = Math.max(dp[i-1][j-1], dp[i-1][j]) + n[i][j];
			}
			max = Math.max(dp[i][j], max);
		}
	}
	return max;
}

3.DAG(有向无环图)最短路径规划

在这里插入图片描述
给定一个通过边连接的结点网络,所有的边都是单向边,且不会构成环。每个边都有长度,求解从 S 点到 T 点的最短路径长度。

(1)确定动态规划数组定义

要求从 S 到 T 的最短路径长度,不妨设 dp[ i ] 为从 S 点到 i 点的最短路径长度。

(2)数组元素间的关系

结合题目和上一步确定的动态规划数组的定义,dp[ i ] 为从 S 点到 i 点的最短路径长度,因为只能从与当前结点有边连接的结点到达当前结点,故到达当前结点的最短路径应当是到达当前结点前一个结点的最短路径加上前一个结点到当前结点的路径,选取其中的最短路径作为以当前结点为终点的最短路径。

即有: d p [ i ] = m i n { d p [ a ] + w [ a ] [ i ]   ∣   a 可 以 到 达 i } dp[i] = min\{dp[a] + w[a][i]\ |\ a 可以到达i\} dp[i]=min{dp[a]+w[a][i]  ai}

(3)确定初始状态

起点位置的 dp 数组值是需要单独初始化的初始状态,即设定初始状态: d q [ s ] = 0 dq[s] = 0 dq[s]=0

至此,可以得到问题求解的函数代码:

DagShortestPath(G, w, s, t)
{
    // 对结点拓扑排序
    topologically sort the nodes in G
    // 初始化
    for each vertex v in G
    {
        dp[v] = INF;
        pre[v] = NULL;
    }
    dp[s] = 0;
    // 根据拓扑顺序,遍历顶点v
    for each v in G, taken in topologically sorted order
    {
        for each edge w[u][v]
        {
            if (dp[v] > dp[u] + w[u][v])
            {
                dp[v] = dp[u] + w[u][v];
                pre[v] = u;
            }
        }
    }
    return dp[t];
}
  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
决策树算法是一种广泛应用于分类和回归的机器学习算法,它基于树形结构对样本进行分类或预测。决策树算法的主要思想是通过一系列的判断来对样本进行分类或预测。在决策树,每个节点表示一个属性或特征,每个分支代表该属性或特征的一个取值,而每个叶子节点代表一个分类或预测结果。 决策树算法的训练过程主要包括以下步骤: 1. 特征选择:根据某种指标(如信息增益或基尼系数)选择最优的特征作为当前节点的分裂属性。 2. 决策树生成:根据选择的特征将数据集分成若干个子集,并递归地生成决策树。 3. 剪枝:通过剪枝操作来提高决策树的泛化性能。 决策树算法的优点包括易于理解和解释、计算复杂度较低、对缺失值不敏感等。但是,决策树算法也存在一些缺点,如容易出现过拟合、对离散数据敏感等。 下面是一个决策树算法的案例:假设我们要根据一个人的年龄、性别、教育程度和职业预测其收入水平(高于或低于50K)。首先,我们需要将这些特征进行编码,将其转换为数值型数据。然后,我们可以使用决策树算法对这些数据进行训练,并生成一个决策树模型。最后,我们可以使用该模型对新的数据进行分类或预测。例如,根据一个人的年龄、性别、教育程度和职业,我们可以使用决策树模型预测该人的收入水平。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值