【数据结构与算法】三个经典案例带你了解动态规划

// 直接从arr中返回我们要的值

return arr[n - 1]

}

这种方法就是一个最简单的动态规划,即通过数组的形式记录着求取斐波那契数列这个大问题过程中每一个小问题的值,最后可以直接通过数组获取到我们想要的值。

因为其没有重复求值的缺点,因此效率肯定比递归的效率高,我们可以来验证一下

// 递归求取的开始时间

let start1 = Date.now()

// 递归求取第40个数的值

console.log(fibonacci1(40))

// 递归求取的结束时间

let end1 = Date.now()

// 动态规划求值的开始时间

let start2 = Date.now()

// 动态规划求取第40个数的值

console.log(fibonacci2(40));

// 动态规划求值的结束时间

let end2 = Date.now()

console.log(`

递归所用时间:${end1 - start1} ms

动态规划所用时间:${end2 - start2} ms

`);

/*

102334155

102334155

递归所用时间:2476 ms

动态规划所用时间:0 ms

*/

从结果我们能很明显地看到,仅仅第40个值对于动态规划来说根本不算什么,使用的时间几乎为0,而递归却因重复求值使用了2s以上的时间

三、案例二:寻找最大公共子串

=======================================================================

首先先来看看问题需求:这里有两个字符串,即 ravenhavoc,现在我们要封装一个函数,来获取这两个字符串的所有最大公共子串,结果就是最大的公共子串为 av,并且最大的公共子串长度为2

首先看到这个问题,我觉得一般大家想到的办法都是跟图示一样

在这里插入图片描述

但这是一种简单粗暴的方法,把它用代码实现的话,中间也会有很多的重复比较的部分,所以我们这里也可以通过动态规划来解决此办法,即创建一个表,用来记录第一个字符串的每一个字符跟第二个字符串每一个字符的比较结果,最后再通过观察表来判断最大公共子串和最大公共子串的长度

假设现在有这样两个字符串:abaccdbadacef,我们要求它俩的最大公共子串

可以先建一个 8 * 7 的表,如图所示

在这里插入图片描述

行的表头表示的是第一个字符串的第n个字符;列的表头表示的是第二个字符串第m个字符

因此行的表头或列的表头为0对应的格子应当都为0,因为字符串没有第0个字符,最少是从第1个开始的,结果如下:

在这里插入图片描述

我们先找到行表头为1的这一行从左往右看,表示拿第一个字符串的第一个字符与第二个字符串的每一个字符进行比较,若不相同,则在对应格子里填0,表示的是连续相同字符的长度为0;若相同,则先看看该格子的左上角那个格子里的数 n 是多少,然后在该格子里填 n + 1

为什么当相同时要在该格子中填入比左上角的值大1的数呢?因为左上角的格子表示的是第一个字符串当前字符的前一个字符与第二个字符串当前字符的前一个字符比较后的连续相同字符长度

我们来看一下第一行的填写过程:

在这里插入图片描述

第二行表示的是拿第一个字符串的第二个字符与第二个字符串的每个字符的比较,过程如图所示:

在这里插入图片描述

第三行表示的是拿第一个字符串的第三个字符与第二个字符串的每个字符的比较,过程如图所示:

在这里插入图片描述

在上图第三行的填写过程中,第一个字符串的第三个字符与第二个字符串的第二个字符比较相同时,我们查看了一下该格子左上角的值,即判断了第一个字符当前字符的前一个字符与第二个字符当前字符的前一个字符比较后的连续字符长度为多少

剩下的三行填写过程如下图所示:

在这里插入图片描述

最终的表格如下图所示:

在这里插入图片描述

从表中我们可以看到,最大的公共子串长度为2,一共有两个长度为2的公共子串,分别是第一个字符串的第2个字符到第3个字符和第一个字符串的第3个字符到第4个字符,即 baac

根据上面的方法,我们来用代码封装一下求取最大公共子串的函数

function publicStr(s1, s2) {

// 创建一个表

let table = []

// 记录最大的公共子串长度

let max = 0

// 子串进行比较,将表填完整

for(let i = 0; i <= s1.length; i++) {

table[i] = []

for(let j = 0; j <= s2.length; j++) {

// 若行表头或列表头为0,格子里填0

if(i == 0 || j == 0) table[i][j] = 0;

// 若字符比对不相同

else if(s1[i - 1] !== s2[j - 1]) table[i][j] = 0;

// 字符比对相同

else {

// 当前格子的值等于左上角格子的值+1

table[i][j] = table[i - 1][j - 1] + 1

// 判断max是否为最大公共子串的长度

if(table[i][j] > max) max = table[i][j]

}

}

}

// 记录所有的最大公共子串的信息

let items = []

// 遍历整个表,找到所有子串长度为max的子串的最后一个字符的索引

for(let i = 0; i < s1.length; i ++) {

let current = table[i]

for(let j = 0; j < s2.length; j ++) {

if(current[j] === max) items.push(i)

}

}

console.log(最大子串长度为${max});

console.log(长度为${max}的子串有:);

for(let i in items) {

let start = items[i] - max

console.log(${s1.slice(start, start + max)});

}

}

我们用上述例子来验证一下该函数是否正确,同时我还打印了一下表的结果,大家可以跟实例中的比对一下是否正确

let s1 = ‘abaccd’

let s2 = ‘badacef’

publicStr(s1, s2)

/* 打印结果:

最大公共子串长度为2

长度为2的子串有:

ba

ac

表:[

[0, 0, 0, 0, 0, 0, 0, 0],

[0, 0, 1, 0, 1, 0, 0, 0],

[0, 1, 0, 0, 0, 0, 0, 0],

[0, 0, 2, 0, 1, 0, 0, 0],

[0, 0, 0, 0, 0, 2, 0, 0],

[0, 0, 0, 0, 0, 1, 0, 0],

[0, 0, 0, 1, 0, 0, 0, 0]

]

*/

四、案例三:背包问题

===================================================================

背包问题也算是一个非常经典的问题,假设现在你的面前有4种珠宝,它们的重量分别为 3345 ,它们的价值分别为 4679,现在你有一个能装下重量为 8 的物品,请问你会如何挑选才能使利益最大化?

当然最简单的办法就是写出所有的组合,然后计算每种组合的价值,然后就能获得利益最大化的方案

这用递归实现是非常简单的,代码如下

// 封装一个判断大小的函数

function max(v1, v2) {

return v1 > v2 ? v1 : v2

}

// 主函数,用于判断当前背包容量下,存放某个物品的最大收益

// 参数:背包容量、存放每个物品重量的数组、存放每个物品价值的数组、物品标号

function knapsack(capacity, size, value, n) {

// 如果没有物品了或者背包没容量了,则最大收益为0

if(n == 0 || capacity == 0) return 0;

// 物品n的重量大于背包容量

else if(size[n - 1] > capacity) {

// 返回上一个物品的最大收益

return knapsack(capacity, size, value, n - 1)

}

// 物品n的重量小于背包容量

else {

// 此时有两种选择:第一种:拿该物品 ; 第二种:不拿该物品

// 我们要取其中收益最大的方案,因此用到max函数

return max(value[n - 1] + knapsack(capacity - size[n - 1], size, value, n - 1), knapsack(capacity, size, value, n - 1))

}

}

// 代码测试

let capacity = 8

let size = [3, 3, 4, 5]

let value = [4, 6, 7, 9]

let n = 4

let res = knapsack(capacity, size, value, n)

console.log(res) // 15 , 表示最大收益价值为15

正如我们文章开头所说的,这样的递归效率总归是不太高的,因此我们要将其用动态规划实现,并且我们将需求改变一下,不光要求出最大收益价值,还要知道是拿了哪几样物品。

同样的,我们先创建一个表,用来记录每一种物品在任一背包容量下的最大收益

在这里插入图片描述

很明显,当背包容量为0时,我们能获得的最大收益一定为0;表中物品编号为0的这一行全部都要填上0,因为这是我们添加的对照行,并没有编号为0的物品,因此结果如图所示:

在这里插入图片描述

现在我们从编号为1的物品开始,判断其在背包容量为 1 ~ 8 的情况下,我们能获取到的最大利益为多少。显而易见,物品1的重量为3,因此当背包容量小于3时,最大收益都为0;当背包容量大于等于3时,因为还没有考虑别的物品,因此我们能获取的最大收益就等于物品1的价值,即等于4,结果如图所示:

在这里插入图片描述

接着我们考虑编号为2的物品在背包容量为 1 ~ 8 的情况下,我们能获取到的最大利益为多少。

首先知道物品2的重量为3,因此在背包容量小于3时,我们无法放入物品2,那么此时的最大收益就等于在当前背包容量下,放入物品1的最大收益;

当背包容量大于等于3时,我们能放入物品2,因此我们现在有两种选择:第一种就是不放物品2,那么我们就只能放物品1,所以我们能获得的最大收益就等于在此背包容量下放入物品1的最大收益;第二种就是放物品2,因为我们已经放了物品2了,只剩一个物品1了,所以此时的最大收益就等于物品2的价值 + 背包剩余容量下放入物品1的最大收益。我们要取这两种情况中收益最大的方案

填表过程如下图所示:

在这里插入图片描述

接着我们又考虑编号为3的物品在背包容量为 1 ~ 8 的情况下,我们能获取到的最大利益为多少。

首先知道物品3的重量为4,因此在背包容量小于4时,我们无法放入物品3,那么我们还需要考虑的就有物品1和物品2,从上一步骤得知,物品2的最大收益时在考虑了物品1的基础上得出的,因此我们只需要考虑放入物品2的最大收益即可,那么此时的最大收益就等于在当前背包容量下,放入物品2的最大收益;

当背包容量大于等于4时,我们能放入物品4,与上一个步骤类似,我们有两种选择,即放物品3和不放物品3

填表结果如下图所示:

在这里插入图片描述

同理,最后一行的填表过程如下图所示:

在这里插入图片描述

最终的填表结果如下图所示:

在这里插入图片描述

在表中可以很明显地看到,我们在背包容量为8的情况下,能获取到的最大收益为15

此时,我们还需要倒着推回去,判断一下是拿了哪几样物品才获取到的最大收益

首先找到最大收益对应的格子为物品4,然后我们判断一下该收益是否等于前一种物品(物品3)的最大收益,若等于,则表示没有放入物品4;否则表示放入了物品4。

为什么会这样判断呢?因为我们说过,在判断一个物品在某背包容量下的最大收益时,当物品重量大于背包容量或者我们选择不放入该物品时,此时的最大收益就等于前一种物品在此背包容量下的最大收益

所以这里能判断,我们放入了物品4,则此时背包容量只剩 8 - 5 = 3,所以我们找到物品3在背包容量等于3情况下最大收益对应的格子,同样判断一下上一种物品(物品2)的最大收益是否等于此格子中的最大收益,当前判断为相等,因此我们没有放入物品3

当前背包容量仍为3,我们找到物品2在背包容量等于3情况下最大收益对应的格子,判断当前最大收益不等于上一种物品(物品1)在背包容量为3情况下的最大收益,因此我们放入了物品2

则此时背包容量为 3 - 3 = 0了,无法再放入任何物品了,所以我们就可以得出结论,我们在放入物品2和物品4的情况下收益最大,最大收益价值为15


上面讲解了背包问题的动态规划思路,下面我们用代码来实现一下

function knapsack(capacity, size, value, n) {

// 返回较大的值

function max(v1, v2) {

return v1 > v2 ? v1 : v2

}

let table = []

// 生成长度为n的表

for(let i = 0; i <= n; i++) {

table[i] = []

}

// 判断每种物品面对不同背包容量时的最大收益

for(let i = 0; i <= n; i++) {

for(let j = 0; j <= capacity; j++) {

// 物品种类序列为0或者背包容量为0时,最大收益为0

if(i == 0 || j == 0) table[i][j] = 0;

// 背包容量小于物品重量时,最大收益等于上一种物品在此背包容量下的最大收益

else if(size[i - 1] > j) {

table[i][j] = table[i - 1][j]

}

/* 背包容量大于物品重量时,最大收益分两种情况:
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:前端)

最后

由于篇幅限制,pdf文档的详解资料太全面,细节内容实在太多啦,所以只把部分知识点截图出来粗略的介绍,每个小节点里面都有更细化的内容!有需要的程序猿(媛)可以帮忙点赞+点击【学习资料】即可免费领取!

…(img-xJiveJlO-1713633199870)]

[外链图片转存中…(img-xNCJLVXj-1713633199871)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!

[外链图片转存中…(img-sRtf8ACG-1713633199871)]

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:前端)

[外链图片转存中…(img-BmJcwAFE-1713633199871)]

最后

由于篇幅限制,pdf文档的详解资料太全面,细节内容实在太多啦,所以只把部分知识点截图出来粗略的介绍,每个小节点里面都有更细化的内容!有需要的程序猿(媛)可以帮忙点赞+点击【学习资料】即可免费领取!

[外链图片转存中…(img-ja0TCk4Q-1713633199871)]

[外链图片转存中…(img-UHnBa1Vg-1713633199872)]

  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
第一部分 基础篇 001 第一个C程序 002 运行多个源文件 003 求整数之积 004 比较实数大小 005 字符的输出 006 显示变量所占字节数 007 自增/自减运算 008 数列求和 009 乘法口诀表 010 猜数字游戏 011 模拟ATM(自动柜员机)界面 012 用一维数组统计学生成绩 013 用二维数组实现矩阵转置 014 求解二维数组的最大/最小元素 015 利用数组求前n个质数 016 编制万年历 017 对数组元素排序 018 任意进制数的转换 019 判断回文数 020 求数组前n元素之和 021 求解钢材切割的最佳订单 022 通过指针比较整数大小 023 指向数组的指针 024 寻找指定元素的指针 025 寻找相同元素的指针 026 阿拉伯数字转换为罗马数字 027 字符替换 028 从键盘读入实数 029 字符行排版 030 字符排列 031 判断字符串是否回文 032 通讯录的输入输出 033 扑克牌的结构表示 034 用“结构”统计学生成绩 035 报数游戏 036 模拟社会关系 037 统计文件的字符数 038 同时显示两个文件的内容 039 简单的文本编辑器 040 文件的字数统计程序 041 学生成绩管理程序 第二部分 数据结构篇 042 插入排序 043 希尔排序 044 冒泡排序 045 快速排序 046 选择排序 047 堆排序 048 归并排序 049 基数排序 050 二叉搜索树操作 051 二项式系数递归 052 背包问题 053 顺序表插入和删除 054 链表操作(1) 055 链表操作(2) 056 单链表就地逆置 057 运动会分数统计 058 双链表 059 约瑟夫环 060 记录个人资料 061 二叉树遍利 062 浮点数转换为字符串 063 汉诺塔问题 064 哈夫曼编码 065 图的深度优先遍利 066 图的广度优先遍利 067 求解最优交通路径 068 八皇后问题 069 骑士巡游 070 用栈设置密码 071 魔王语言翻译 072 火车车厢重排 073 队列实例 074 K阶斐波那契序列 第三部分 数值计算与趣味数学篇 075 绘制余弦曲线和直线的迭加 076 计算高次方数的尾数 077 打鱼还是晒网 078 怎样存钱以获取最大利息 079 阿姆斯特朗数 080 亲密数 081 自守数 082 具有abcd=(ab+cd)2性质的数 083 验证歌德巴赫猜想 084 素数幻方 085 百钱百鸡问题 086 爱因斯坦的数学题 087 三色球问题 088 马克思手稿的数学题 089 配对新郎和新娘 090 约瑟夫问题 091 邮票组合 092 分糖果 093 波瓦松的分酒趣题 094 求π的近似值 095 奇数平方的有趣性质 096 角谷猜想 097 四方定理 098 卡布列克常数 099 尼科彻斯定理 100 扑克牌自动发牌 101 常胜将军 102 搬山游戏 103 兔子产子(菲波那契数列) 104 数字移动 105 多项式乘法 106 产生随机数 107 堆栈四则运算 108 递归整数四则运算 109 复平面作图 110 绘制彩色抛物线 111 绘制正态分布曲线 112 求解非线性方程 113 实矩阵乘法运算 114 求解线性方程 115 n阶方阵求逆 116 复矩阵乘法 117 求定积分 118 求满足特异条件的数列 119 超长正整数的加法 第四部分 图形篇 120 绘制直线 121 绘制圆 122 绘制圆弧 123 绘制椭圆 124 设置背景色和前景色 125 设置线条类型 126 设置填充类型和填充颜色 127 图形文本的输出 128 金刚石图案 129 飘图案 130 圆环图案 131 肾形图案 132 心脏形图案 133 渔网图案 134 沙丘图案 135 设置图形方式下的文本类型 136 绘制正多边形 137 正六边形螺旋图案 138 正方形螺旋拼块图案 139 图形法绘制圆 140 递归法绘制三角形图案 141 图形法绘制椭圆 142 抛物样条曲线 143 Mandelbrot分形图案 144 绘制布朗运动曲线 145 艺术清屏 146 矩形区域的颜色填充 147 VGA256色模式编程 148 绘制蓝天图案 149 屏幕检测程序 150 运动的小车动画 151 动态显示位图 152 利用图形页实现动画 153 图形时钟 154 音乐动画 第五部分 系统篇 155 读取DOS系统的国家信息 156 修改环境变量 157 显示系统文件表 158 显示目录内容 159 读取磁盘文件 160 删除目录树 161 定义文本模式 162 设计立体窗口 163 彩色弹出菜单 164 读取CMOS信息 165 获取BIOS设备列表 166 锁住硬盘 167 备份/恢复硬盘分区表 168 设计口令程序 169 程序自我保护 第六部分 常见试题解答篇 170 水果拼盘 171 小孩吃梨 172 删除字符串的特定字符 173 求解符号方程 174 计算标准差 175 求取符合特定要求的素数 176 统计符合特定条件的数 177 字符串倒置 178 部分排序 179 产品销售记录处理 180 特定要求的字符编码 181 求解三角方程 182 新完全平方数 183 三重回文数 184 奇数方差 185 统计选票 186 同时整除 187 字符左右排序 188 符号算式求解 189 数字移位 190 统计最高成绩 191 比较字符串长度 192 合并整数 193 矩阵逆置 194 删除指定的字符 195 括号匹配 196 字符串逆置 197 SIX/NINE问题 198 单词个数统计 199 方差运算 200 级数运算 201 输出素数 202 素数题 203 序列排序 204 整数各位数字排序 205 字符串字母移位 206 Fibonacc数列 第七部分 游戏篇 207 商人过河游戏 208 吃数游戏 209 解救人质游戏 210 打字训练游戏 211 双人竞走游戏 212 迷宫探险游戏 213 迷你撞球游戏 214 模拟扫雷游戏 215 推箱子游戏 216 五子棋游戏 第八部分 综合实例篇 217 综合CAD系统 218 功能强大的文本编辑器 219 图书管理系统 220 进销存管理系统
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值