01数据结构-初探动态规划

01数据结构-初探动态规划

  • 前言
  • 1.基本思想
  • 2.重叠子问题
  • 3.斐波那契数列
  • 4.备忘录(记忆化搜索表)
    • 4.1备忘录(记忆化搜索表)代码实现
  • 5.DP table
    • 5.1DP table代码实现
  • 6.练习

前言

在学习动态规划时切忌望文生义,因为其名字与其思想关系不大,你可以自己想一个记住其思想的名字,例如:递推公式法,状态转移方程法等等。与其说动态规划是一个算法,还不如说是解决问题的方法论,动态规划的一般形式就是求最优值,比如最长公共子序列,最大子段和,最优二叉搜索树等等。

贪心算法:在前面我们提到过的Prim算法和Kruskal算法就借用到了贪心算法的思想,例如我们的Prim算法,每次我们都选择选中的顶点中的边的最小值,但是有一定的条件:选择出来的边不能闭环,像这种前一步的贪心和后一步的贪心没有直接关系就比较简单一点,例如一个序列中找最大值,最小值。

分治算法:大问题拆分成小问题,小问题之间相对独立,每个小问题解决方案是一样的

动态规划:大问题拆分成小问题,小问题之间互相依赖重复,每个小问题解决方案是一样的。

1.基本思想

动态规划算法与分治法类似,其基本思想就是将待求解问题分解成若干子问题,先求解子问题,然后从这些子问题的解得到原问题的解。

与分治法不同的是,适合动态规划法求解的问题,经分解得到的子问题往往不是相互独立的。若用分治法来解这类问题,则分解得到的子问题数目太多,以至于最后解决原问题需要耗费指数时间。然而,不同子问题的数目常常只有多项式量级。

在用分治法求解时,有些子问题被重复结算了很多次。如果我们能够保存已经解决的子问题的答案,而在需要时再找出已求得的答案,这样就可以避免大量的重复计算,从而得到多项式时间复杂度的算法。为了达到此目的,可以用一个表来记录所有已解决的子问题的答案,不管该子问题以后是否被用到,只要它被计算过,就将其结果填入表中。这就是动态规划的基本思想。

基本要点:将待求解问题分解成若干子问题,先求解子问题,然后从这些子问题的解得到原问题的解;经分解得到的子问题往往不是相互独立的;保存已经解决的子问题的答案,避免重复计算。

例如下图我们想要f(N)的数据,假设需要知道f(x)和f(y),f(x)需要从f(x1)得知,f(y)需要从f(x2)得知,而f(x2)和f(x1)有关,那我们就可以采用动态规划的思想,把f(x1)计算出的结果保存下来,这样计算f(x2)的时候就可以少计算一次f(x1)。
在这里插入图片描述

2.重叠子问题

在用递归算法自顶向下解决一个问题时,每次产生的子问题并不总是新问题,有些子问题被反复计算多次。动态规划正是利用了这种子问题的重叠性质,对每个子问题只解一次,而后将其解保存到一个表格中,当再次需要解此子问题时,只是简单地用常数时间查看一下结果。

动态规划经分解得到的子问题往往不是相互独立的 。如果经分解得到的子问题的解之间相互独立,比如二分查找(Binary Search)经分解得到的子问题之间相互独立,不存在重叠子问题,所以不适合用动态规划,更适合分治算法。

接下来来看一个非常经典的重叠子问题:斐波那契数列。

3.斐波那契数列

我们可以理解斐波那契数列为:f(n)=f(n-1)+f(n-2)(递推公式)。一个数字等于前面两个数字的和,这就是一个大问题拆分成几个小问题,我们需要把它拆分成递归搜索树。
在这里插入图片描述
如图,如果我们想知道斐波那契数列中的第5个数字,我们需要知道第3个和第4个数字,一次向下类推可以得到一颗二叉树,明显到当我们拆分到2的时候,我们向下再拆一次就变成了需要知道0和1的问题,此时无法再向下拆分,我们就认为,0和1是初始状态(你也可以认为是1和2),当我们计算从下往上运算的时候,如果我们不保存已经计算得到的结果的话,我们会大量重复的计算,例如:先看5的左子树我们通过计算0,1计算2,再通过2,1计算3,此时3的右边2我们还需要重复计算,因为我们没有保存第一次计算得到的2的数值,同理当我们计算到4的时候,我们还需要计算得到5的右子树的3,因为我们没有保存第一次计算3时得到的数值,接下来看一看如果我们不保存值的话的时间长短。

这就是一个很简单的递归代码,我就不过多叙述

#include <stdio.h>
#include <time.h>
#include <stdlib.h>unsigned int fib01(unsigned int n) {if (n == 0) {return 0;}if (n == 1) {return 1;}return fib01(n - 1) + fib01(n - 2);
}// 1 1 2 3 5
void test01() {clock_t start = clock();unsigned int x = fib01(40);clock_t end = clock();printf("cost time: %f s。fib01 = %u\n", (double)(end - start) / CLOCKS_PER_SEC, x);
}int main() {test01();return 0;
}

结果:

D:\work\DataStruct\cmake-build-debug\06_DP\fib.exe
cost time: 0.504000 s。fib01 = 102334155进程已结束,退出代码为 0

现在将数字改为46

#include <stdio.h>
#include <time.h>
#include <stdlib.h>unsigned int fib01(unsigned int n) {if (n == 0) {return 0;}if (n == 1) {return 1;}return fib01(n - 1) + fib01(n - 2);
}// 1 1 2 3 5
void test01() {clock_t start = clock();unsigned int x = fib01(46);clock_t end = clock();printf("cost time: %f s。fib01 = %u\n", (double)(end - start) / CLOCKS_PER_SEC, x);
}int main() {test01();return 0;
}

结果:

D:\work\DataStruct\cmake-build-debug\06_DP\fib.exe
cost time: 8.964000 s。fib01 = 1836311903进程已结束,退出代码为 0

可以看到仅仅只是增加了6个数字,时间一下子增加了8秒之多,这是因为,我们每增加一个,都会多重复计算两层的数字,时间复杂度近似于n2
在这里插入图片描述
我们有两种方法解决这种问题,第一种是备忘录(记忆化搜索表),第二种是DP table,第一种方法是把大问题拆分成小问题,自顶向下解决问题,而第二种方法是拿到一个大问题,先想有哪些小问题需要我们解决,是自底向上解决问题。两种方法的时间复杂度都差不多,但第一种思路显然更符合人类的思考方式,所以如果是竞赛的同学多考虑第一种方法。

4.备忘录(记忆化搜索表)

备忘录方法为每一个子问题建立一个记录项,初始时,该记录项存入一个特殊的值,表示该子问题尚未被解决(比如斐波那契数的备忘录版本中将其设置为-1)。

在求解过程中,对每个待求解的子问题,首先查看其相应的记录项。若记录项中存储的是初始化时存入的特殊值,则表示该子问题是第一次遇到,此时计算出该子问题的解,并保存在相应的记录项中,以备以后查看。若记录项中存储的已不是初始化时存入的特殊值,则表示该子问题已被计算过,其相应的记录项存储的是该子问题的答 案。此时,只要从记录项中取出该子问题的答案即可,而不必重新计算。

4.1备忘录(记忆化搜索表)代码实现

#include <stdio.h>
#include <time.h>
#include <stdlib.h>// mem1[i] 记忆了第i个斐波那契数列的值
static unsigned int *mem1;
unsigned int fib02(unsigned int n) {if (n == 0) {return 0;}if (n == 1) {return 1;}if (mem1[n] == -1) {mem1[n] = fib02(n - 1) + fib02(n - 2);}return mem1[n];
}void test02() {int n = 46;mem1 = malloc(sizeof(unsigned int) * (n + 1));// 初始化这个表里,存储一个以后算法中永远不会出现的状态for (int i = 0; i <= n; ++i) {mem1[i] = -1;		}clock_t start = clock();unsigned int x = fib02(n);clock_t end = clock();printf("cost time: %f s。fib02 = %u\n", (double)(end - start) / CLOCKS_PER_SEC, x);free(mem1);
}

结果:

D:\work\DataStruct\cmake-build-debug\06_DP\fib.exe
cost time: 0.000000 s。fib02 = 1836311903进程已结束,退出代码为 0

可以看到,用的时间极短,我们存储了已经计过的值后,当遇到相同的值时我们直接返回就行不再需要重复计算,这大大减少了递归搜索树的节点,所以时间效率很高。

5.DP table

DP table就是动态规划算法自底向上建立的一个表格,用于保存每一个子问题的解,并返回表中的最后一个解。比如斐波那契数,我们先计算 fib(0),然后 fib(1),然后 fib(2),然后 fib(3),以此类推,直至计算出fib(n)。

比如我们计算 fib(5),先由 fib(0) + fib(1) 得到 fib(2),再由 fib(1) + fib(2) 得到 fib(3),再由 fib(2) + fib(3) 得到 fib(4),最后由 fib(3) + fib(4) 得到 fib(5)。

也就是说,我们只需要存储子问题的解,而不需要重复计算子问题的解。

5.1DP table代码实现

#include <stdio.h>
#include <time.h>
#include <stdlib.h>
unsigned int fib03(unsigned int n) {unsigned int *mem = malloc(sizeof(unsigned int) * (n + 1));//递推mem[0] = 0;mem[1] = 1;for (int i = 2; i <= n; ++i) {mem[i] = mem[i - 1] + mem[i - 2];}unsigned int result = mem[n];free(mem);return result;
}void test03() {int n = 146;clock_t start = clock();unsigned int x = fib03(n);clock_t end = clock();printf("cost time: %f s。fib03 = %u\n", (double)(end - start) / CLOCKS_PER_SEC, x);
}int main() {test03();return 0;
}
D:\work\DataStruct\cmake-build-debug\06_DP\fib.exe
cost time: 0.000000 s。fib03 = 2620762145进程已结束,退出代码为 0

可以看到即使数字变成了146,我们的时间效率还是很高,因为我们已经保存了很多节点的数字。

6.练习

问题描述:给定三个数{1,3,5},请问使用这三个数,有多少种方式可以构造出一个给定的数n(假设这里n等于6)(允许重复和不同顺序)
在这里插入图片描述
如图,这个题的递归搜索树大致如图,我没有画完全,大致能懂意思就行。

我们可以先写出递归的函数:

int combine01(int n) {if (n < 0) {return 0;}if (n == 1 || n == 0) {return 1;}return combine01(n - 1) + combine01(n - 3) + combine01(n - 5);
}

当n小于0的时候很明显没有组合方式,当n等于1或者n等于0的时候只有一种组合方式。

  1. 最后一步采用操作1(步长为1)
    如果最后一步是操作1,那么在此之前需要解决规模为 n-1 的问题

所以有 combine01(n-1) 种方式

  1. 最后一步采用操作3(步长为3)
    如果最后一步是操作3,那么在此之前需要解决规模为 n-3 的问题

所以有 combine01(n-3) 种方式

  1. 最后一步采用操作5(步长为5)
    如果最后一步是操作5,那么在此之前需要解决规模为 n-5 的问题

所以有 combine01(n-5) 种方式

总和起来就是我们所有的方法数,接下来先采用今天讲的备忘录(记忆化搜索表)实现:

#include <stdio.h>
#include <time.h>
#include <stdlib.h>
static int solve(int n, int *mem) {if (n < 0) {return 0;}if (n == 1 || n == 0) {return 1;}if (mem[n] == -1) {mem[n] = solve(n - 1, mem) + solve(n - 3, mem) + solve(n - 5, mem);}return mem[n];
}int combine02(int n) {int result;int *mem = malloc(sizeof(int) * (n + 1));for (int i = 0; i <= n; ++i) {mem[i] = -1;}result = solve(n, mem);free(mem);return result;
}int main() {printf("%d\n", combine03(6));return 0;
}

结果:

D:\work\DataStruct\cmake-build-debug\06_DP\cunNum.exe
8进程已结束,退出代码为 0

再来用DP table的方法:

#include <stdio.h>
#include <time.h>
#include <stdlib.h>
static int solve(int n, int *mem) {if (n < 0) {return 0;}if (n == 1 || n == 0) {return 1;}if (mem[n] == -1) {mem[n] = solve(n - 1, mem) + solve(n - 3, mem) + solve(n - 5, mem);}return mem[n];
}
int combine03(int n) {int *mem = malloc(sizeof(int) * (n + 1));mem[1] = 1;mem[2] = 1;mem[3] = 2;mem[4] = 3;mem[5] = 5;for (int i = 6; i <= n; ++i) {mem[i] = mem[i - 1] + mem[i - 3] + mem[i - 5];}int result = mem[n];free(mem);return result;
}int main() {printf("%d\n", combine03(6));return 0;

结果:

D:\work\DataStruct\cmake-build-debug\06_DP\cunNum.exe
8进程已结束,退出代码为 0

大家可以在LeetCode刷类似的题目,大概先写这些吧,今天的博客就先写到这,谢谢您的观看。

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

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

相关文章

[智能算法]可微的神经网络搜索算法-FBNet

一、概述 相较于基于强化学习的NAS&#xff0c;可微NAS能直接使用梯度下降更新模型结构超参数&#xff0c;其中较为有名的算法就是DARTS&#xff0c;其具体做法如下。 首先&#xff0c;用户需要定义一些候选模块&#xff0c;这些模块内部结构可以互不相同&#xff08;如设置不同…

Elasticsearch安装启动常见问题全解析

文章目录&#x1f4da; Elasticsearch 安装与启动问题总结一、核心问题概览二、详细问题分析与解决方案1. &#x1f510; **权限问题&#xff1a;AccessDeniedException**❌ 错误日志&#xff1a;&#x1f4cc; 原因&#xff1a;✅ 解决方案&#xff1a;2. ⚙️ **配置冲突&…

Uniapp中使用renderjs实现OpenLayers+天地图的展示与操作

Uniapp中自带的地图组件对支持的地图服务略有局限&#xff0c;同时&#xff0c;该组件在样式布局上层级过高且无法控制&#xff0c;无法满足部分高度自定义化的需求。故引入renderjs视图层工具搭配OpenLayers框架对地图功能进行实现&#xff0c;但由于renderjs的限制&#xff0…

从C++开始的编程生活(8)——内部类、匿名对象、对象拷贝时的编译器优化和内存管理

前言 本系列文章承接C语言的学习&#xff0c;需要有C语言的基础才能学会哦~ 第8篇主要讲的是有关于C的内部类、匿名对象、对象拷贝时的编译器优化和内存管理。 C才起步&#xff0c;都很简单&#xff01;&#xff01; 目录 前言 内部类 性质 匿名对象 性质 ※对象拷贝时的…

MT5追大速率回测BUG

将MT5策略测试器中的回测速率调到最大(最快速度),**确实非常容易导致出现不符合策略逻辑的秒级成交(闪电交易)**。这并非MT5的“bug”,而是由**回测引擎的工作方式**与**策略代码的编写方法**在高速运行下不匹配所导致的。 --- ### 为什么最大速率会导致问题? MT5回测…

[数据结构——lesson10.堆及堆的调整算法]

引言 上节我们学习完二叉树后[数据结构——lesson9.二叉树]&#xff0c;这节我们将学习数据结构——堆 学习目标 1.堆的概念及结构 堆是一种特殊的完全二叉树结构&#xff0c;在计算机科学和数据结构中广泛应用&#xff0c;特别是在堆排序算法和优先队列的实现中&#xff0c;…

九识智能与北控北斗合作研发的L4级燃气超微量高精准泄漏检测无人车闪耀服贸会,守护城市安全

2025年9月10日至14日&#xff0c;2025年中国国际服务贸易交易会将于北京首钢园举办。在这场国际盛会上&#xff0c;九识智能与北京北控北斗科技投资有限公司&#xff08;以下简称“北控北斗”&#xff09;合作研发的L4级燃气超微量高精准泄漏检测无人车及相关系统解决方案&…

【C语言入门】手把手教你实现顺序栈

栈是计算机科学中最基础且重要的数据结构之一&#xff0c;它遵循"后进先出"&#xff08;LIFO&#xff09;的原则。想象一下一叠盘子&#xff0c;你只能从最上面取放&#xff0c;这就是栈的直观体现。本文将用C语言带你一步步实现一个顺序栈&#xff0c;即使你是编程小…

北斗导航 | ARAIM(高级接收机自主完好性监测)算法在民航LPV-200进近中的具体实现流程

要详细说明ARAIM(高级接收机自主完好性监测)算法在民航LPV-200进近中的具体实现流程,需结合ARAIM的核心逻辑(多星座融合、多假设解分离、风险优化分配)与LPV-200的严格要求(垂直保护级VPL≤35米、垂直告警限VAL=35米、有效监测门限EMT≤15米等),以下是 step-by-step 的…

AIPex:AI + 自然语言驱动的浏览器自动化扩展

AIPex:AI + 自然语言驱动的浏览器自动化扩展 引言 一、快速上手 1.1 安装AIPex扩展 1.2 首次配置 1.3 界面介绍 第二章:30+工具详解 2.1 标签页管理工具集 🗂️ **get_all_tabs - 全局标签页概览** 🎯 **switch_to_tab - 智能标签页切换** 📋 **标签页批量操作** 📋 …

机器学习模型可信度与交叉验证:通俗讲解

先从一个故事说起&#xff1a;农场里的火鸡科学家&#xff0c;观察了一年发现“每天上午11点必有食物”&#xff0c;结果感恩节当天&#xff0c;它没等到食物&#xff0c;反而成了人类的食物。这个故事告诉我们&#xff1a;只靠过去的经验下结论&#xff0c;很可能出错——机器…

HTML5和CSS3新增的一些属性

1、HTML5新增特性这些新特性都有兼容性问题&#xff0c;基本是IE9以上版本浏览器才支持1&#xff09;新增语义化标签2&#xff09;新增多媒体标签音频&#xff1a;<audio>视频&#xff1a;<video>&#xff08;1&#xff09;视频<video>---尽量使用mp4格式<…

Redis的RedLock

RedLock算法深度解析RedLock是Redis作者针对分布式环境设计的多节点锁算法&#xff0c;核心目标是解决单点Redis在分布式锁场景中的可靠性缺陷。传统方案的局限性单节点Redis锁的问题单点故障&#xff1a;单个Redis实例宕机导致所有锁服务不可用可靠性不足&#xff1a;无法保证…

SpringMVC @RequestMapping的使用演示和细节 详解

目录 一、RequestMapping是什么&#xff1f; 二、RequestMapping 的使用演示 1.RequestMapping在方法上的使用&#xff1a; 2.RequestMapping同时在类和方法上使用&#xff1a; 3.RequestMapping指定请求参数&#xff1a; 4.RequestMapping使用Ant风格URL&#xff1a; 5.Requ…

flutter项目 -- 换logo、名称 、签名、打包

1、换logo, 透明底&#xff0c;下面5个尺寸&#xff0c;需要UI设计2、换名没配置型的改名方式如下 打开app/src/main/AndroidManifest.xml3、签名 运行 flutter doctor -vD:\project\Apk\keystore 自己建立的keystore文件夹&#xff0c; 注意命令后是 megoai-release-key(自…

【贪心算法】day9

&#x1f4dd;前言说明&#xff1a; 本专栏主要记录本人的贪心算法学习以及LeetCode刷题记录&#xff0c;按专题划分每题主要记录&#xff1a;&#xff08;1&#xff09;本人解法 本人屎山代码&#xff1b;&#xff08;2&#xff09;优质解法 优质代码&#xff1b;&#xff…

linux C 语言开发 (八) 进程基础

文章的目的为了记录使用C语言进行linux 开发学习的经历。开发流程和要点有些记忆模糊&#xff0c;赶紧记录&#xff0c;防止忘记。 相关链接&#xff1a; linux C 语言开发 (一) Window下用gcc编译和gdb调试 linux C 语言开发 (二) VsCode远程开发 linux linux C 语言开发 (…

从零学算法1094

1094.拼车 车上最初有 capacity 个空座位。车 只能 向一个方向行驶&#xff08;也就是说&#xff0c;不允许掉头或改变方向&#xff09; 给定整数 capacity 和一个数组 trips , trips[i] [numPassengersi, fromi, toi] 表示第 i 次旅行有 numPassengersi 乘客&#xff0c;接他…

B2B企业营销型AI Agent服务商推荐:谁更专业?如何选型?

一、引言&#xff1a;为什么B2B企业需要营销型AI Agent&#xff1f;在当前竞争激烈的B2B市场中&#xff0c;企业普遍面临几大挑战&#xff1a;线索获取难&#xff1a;获客成本持续上升&#xff0c;高质量线索难以筛选。销售周期长&#xff1a;从初步接触到签单&#xff0c;往往…

算法-双指针5.6

目录 &#x1f33f;力扣611-有效三角形得个数 &#x1f9ca;题目链接&#xff1a;https://leetcode.cn/problems/valid-triangle-number/description/ &#x1f9ca;题目描述&#xff1a;​编辑 &#x1f9ca;解题思路&#xff1a; &#x1f9ca;解题代码&#xff1a; &a…