C++ 记忆化搜索算法详解
一、基本概念
记忆化搜索(Memoization) 是一种通过缓存子问题结果来优化递归算法的技术。核心思想是避免重复计算,将已解决的子问题结果存储起来,需要时直接取用,而非重新计算。它本质上是自顶向下的动态规划方法。
二、适用场景
- 递归问题存在重叠子问题:如斐波那契数列、爬楼梯问题。
- 状态空间可被唯一标识:每个子问题可由一组参数唯一确定。
- 子问题数目有限:缓存空间不会过大。
三、实现步骤
- 定义递归函数:明确函数参数及返回值。
- 设计缓存结构:选择数组、哈希表等存储结果。
- 检查缓存:在函数开头检查当前状态是否已计算。
- 递归计算并缓存:若未计算,递归求解并保存结果。
- 处理边界条件:确保递归终止条件正确。
四、C++ 代码示例
示例1:斐波那契数列
#include <iostream>
#include <vector>
using namespace std;
vector<int> memo;
int fib(int n) {
if (n <= 1) return n;
if (memo[n] != -1) return memo[n]; // 检查缓存
memo[n] = fib(n-1) + fib(n-2); // 计算并缓存
return memo[n];
}
int main() {
int n = 40;
memo.resize(n+1, -1); // 初始化为-1表示未计算
cout << fib(n); // 输出:102334155
return 0;
}
示例2:0-1背包问题
#include <iostream>
#include <vector>
using namespace std;
int knapsack(int W, int wt[], int val[], int n, vector<vector<int>>& memo) {
if (n == 0 || W == 0) return 0;
if (memo[n][W] != -1) return memo[n][W]; // 检查缓存
if (wt[n-1] > W) {
memo[n][W] = knapsack(W, wt, val, n-1, memo);
} else {
int include = val[n-1] + knapsack(W - wt[n-1], wt, val, n-1, memo);
int exclude = knapsack(W, wt, val, n-1, memo);
memo[n][W] = max(include, exclude); // 计算并缓存
}
return memo[n][W];
}
int main() {
int val[] = {60, 100, 120};
int wt[] = {10, 20, 30};
int W = 50;
int n = 3;
vector<vector<int>> memo(n+1, vector<int>(W+1, -1)); // 二维缓存
cout << knapsack(W, wt, val, n, memo); // 输出:220
return 0;
}
五、注意事项
- 状态设计:确保参数唯一标识子问题(如背包问题的物品索引和容量)。
- 缓存选择:
- 数组:适用于状态参数为连续整数。
- 哈希表:适用于非连续或复合状态(需自定义哈希函数)。
- 初始化:将缓存初始化为特殊值(如-1),表示未计算。
- 栈溢出:递归深度过大会导致栈溢出,可改用迭代动态规划。
六、性能分析
- 时间复杂度:从指数级(如O(2ⁿ))优化到多项式级(如O(nW))。
- 空间复杂度:取决于状态空间大小(如O(n)或O(nW))。
七、进阶技巧
-
哈希表优化:使用
unordered_map
存储非连续状态。#include <unordered_map> unordered_map<string, int> cache; // 复合键转为字符串
-
尾递归优化:减少栈深度,但C++支持有限,需谨慎使用。
-
滚动数组:动态规划中优化空间,但记忆化搜索中较少用。
八、总结
记忆化搜索通过缓存子问题结果,显著提升递归算法效率,适用于具有重叠子问题的场景。在C++中灵活运用数组或哈希表存储状态,能有效解决复杂问题如动态规划、组合优化等。