前言
今天分享的内容是前端必问算法,没有之一--动态规划。
动态规划(Dynamic Programming, DP)是一种在数学、计算机科学和经济学领域中使用的解决复杂问题的方法。它的核心思想是将问题分解成相互依赖的子问题,然后用一个表格存储这些子问题的解,以避免重复计算。动态规划适用于具有重叠子问题和最优子结构特点的问题,通常可用于求解最优化问题。
提炼几个关键点:
- 重复的子问题
- 最优结构子问题
- 避免重复计算
其中,第一个点和上篇文章讲到的回溯算法一致,都是将一个大问题分解成多个重复的子问题。而后面两点就是动态规划的独有特点了。动态规划可以通过找到最优结构的子问题,从而避免重复计算
本篇文章的问题和上篇文章的问题一致,即找到方格的最短路径
假设 n*n 的方格,每个方格都有一个数字,表示这个方格的权重。现在要从(0,0)走到(n-1, n-1),有很多路径。就像下面这个九宫格,从左上角走到右下角,可以右右右下下下,也可以下下下右右右,也可以右右下下下右,有很多种走法,每种走法的权重都不一样,现在要找到权重之和最小的那条路径
问题描述摘抄自上篇文章🥳前端算法面试之找到最短路径-每日一练 - 掘金,这里还有用回溯算法解决该问题的讲解
问题分析
用回溯算法的解决思路就是找到所有的路径,但是这里还有优化的空间。
就拿上面的九宫格举例子,假设已经走到了(1,1)这个位置,那么有两种可能,一种是走(0,0)->(1,0)->(1,1),那么路径权重就是 12, 还有一种是走(0,0)->(0,1)->(1,1),这样路径权重就是 18 了。其实对于后者就没必要继续算下去了,直接使用前者的路线继续算就可以了。但对于回溯算法,每种权重还是会继续算下去,这就是回溯算法时间复杂度高的原因。
动态规划对回溯算法优化,正是从这个方向入手。走到某一格,只保留最短的权重路径,从而继续下去。直接抛弃其他的路径走法。
对于任何一格,可以往下走的选择都是要么往右,要么往下。假设现在走到了 (i,j)
格,它的上一步的路径来源也就要么来自上方(i-1,j)
,要么来自左方(i,j-1)
。然后它只要选择这两个路径中最短权重的,加上自身的权重 weight(i,j)
,意思是走到了它这一格,最短路径是 weight(i,j) + min( weight(i-1,j), weight(i, j-1) )
。 如果 X 不是重点的话,就相当于挑了一条最短路径继续往下走。如果 X 是重点,那么整个方格的最短路径的权重就求出来了。
到这里,思路分析得差不多了,下面看代码怎么实现的
动态规划的实现
javascript
/**
*
* @param {number[][]} data
*/
const findShortPath = (data) => {
// 初始化path数组
const path = Array(data.length)
.fill(0)
.map(() => Array(data.length).fill(0));
path[0][0] = data[0][0];
// 初始化行
for (let i = 1; i < data.length; i++) {
path[0][i] = path[0][i - 1] + data[0][i];
}
// 初始化列
for (let j = 1; j < data.length; j++) {
path[j][0] = path[j - 1][0] + data[j][0];
}
// 计算剩下的格子
for (let i = 1; i < data.length; i++) {
for (let j = 1; j < data.length; j++) {
path[i][j] = data[i][j] + Math.min(path[i - 1][j], path[i][j - 1]);
}
}
// check
console.log(path);
console.log("the min weight is: ", path[data.length - 1][data.length - 1]);
};
代码的逻辑也是按照上面分析的来,计算每个格子的时候,值为data[i][j] + Math.min(path[i - 1][j], path[i][j - 1])
,意为选取最短的路径往下走。注意⚠️,相较于回溯算法,动态规划的高明之处正在于此,只保留最短路径往下走,而不是计算所有的路径值。
在计算路径之前,先要初始化第 1 行和第 1 列的数据,方便后面的计算。
计算完成之后,函数的末尾会打印出 path,大家可以看看生成的 path 长什么样子。并且 path 最后一格的值就是我们要求的最短路径
执行代码
javascript
const data = [
[3, 4, 2, 1],
[5, 2, 4, 1],
[7, 6, 4, 3],
[5, 9, 2, 4],
];
findShortPath(data);
输出结果:
下面测试几个简单的方格数据,方便大家判断代码的正确性:
例一
javascript
const data = [
[1, 2, 2],
[1, 2, 2],
[1, 1, 1],
];
打印结果:
例二
javascript
const data = [
[1, 2, 2],
[1, 0, 2],
[1, 0, 1],
];
打印结果:
代码正确🙆
总结
这篇文章分享了如何用动态规划的方法找到权重最短的路径,并且分析了回溯算法复杂度高的原因,从原因入手,就是动态规划提高性能的方向,即保留局部最优的路径,其他的路径可能性就不考虑。同时文中给出了 JS 代码实现,例子也很详细。
可以评论区留言哦。我每天都会分享一篇算法小练习,喜欢就点赞+关注吧