C++数据结构与算法 动态规划
动态规划:
和贪婪算法一样,动态规划对一个问题的解是一系列的决策过程,贪婪算法依据贪婪准则,做出的每一个抉择都是不可撤回的,在动态规划中,需要考察一系列抉择,已确定一个最优抉择序列是否包含一个最优抉择子序列。
例如,在上图中,要从节点1到达节点5,选择一条最短的路径,第一步可以选择的顶点为2,3,4。假设选择顶点3, 然后决定如何从顶点3到达顶点5。如果选择了一条从顶点3到顶点5一条不是最短的路径,那么即使第一步选择了从顶点1到顶点3这条最短的路径,连起来构成从1到5的路径也不是最短的。
所以在最短路径中,如果第一次选择的是某一个顶点v,那么接下来所选择的从v到d的路径必须是最短的。
2. 背包问题:
在0/1背包问题中,需要选择 的值,如果选择
,那么背包问题就转换为物品为
背包容量认为c的问题,如果选择
,则背包问题转换为物品为
背包容量认为
的问题.,另
表示剩余背包的容量。
在第一次选择之后,需要考虑使用剩余的物品满足装在容量为 r 的背包,剩余的物品(2到n)和剩余的容量r规定了在第一次选择之后问题的状态。不管第一次选择的结果如何,必须是这个问题状态的一个最优解。如果不是,那么必有一个更好的选择,假设为
,于是
便是初始问题的一个更好的解。
当最优抉择包含最优抉择系序列的时候,可以建立动态规划递归方程,可以高效的解决问题。
0/1背包问题:
在背包问题中,最优选择序列由最优选择子序列构成,假设 f(i,y) 表示剩余容量为y, 剩余物品为 i,i+1,...n
的背包问题的最优解的值(0/1)。
剩余第n个物品,如果剩余的容量y大于该物品的体积 ,则 f(n,y) 的返回值为p_n,否则为0;
所以有:
根据最优序列由最优子序列构成的结论,可以得到上面的递推公式。
所以无论第一次的选择是什么,接下来的选择一定是当前状态下的最优选择。称此为最优原则。
动态规划求解的步骤:
1. 证实最有原则是适用的
2. 建立动态规划的递归方程
3. 求解动态规划的递归方程以获得最优解
4. 沿着最优解的生成过程进行回溯
关于编程求解动态规划的问题:
在求解动态规划递归方程的规程中,应该避免递归中的重复计算。可以通过以迭代的方式来避免递归过程中的重复计算。迭代方式和递归方式拥有相同的复杂度,但是迭代方式不需要附加的递归栈空间。
上述背包问题的递归写法:
#include <iostream> #include <algorithm> using namespace std; // 背包问题的递归解法 int f(int i, int capacity) { // 参数 : // i:第i个物品 // capacity: 背包剩余的容量 int number_of_object = 3; // 物品的个数 int weight[] = {100, 14, 10}; // 每个物品的权重 int profit[] = {20, 18, 15}; if(i == number_of_object) { return (capacity < weight[number_of_object-1]?0:profit[number_of_object-1]); // 递归的终止条件 } if(capacity < weight[i-1]) { return f(i+1, capacity); } return max(f(i+1, capacity), f(i+1, capacity-weight[i-1])+profit[i-1]); } int main(int argc, char *argv[]) { cout << f(1, 116) << endl; return 0; }
上面的程序以递归的方式求解,递归需要额外的栈空间,下面对上述的递归方法做一下改进:
1. 在问题中,假设n=5, 物品的重量为weight = [2, 2, 6, 5, 4], 物品的权重profit = [6, 3, 5, 4, 6], 且c = 10;
求解 f(1, 10)的递归调用过程如下所示:
为了避免对 f(i,y) 的重复计算,可以建立一个列表对其值进行存储,在本题中,可以使用整形数组对数据进行存储,farray[i, y]
通过这种方法,可以将时间复杂度从原来的O(n^2)降到O(cn)
代码如下所示:
#include <iostream> #include <algorithm> using namespace std; // 背包问题的递归解法 int f(int i, int capacity) { // 参数 : // i:第i个物品 // capacity: 背包剩余的容量 int number_of_object = 3; // 物品的个数 int weight[] = {100, 14, 10}; // 每个物品的权重 int profit[] = {20, 18, 15}; if(i == number_of_object) { return (capacity < weight[number_of_object-1]?0:profit[number_of_object-1]); // 递归的终止条件 } if(capacity < weight[i-1]) { return f(i+1, capacity); } return max(f(i+1, capacity), f(i+1, capacity-weight[i-1])+profit[i-1]); } int f_1(int i, int capacity) { // 参数 // i: 剩余i, i+1,...n个物品 // capacity: 剩余的背包容量 // 利用矩阵farray[i][y]存储f(i,y)的值, 维度为(n+1)*(c+1) // n: 物品个数 // c:背包的容量 int number_of_object = 5; int weight[] = {2, 2, 6, 5, 4}; int profit[] = {6, 3, 5, 4, 6}; int n = 5; int c = 10; int** farray = new int*[n+1]; for(int k=0; k<=n; k++) { farray[k] = new int[c+1]; } for(int p=0; p<=n; p++) { for(int q=0; q<=c; q++) { farray[p][q] = -1; } } // 检查需要的值是否已经计算过了,如果已经计算过了,则直接返回 if(farray[i][capacity] > 0) { return farray[i][capacity]; } // 还没有计算过 if(i==number_of_object) { farray[i][capacity] = (capacity < weight[i-1]?0:profit[i-1]); return farray[i][capacity]; } if(capacity<weight[i]) { farray[i][capacity] = f(i+1, capacity); } else { farray[i][capacity] = max(f(i+1, capacity), f(i+1, capacity-weight[i-1])+profit[i-1]); } int result = farray[i][capacity]; // 释放内存 for(int k=0; k<=n; k++) { delete [] farray[k]; } delete [] farray; return result; // 返回计算的值 } int main(int argc, char *argv[]) { cout << f_1(1, 10) << endl; return 0; }
矩阵乘法链
问题描述:
矩阵A(mxn)与矩阵B(nxp)相乘需用时,将mnp作为两个矩阵相乘所需时间的测量值,假设要计算多个矩阵的乘积,一种是(AB)C,另一种是A(BC),虽然计算结果相同,但是时间性能会有很大的差异。
假设:
A(100x1), B(1x100), C(100x1)
1. (A*B)*C
所需时间 t=100*1*100 + 100*100*100 = 1010000, 此外,计算A*B需要100x100的矩阵来存储中间结果;
2.A*(B*C)
所需时间 t=1x100x1 + 100x1x1, 且只需要一个存储单元来存储中间结果:
将问题推广到一般的情况下,假设有q个矩阵相乘,M1xM2XM3....xMq, 则相乘的方式随着q的增加以指数及增长,因此,当q很大的时候,考虑每一种乘法顺序并选择最优者是不切实际的。
动态规划方法:
可以使用动态规划来求解一个矩阵相乘的乘法顺序,动态规划可以将算法的时间复杂度降为O(q^3)
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)