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

news2024/9/22 17:26:19

在这里插入图片描述

🔥 个人主页:空白诗

在这里插入图片描述

文章目录

    • 一、最优子结构
      • 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,则返回 n
  if (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.coloradmin.cn/o/1921510.html

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

相关文章

Java I/O模式 (一)

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

调整网络安全策略以适应不断升级的威胁形势

关键网络安全统计数据和趋势 当今数字时代网络安全的重要性

【大语言模型应用形态 AI Agent 发展趋势深度分析 2024】

文末有福利&#xff01; 一、智能体&#xff08;AI Agent&#xff09; 1. 智能体正成为大模型重要研发方向 随着技术飞速发展&#xff0c;智能体&#xff08;AI Agent&#xff09;正成为一股革命性力量&#xff0c;正在重新定义人与数字系统互动的方式。AI Agent是一种高效、…

使用DeepWalk 和Word2Vec实现单词Embedding

0、准备“边”数据集Wiki_edgelist import pandas as pddf pd.read_csv(./data/wiki/Wiki_edgelist.txt, sep , headerNone, names["source", "target"]) df.head() 1、读入 起点-终点 边数据&#xff0c;构建图 # 1、读入 起点-终点 边数据&#xff0c…

如何评估独立站的外链质量?

要评估独立站的外链质量时&#xff0c;首先要看的不是别的&#xff0c;而是内容&#xff0c;跟你网站相关的文章内容才是最重要的&#xff0c;其他的一切其实都不重要。什么网站的DA&#xff0c;评级&#xff0c;网站的主要内容跟你的文章内容是否相关其实都不重要&#xff0c;…

git提交大文件服务500

错误如图 需保证git服务端能接收大文件 修改项目下.git文件中的config文件&#xff0c;加入 [http] postBuffer 524288000

逆变-TI视频课笔记

目录 1、全桥逆变 1.1、全桥逆变SPWM仿真 2、半桥逆变 2.1、本课小结 3、多重逆变&#xff08;间接的“交-直-交-直”变流&#xff09; 3.1、多电平逆变的目的 3.2、单逆变桥 3 电平控制时序 3.3、大功率设备的功率因数 3.4、本课小结 视频链接&#xff1a;文字…

怎样在 C 语言中实现堆排序?

&#x1f345;关注博主&#x1f397;️ 带你畅游技术世界&#xff0c;不错过每一次成长机会&#xff01; &#x1f4d9;C 语言百万年薪修炼课程 【https://dwz.mosong.cc/cyyjc】通俗易懂&#xff0c;深入浅出&#xff0c;匠心打磨&#xff0c;死磕细节&#xff0c;6年迭代&…

小程序-设置环境变量

在实际开发中&#xff0c;不同的开发环境&#xff0c;调用的接口地址是不一样的 例如&#xff1a;开发环境需要调用开发版的接口地址&#xff0c;生产环境需要正式版的接口地址 这时候&#xff0c;我们就可以使用小程序提供了 wx.getAccountInfoSync() 接口&#xff0c;用来获取…

分享 | 一文简述模糊测试智能体技术实践

近日&#xff0c;华为2012实验室中央软件院旗下的欧拉多咖创新团队成功举办了【欧拉多咖 — 操作系统研讨会】。本次研讨会以“系统安全AI&#xff1f;”为主题&#xff0c;探讨了大模型技术如何推动基础软件迈向大规模算力时代&#xff0c;并详细讨论了在这一过程中系统软件所…

论文 | LEAST-TO-MOST PROMPTING ENABLES COMPLEXREASONING IN LARGE LANGUAGE MODELS

论文主题&#xff1a; 这篇论文提出了“从简单到复杂提示”&#xff08;Least-to-Most Prompting&#xff09;这一新的提示策略&#xff0c;旨在解决大语言模型在解决比提示示例更复杂的问题时表现不佳的难题。 核心思想&#xff1a; 将复杂问题分解成一系列更简单的子问题。按…

Verilog基础:操作数的位选(bit-select)和域选(part select)

相关阅读 Verilog基础https://blog.csdn.net/weixin_45791458/category_12263729.html?spm1001.2014.3001.5482 位选 位选(bit-select)用于选择一个向量(vector)的某位&#xff0c;可以是线网大类(net)&#xff0c;也可以是变量大类(variable)中的reg、integer和time&#xf…

Redis 主从复制,哨兵与集群

目录 一.redis主从复制 1.redis 主从复制架构 2.主从复制特点 3.主从复制的基本原理 4.命令行配置 5.实现主从复制 6.删除主从复制 7.主从复制故障恢复 8.主从复制完整过程 9.主从同步优化配置 二.哨兵模式&#xff08;Sentinel&#xff09; 1.主要组件和概念 2.哨…

半小时获得一张ESG入门证书【详细中英文笔记一】

前些日子&#xff0c;有朋友转发了一则小红书的笔记给我&#xff0c; 标题是《半小时获CFI官方高颜值免费证书 ESG认证》。这对考证狂魔的我来说&#xff0c;必然不能错过啊&#xff0c;有免费的羊毛不薅白不薅。 ESG课程的 CFI 官方网址戳这里&#xff1a;CFI 于是信心满满的…

Electron运行报错:Error Cannot find module ‘node_moduleselectroncli.js‘

Electron运行报错&#xff1a;Error: Cannot find module ‘node_modules\electron\cli.js’ 顾名思义&#xff0c;命令行执行Electron .时候&#xff0c;会优先从项目目录查找对应依赖&#xff0c;如果是报错显示是找不到项目目录下的依赖&#xff0c;我们可以从安装在全局的…

轮转数组(时间复杂度不同的三种思路)

&#xff08;来源&#xff1a;LeetCode&#xff09; 题目 分析 其实一次轮转就是将最后一个数据放到最前面&#xff0c;其他数据整体向后移动一位。k为几就重复这个行为几次。 思路1 我们应该很快能想到最直接的一种思路。while(k--)……循环内完成两件事&#xff0c;保存最…

【C++】函数重载详解

&#x1f4e2;博客主页&#xff1a;https://blog.csdn.net/2301_779549673 &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01; &#x1f4e2;本文由 JohnKi 原创&#xff0c;首发于 CSDN&#x1f649; &#x1f4e2;未来很长&#…

Python面试全攻略:基础知识、特性、算法与实战解析

随着Python的普及&#xff0c;越来越多的人开始学习Python并尝试在面试中展示自己的技能。在这篇文章中&#xff0c;我们将探讨Python面试需要注意的问题以及一些经典的Python算法。 一、Python面试需要注意的问题 基础知识 在Python面试中&#xff0c;基础知识是非常重要的。…

开源浪潮:助力未来科技的飞速发展

文章目录 开源项目有哪些机遇与挑战&#xff1f;开源项目的发展趋势发展现状开源社区的活跃度 我是如何参与开源项目的经验分享选择开源项目贡献代码 开源项目的挑战开源项目面临的挑战 开源项目有哪些机遇与挑战&#xff1f; 随着全球经济和科技环境的快速变化&#xff0c;开源…

设计模式 - 最简单最有趣的方式讲述

别名《我替你看Head First设计模式》 本文以故事的形式带你从0了解设计模式&#xff0c;在其中你仅仅是一名刚入职的实习生&#xff0c;在项目中摸爬滚打。&#xff08;以没有一行真正代码的形式&#xff0c;让你无压力趣味学习&#xff09; 设计模式 策略模式观察者模式装饰者…