动态规划(Dynamic Programming,简称 DP)是一种用于解决最优化问题的方法。其核心思想是通过将复杂问题分解为更小的子问题,保存子问题的解,并避免重复计算,以此提高效率。动态规划通常用于解决具有最优子结构和重叠子问题性质的问题。
基本步骤
1. 确定状态:定义问题中的状态变量,这些状态变量通常代表问题的解的部分信息。
2. 状态转移方程:根据问题的性质,给出状态之间的关系,这通常是由已知状态推导出未知状态。
3. 初始化:设定初始状态。
4. 结果输出:根据计算得到的状态,返回最优解或所需结果。
应用示例
斐波那契数列
斐波那契数列的定义是:
F(0)=0, F(1)=1, F(n)=F(n−1)+F(n−2) (n≥2)
#include <iostream>
#include <vector>
using namespace std;
int fib(int n) {
if (n <= 1) return n;
vector<int> dp(n + 1, 0); // dp[i]表示第i个斐波那契数
dp[0] = 0;
dp[1] = 1;
for (int i = 2; i <= n; ++i) {
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp[n];
}
int main() {
int n;
cout << "Enter n: ";
cin >> n;
cout << "Fibonacci of " << n << " is " << fib(n) << endl;
return 0;
}
背包问题
给定一个背包容量为 W
,有 n
个物品,每个物品的重量为 wi
,价值为 vi
。问如何选择物品,使得在不超过背包容量的情况下,背包中的物品的总价值最大。
#include <iostream>
#include <vector>
using namespace std;
int knapsack(int W, const vector<int>& weights, const vector<int>& values, int n) {
vector<vector<int>> dp(n + 1, vector<int>(W + 1, 0)); // dp[i][w]表示前i个物品,背包容量为w时的最大价值
for (int i = 1; i <= n; ++i) {
for (int w = 1; w <= W; ++w) {
if (weights[i - 1] <= w) {
// 可以选择当前物品或不选择
dp[i][w] = 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][W]; // 返回最大价值
}
int main() {
int n, W;
cout << "Enter number of items and knapsack capacity: ";
cin >> n >> W;
vector<int> weights(n), values(n);
cout << "Enter weights and values: ";
for (int i = 0; i < n; ++i) {
cin >> weights[i] >> values[i];
}
cout << "Maximum value in knapsack: " << knapsack(W, weights, values, n) << endl;
return 0;
}
最长公共子序列
给定两个字符串,求它们的最长公共子序列的长度。LCS 的定义是:在不改变字符顺序的前提下,删除一些字符使得剩下的字符是两个字符串的公共子序列。
#include <iostream>
#include <vector>
#include <string>
using namespace std;
int lcs(const string& s1, const string& s2) {
int m = s1.length(), n = s2.length();
vector<vector<int>> dp(m + 1, vector<int>(n + 1, 0)); // dp[i][j]表示s1[0..i-1]和s2[0..j-1]的LCS长度
for (int i = 1; i <= m; ++i) {
for (int j = 1; j <= n; ++j) {
if (s1[i - 1] == s2[j - 1]) {
dp[i][j] = dp[i - 1][j - 1] + 1; // 如果相等,LCS长度加1
} else {
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]); // 否则取最大值
}
}
}
return dp[m][n]; // 返回LCS的长度
}
int main() {
string s1, s2;
cout << "Enter two strings: ";
cin >> s1 >> s2;
cout << "Length of Longest Common Subsequence: " << lcs(s1, s2) << endl;
return 0;
}
最短路径问题
在图中寻找从一个源点到所有其他点的最短路径,可以使用动态规划的思想结合贪心算法来解决。这里给出一个用 Dijkstra 算法求解单源最短路径的 C++ 实现。
#include <iostream>
#include <vector>
#include <climits>
#include <queue>
using namespace std;
typedef pair<int, int> pii; // (距离, 节点)
void dijkstra(int n, int source, const vector<vector<pii>>& adj) {
vector<int> dist(n, INT_MAX);
dist[source] = 0;
priority_queue<pii, vector<pii>, greater<pii>> pq;
pq.push({0, source});
while (!pq.empty()) {
int u = pq.top().second;
int d = pq.top().first;
pq.pop();
if (d > dist[u]) continue;
for (const auto& edge : adj[u]) {
int v = edge.first, w = edge.second;
if (dist[u] + w < dist[v]) {
dist[v] = dist[u] + w;
pq.push({dist[v], v});
}
}
}
for (int i = 0; i < n; ++i) {
if (dist[i] == INT_MAX)
cout << "INF ";
else
cout << dist[i] << " ";
}
cout << endl;
}
int main() {
int n, m;
cout << "Enter number of nodes and edges: ";
cin >> n >> m;
vector<vector<pii>> adj(n); // 邻接表
cout << "Enter the edges (u, v, w): ";
for (int i = 0; i < m; ++i) {
int u, v, w;
cin >> u >> v >> w;
adj[u].push_back({v, w});
adj[v].push_back({u, w}); // 如果是无向图
}
int source;
cout << "Enter the source node: ";
cin >> source;
cout << "Shortest distances from source " << source << ": ";
dijkstra(n, source, adj);
return 0;
}
字符串编辑距离
字符串编辑距离(Edit Distance)问题,又称为Levenshtein 距离,是衡量两个字符串之间的相似度的标准。它表示将一个字符串转换成另一个字符串所需的最少编辑操作次数。
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
using namespace std;
// 动态规划实现字符串编辑距离
int minDistance(const string& s1, const string& s2) {
int m = s1.length();
int n = s2.length();
// 创建一个二维dp数组,dp[i][j]表示将s1[0..i-1]转换为s2[0..j-1]的最小操作数
vector<vector<int>> dp(m + 1, vector<int>(n + 1, 0));
// 初始化边界条件:空字符串转换
for (int i = 0; i <= m; ++i) dp[i][0] = i; // 从s1转换到空字符串
for (int j = 0; j <= n; ++j) dp[0][j] = j; // 从空字符串转换到s2
// 填充dp表格
for (int i = 1; i <= m; ++i) {
for (int j = 1; j <= n; ++j) {
if (s1[i - 1] == s2[j - 1]) {
dp[i][j] = dp[i - 1][j - 1]; // 字符相同,不需要任何操作
} else {
dp[i][j] = min({
dp[i - 1][j] + 1, // 删除字符
dp[i][j - 1] + 1, // 插入字符
dp[i - 1][j - 1] + 1 // 替换字符
});
}
}
}
return dp[m][n]; // 返回最终的编辑距离
}
int main() {
string s1, s2;
cout << "Enter the first string: ";
cin >> s1;
cout << "Enter the second string: ";
cin >> s2;
int result = minDistance(s1, s2);
cout << "The minimum edit distance is: " << result << endl;
return 0;
}
总结
动态规划是一种强大的算法设计技巧,通过优化子问题的解来减少重复计算,显著提升算法效率。常见的应用场景包括:最短路径、背包问题、字符串匹配、图算法等。