【JavaScript 算法】动态规划:最优子结构与重叠子问题

在这里插入图片描述

🔥 个人主页:空白诗

在这里插入图片描述

文章目录

    • 一、最优子结构
      • 1.1 最优子结构的例子
      • 1.2 如何识别最优子结构
    • 二、重叠子问题
      • 2.1 重叠子问题的例子
      • 2.2 解决重叠子问题的方法
      • 2.3 如何识别重叠子问题
    • 三、经典动态规划问题及其 JavaScript 实现
      • 3.1 斐波那契数列
      • 3.2 背包问题
    • 四、总结

在这里插入图片描述

在算法的世界里,动态规划(Dynamic Programming,简称DP)是一种解决复杂问题的有力工具。它通过将问题分解为更小的子问题,并记忆这些子问题的结果,从而避免重复计算,提高效率。动态规划的两个核心概念是最优子结构重叠子问题


一、最优子结构

最优子结构指的是一个问题的最优解可以由其子问题的最优解构造而成。换句话说,如果我们可以通过解决子问题来解决原问题,那么这个问题就具有最优子结构性质。

1.1 最优子结构的例子

例子1:最短路径问题

在图论中,找到从一个顶点到另一个顶点的最短路径是一个常见的问题。如果一个路径的最优解(最短路径)可以由其子路径的最优解构成,那么这个问题就具有最优子结构。

假设我们要从顶点A到顶点D找到最短路径,而经过顶点B和C是最优路径的一部分,那么从A到B的路径和从B到C的路径也必须是最短的。否则,我们可以找到一条更短的路径替代原路径。

A到D的最短路径
A到B的最短路径
B到C的最短路径
C到D的最短路径

例子2:矩阵链乘法

在矩阵链乘法中,我们需要找到一种最有效的方式来计算多个矩阵的乘积。这个问题也具有最优子结构性质,因为计算矩阵链的最优乘积方式可以通过计算它的子链的最优乘积方式来得到。

1.2 如何识别最优子结构

识别一个问题是否具有最优子结构性质,通常需要以下步骤:

  1. 分解问题:将原问题分解为子问题,确保子问题独立且易于解决。
  2. 验证子问题:检查子问题的解是否可以组合成原问题的解。
  3. 组合子问题:确认是否可以通过组合子问题的最优解来获得原问题的最优解。

二、重叠子问题

重叠子问题是指在解决一个问题的过程中,会多次遇到相同的子问题。如果这些子问题重复出现,我们可以使用记忆化技术(Memoization)或表格法(Tabulation)来存储它们的计算结果,从而避免重复计算。

2.1 重叠子问题的例子

例子1:斐波那契数列

斐波那契数列是重叠子问题的经典例子。在计算斐波那契数列的过程中,我们会多次计算相同的子问题。例如,计算F(5)时,我们需要计算F(4)和F(3),而计算F(4)又需要计算F(3)和F(2)。这样会导致大量的重复计算。

F5
F4
F3
F3
F2
F2
F1
F2
F1
F1
F0
F1
F0

例子2:最长公共子序列

在计算两个字符串的最长公共子序列(LCS)时,我们也会遇到重叠子问题。例如,在比较字符串“ABCBDAB”和“BDCABA”时,我们需要比较子序列“BCBDAB”和“DCABA”以及“ABCBDAB”和“DCABA”,这些子序列的比较会重复多次。

LCS1
LCS2
LCS3
LCS4
LCS5
LCS6
LCS7
LCS8
LCS9
LCS10
LCS11

在这张图中,我们看到计算最长公共子序列时的一些重叠子问题。每一个节点代表一个子问题,例如”LCS1”表示求解字符串”ABCBDAB”和”BDCABA”的最长公共子序列,而”LCS2”表示求解”BCBDAB”和”DCABA”的最长公共子序列。因为这些子问题在多个计算路径中会重复出现,所以它们就是重叠子问题的例子。

2.2 解决重叠子问题的方法

1. 记忆化技术(Memoization)

记忆化技术是一种自顶向下的解决方法,通过递归计算子问题,并将计算结果存储在一个表中。每当需要计算一个子问题时,先检查表中是否已有结果,如果有则直接使用,否则计算并存储结果。

function fibonacci(n, memo = {}) {if (n <= 1) return n;if (memo[n]) return memo[n];memo[n] = fibonacci(n - 1, memo) + fibonacci(n - 2, memo);return memo[n];
}console.log(fibonacci(10)); // 输出55

2. 表格法(Tabulation)

表格法是一种自底向上的解决方法,通过迭代计算所有子问题的解,并将这些解存储在一个表中。这样,每当需要计算一个子问题时,可以直接查表得到结果。

function fibonacci(n) {if (n <= 1) return n;const table = [0, 1];for (let i = 2; i <= n; i++) {table[i] = table[i - 1] + table[i - 2];}return table[n];
}console.log(fibonacci(10)); // 输出55

2.3 如何识别重叠子问题

识别一个问题是否具有重叠子问题性质,通常需要以下步骤:

  1. 分解问题:将原问题分解为子问题。
  2. 检测重复:检查是否存在重复计算的子问题。
  3. 优化策略:选择合适的优化策略,如记忆化技术或表格法,来存储和复用子问题的计算结果。

通过理解最优子结构和重叠子问题的概念,我们可以更好地应用动态规划来解决实际问题。这两个核心概念帮助我们识别问题的结构特性,并选择合适的优化策略,从而提高算法的效率。


三、经典动态规划问题及其 JavaScript 实现

3.1 斐波那契数列

斐波那契数列是动态规划的经典问题之一。其递推公式为:
F ( n ) = F ( n − 1 ) + F ( n − 2 ) F(n) = F(n-1) + F(n-2) F(n)=F(n1)+F(n2)

基准条件为:
F ( 0 ) = 0 , F ( 1 ) = 1 F(0) = 0, F(1) = 1 F(0)=0,F(1)=1

记忆化技术实现斐波那契数列

/*** 计算斐波那契数列的第 n 项* 使用记忆化技术来避免重复计算* @param {number} n - 斐波那契数列的第 n 项* @param {object} memo - 用于存储已经计算过的斐波那契数值* @returns {number} - 第 n 项的斐波那契数值*/
function fibonacci(n, memo = {}) {// 基准条件:如果 n 小于等于 1,则返回 nif (n <= 1) return n;// 如果 memo 对象中已经存在第 n 项的结果,则直接返回if (memo[n]) return memo[n];// 否则,计算第 n 项的结果,并将其存储在 memo 对象中memo[n] = fibonacci(n - 1, memo) + fibonacci(n - 2, memo);// 返回第 n 项的结果return memo[n];
}// 示例:计算斐波那契数列的第 10 项
console.log(fibonacci(10)); // 输出55

在上述代码中,我们使用了一个 memo 对象来存储已经计算过的斐波那契数值,这样在遇到重复子问题时可以直接返回结果,避免了重复计算。


3.2 背包问题

背包问题描述了这样一个场景:给定一组物品,每个物品有一定的重量和价值,在总重量不超过容量的情况下,如何选择物品使得总价值最大。

动态规划实现背包问题

/*** 解决背包问题,找到在不超过最大容量的情况下,能够获得的最大价值* @param {number[]} weights - 物品的重量数组* @param {number[]} values - 物品的价值数组* @param {number} capacity - 背包的最大容量* @returns {number} - 背包能够容纳的最大价值*/
function knapsack(weights, values, capacity) {const n = weights.length;// 创建一个二维数组 dp,用于存储动态规划的结果// dp[i][w] 表示前 i 件物品在容量为 w 时能够获得的最大价值const dp = Array.from({ length: n + 1 }, () => Array(capacity + 1).fill(0));// 遍历每一件物品for (let i = 1; i <= n; i++) {// 遍历每一种可能的容量for (let w = 1; w <= capacity; w++) {// 如果当前物品的重量小于等于当前容量if (weights[i - 1] <= w) {// 选择当前物品,或者不选择当前物品,取最大值dp[i][w] = Math.max(dp[i - 1][w], // 不选择当前物品dp[i - 1][w - weights[i - 1]] + values[i - 1] // 选择当前物品);} else {// 如果当前物品的重量大于当前容量,则不能选择当前物品dp[i][w] = dp[i - 1][w];}}}// 返回最大价值return dp[n][capacity];
}// 示例:背包问题
const weights = [1, 3, 4, 5]; // 物品的重量数组
const values = [1, 4, 5, 7]; // 物品的价值数组
const capacity = 7; // 背包的最大容量console.log(knapsack(weights, values, capacity)); // 输出9

在上述代码中,我们使用一个二维数组 dp 来存储动态规划的结果。dp[i][w] 表示前 i 件物品在容量为 w 时能够获得的最大价值。通过遍历每一件物品和每一种可能的容量,我们可以找到在不超过最大容量的情况下,能够获得的最大价值。


四、总结

动态规划通过分解问题、存储子问题结果,解决了许多经典的计算问题。在实际应用中,识别问题是否具有最优子结构和重叠子问题的性质,并正确使用记忆化技术或表格法,可以显著提高算法的效率。

通过以上两个示例,相信大家对动态规划的基本思想和应用有了更深入的理解。在实际开发中,遇到复杂问题时,不妨考虑一下是否可以通过动态规划来解决。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/45508.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

GAMMA软件适配航天宏图一号多星干涉数据

文章目录 1.航天宏图一号 X-频段 多基雷达星座2.航天宏图算法人员小结3.双基成像与单基成像干涉处理区别 GAMMA软件是世界著名的瑞士GAMMA遥感公司开发的专门用于干涉雷达数据处理的全功能商业软件。作为业内标杆软件&#xff0c;被全球范围内的研究人员、公司和公共机构广泛使…

MS5199T芯片解决方案以及驱动程序(国产三路5VADC)

一、MS5199T芯片介绍 MS5198T/MS5199T 为适合高精度测量应用的低功耗、低噪 声、三通道差分输入的 16bit/24bit 模数转换器。其内部集成了低 噪声输入缓冲器、低噪声仪表放大器,当增益设置为 64,更新 速率为 4.17Hz 时,均方根噪声为 10nV。 MS5198T/MS5199T 还集 成了低端电…

使用F1C200S从零制作掌机之debian文件系统完善NES

一、模拟器源码 源码&#xff1a;https://files.cnblogs.com/files/twzy/arm-NES-linux-master.zip 二、文件系统 文件系统&#xff1a;debian bullseye 使用builtroot2018构建的文件系统&#xff0c;使用InfoNES模拟器存在bug&#xff0c;搞不定&#xff0c;所以放弃&…

Datawhale 2024 年 AI 夏令营第二期——电力需求预测挑战赛

#AI夏令营 #Datawhale #夏令营 1.赛事简介 随着全球经济的快速发展和城市化进程的加速&#xff0c;电力系统面临着越来越大的挑战。电力需求的准确预测对于电网的稳定运行、能源的有效管理以及可再生能源的整合至关重要。 2.赛事任务 给定多个房屋对应电力消耗历史N天的相关…

TCP协议:如何实现客户端和服务器端的交流?

实例&#xff1a;超简陋版人工AI对答程序 一、描述&#xff1a; 在客户端输入问题&#xff0c;服务器端将给出答案。 二、代码示例 1.客户端 步骤&#xff1a; 首先创建一个Scanner对象input&#xff0c;用于从控制台读取用户输入的问题。用户输入的一行文本将存储在quest…

【Jfrog Artifactory】配置邮件服务器

教程使用QQ邮箱 配置路径是&#xff1a; http://IP:8082/ui/admin/configuration/mail 进入到Mail Server&#xff0c;然后按照格式填入&#xff1a; Host &#xff1a;smtp.qq.com 【发送服务器】 Port&#xff1a;587 【我的环境465无法发送成功】 Username&#xff1a;QQ邮…

C++:从C语言过渡到C++

在这篇博客中&#xff0c;我将会介绍从C语言过渡到C的一些基础知识。 目录 C起源 C的关键字 输出hello&#xff0c;world ​编辑 命名空间 1.什么是命名空间 2.namespace的作用 3.域作用限定符 4.命名空间的使用 IO流 缺省参数 函数重载 引用 1.引用的定义 2.引…

【Python3】自动化测试_Playwright最简单示例

启动 Playwright实例&#xff1a;sync_playwright().start() 终止 Playwright 实例&#xff1a;myPlaywright.stop() Playwright 模块提供了一种启动浏览器实例的方法。以下是使用 Playwright 驱动自动化的典型示例&#xff1a; from playwright.sync_api import sync_playw…

C++入门基础简述

文章目录 前言1、C首个程序2、namespace关键字3、C输入/输出4、缺省参数5、函数重载6、C中的引用7、const 引用8、指针和引用的关系9、inline关键字10、nullptr关键字 前言 此篇文章主要简述流程&#xff1a;C首个程序 -> namespace关键字 -> C输入/输出 -> 缺省参数 …

【C++航海王:追寻罗杰的编程之路】一篇文章带你认识哈希

目录 1 -> unordered系列关联式容器 1.1 -> unordered_map 1.1.1 -> unordered_map的文档介绍 1.1.2 -> unordered_map的接口说明 1.2 -> unordered_set 2 -> 底层结构 2.1 -> 哈希概念 2.2 -> 哈希冲突 2.3 -> 哈希函数 2.4 -> 哈希冲…

100 个网络基础知识普及,看完成半个网络高手!

1&#xff09;什么是链接&#xff1f; 链接是指两个设备之间的连接。它包括用于一个设备能够与另一个设备通信的电缆类型和协议。 2&#xff09;OSI 参考模型的层次是什么&#xff1f; 有 7 个 OSI 层&#xff1a;物理层&#xff0c;数据链路层&#xff0c;网络层&#xff0…

公有云API常见的认证方式

公有云API常见的认证方式 Token认证 &#xff08;百度云、腾讯云&#xff09; AK/SK认证 (阿里云、天翼云、腾讯云) RSA非对称加密方式 &#xff08;信核云灾备&#xff09; Token认证 AK/SK认证 RSA认证 种方式使用一对密钥&#xff0c;即公钥和私钥。公钥可以公开&#x…

柳永,市井生活的吟游者

柳永&#xff0c;原名柳三变&#xff0c;字景庄&#xff0c;后改名为柳永&#xff0c;字耆卿&#xff0c;约生于宋太宗雍熙元年&#xff08;公元984年&#xff09;&#xff0c;卒于宋仁宗皇祐五年&#xff08;公元1053年&#xff09;&#xff0c;享年69岁。他是北宋著名词人&am…

基于eBPF的procstat软件追踪程序Offcpu时间

在现代计算机系统中&#xff0c;性能调优和问题诊断是大家经常会面临的问题&#xff0c;解决这些性能问题是确保程序高效运行的关键。有时不知为何程序的吞吐量和时延出现抖动&#xff0c;有一种可能就是程序发生了Offcpu。了解程序的 Offcpu 时间有助于识别潜在的性能瓶颈和系…

【论文速读】《面向深度学习的联合消息传递与自编码器》,无线AI的挑战和解决思路

这篇文章来自华为的渥太华无线先进系统能力中心和无线技术实验室&#xff0c;作者中有大名鼎鼎的童文。 一、自编码架构的全局收发机面临的主要问题 文章对我比较有启发的地方&#xff0c;是提到自编码架构的全局收发机面临的主要问题&#xff1a; 问题一&#xff1a;基于随…

Rhino云渲染使用教程

Rhino是一款功能强大、灵活易用的三维计算机图形建模软件&#xff0c;广泛应用于建筑、工业设计、产品设计、珠宝设计、动画制作等多个领域。哪Rhino可不可以使用云渲染呢&#xff1f;答案是可以的&#xff0c;下面给大家介绍一下炫云Rhino云渲染的使用方法。 1、搜索“炫云”…

springboot枚举简单使用笔记

springboot枚举简单使用笔记 定义枚举: package com.geofly.ynygzx.iscp.api.common.enums;/*** Description: 需求提交记录状态枚举** Param:* Return:* Author yanghaoxing* Date 2024/7/12 10:01*/public enum RequirementSubmissionStatus {BACK("已撤回", 0),S…

Java I/O模式 (一)

第一章 Java的I/O演进之路 1.1 I/O模型基本说明 1/0模型&#xff1a;就是用什么样的通道或者说是通信模式和架构进行数据的传输和接收&#xff0c;很大程度上决定了程序通信的性能&#xff0c;Java 共支持3种网络编程的/10 模型&#xff1a;BIO、NIO、AIO 实际通信需求下&am…

Centos忘记密码,重置root密码

Centos忘记密码&#xff0c;重置root密码 操作环境&#xff1a;Centos7.6 1、选择包含rescue的选项&#xff0c;按e进入编辑模式 首先&#xff0c;我们需要重启系统&#xff0c;进入开机引导菜单界面。在这里&#xff0c;我们可以看到系统的内核版本和启动参数等信息。我们需…

【JavaEE精炼宝库】文件操作(2)——文件内容读写 | IO流

文章目录 一、输入流1.1 InputStream 概述&#xff1a;1.2 read 方法详解&#xff1a;1.3 close 方法&#xff1a;1.4 利用 Scanner 进行读操作&#xff1a;1.5 Reader&#xff1a; 二、输出流2.1 OutputStream 概述&#xff1a;2.2 write 方法详解&#xff1a;2.3 利用 PrintW…