动态规划
动态规划简介
动态规划(DP)是一种通过将复杂问题分解为更简单的子问题来解决复杂问题的算法技术。它存储子问题的结果以避免重复计算,使其比朴素的递归方法更高效。
DP问题有两个关键属性:最优子结构(最优解包含子问题的最优解)和重叠子问题(相同的子问题被多次求解)。
基础DP:斐波那契记忆化
斐波那契数列是DP的完美入门。比较朴素递归方法与记忆化和自底向上解决方案。
斐波那契:递归 vs DP
#include <iostream>
#include <vector>
#include <chrono>
using namespace std;
using namespace std::chrono;
class Fibonacci {
public:
// Naive recursion - O(2^n)
long long fibRecursive(int n) {
if (n <= 1) return n;
return fibRecursive(n - 1) + fibRecursive(n - 2);
}
// Top-down DP (Memoization) - O(n)
long long fibMemo(int n, vector<long long>& memo) {
if (n <= 1) return n;
if (memo[n] != -1) {
return memo[n];
}
memo[n] = fibMemo(n - 1, memo) + fibMemo(n - 2, memo);
return memo[n];
}
// Bottom-up DP (Tabulation) - O(n)
long long fibDP(int n) {
if (n <= 1) return n;
vector<long long> dp(n + 1);
dp[0] = 0;
dp[1] = 1;
for (int i = 2; i <= n; i++) {
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp[n];
}
// Space-optimized DP - O(1) space
long long fibOptimized(int n) {
if (n <= 1) return n;
long long prev2 = 0, prev1 = 1, current;
for (int i = 2; i <= n; i++) {
current = prev1 + prev2;
prev2 = prev1;
prev1 = current;
}
return current;
}
void comparePerformance(int n) {
cout << "Computing Fibonacci(" << n << "):" << endl;
// Test memoization
vector<long long> memo(n + 1, -1);
auto start = high_resolution_clock::now();
long long result1 = fibMemo(n, memo);
auto end = high_resolution_clock::now();
auto duration1 = duration_cast<microseconds>(end - start);
cout << "Memoization: " << result1 << " (Time: " << duration1.count() << " μs)" << endl;
// Test bottom-up DP
start = high_resolution_clock::now();
long long result2 = fibDP(n);
end = high_resolution_clock::now();
auto duration2 = duration_cast<microseconds>(end - start);
cout << "Bottom-up DP: " << result2 << " (Time: " << duration2.count() << " μs)" << endl;
// Test optimized
start = high_resolution_clock::now();
long long result3 = fibOptimized(n);
end = high_resolution_clock::now();
auto duration3 = duration_cast<microseconds>(end - start);
cout << "Optimized: " << result3 << " (Time: " << duration3.count() << " μs)" << endl;
}
};
int main() {
Fibonacci fib;
fib.comparePerformance(40);
return 0;
}记忆化和制表法通过避免重复计算显著提高性能。
经典DP:0/1背包问题
背包问题是DP的基础。给定具有重量和价值的物品,找出能够装入给定容量背包中的最大价值。
0/1背包实现
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
class Knapsack {
public:
// Recursive solution with memoization
int knapsackMemo(int capacity, vector<int>& weights, vector<int>& values,
int n, vector<vector<int>>& memo) {
// Base case
if (n == 0 || capacity == 0) {
return 0;
}
// Check if already computed
if (memo[n][capacity] != -1) {
return memo[n][capacity];
}
// If weight of nth item is more than capacity, it cannot be included
if (weights[n - 1] > capacity) {
memo[n][capacity] = knapsackMemo(capacity, weights, values, n - 1, memo);
} else {
// Return maximum of two cases:
// 1. nth item included
// 2. nth item not included
int include = values[n - 1] + knapsackMemo(capacity - weights[n - 1],
weights, values, n - 1, memo);
int exclude = knapsackMemo(capacity, weights, values, n - 1, memo);
memo[n][capacity] = max(include, exclude);
}
return memo[n][capacity];
}
// Bottom-up DP solution
int knapsackDP(int capacity, vector<int>& weights, vector<int>& values, int n) {
// Create DP table
vector<vector<int>> dp(n + 1, vector<int>(capacity + 1, 0));
// Build table dp[][] in bottom-up manner
for (int i = 1; i <= n; i++) {
for (int w = 1; w <= capacity; w++) {
if (weights[i - 1] <= w) {
// Take maximum of including or excluding current item
dp[i][w] = max(values[i - 1] + dp[i - 1][w - weights[i - 1]],
dp[i - 1][w]);
} else {
// Cannot include current item
dp[i][w] = dp[i - 1][w];
}
}
}
return dp[n][capacity];
}
// Space-optimized version
int knapsackOptimized(int capacity, vector<int>& weights, vector<int>& values, int n) {
vector<int> dp(capacity + 1, 0);
for (int i = 0; i < n; i++) {
// Traverse in reverse to avoid using updated values
for (int w = capacity; w >= weights[i]; w--) {
dp[w] = max(dp[w], dp[w - weights[i]] + values[i]);
}
}
return dp[capacity];
}
// Function to print which items are selected
void printSelectedItems(int capacity, vector<int>& weights, vector<int>& values, int n) {
vector<vector<int>> dp(n + 1, vector<int>(capacity + 1, 0));
// Build DP table
for (int i = 1; i <= n; i++) {
for (int w = 1; w <= capacity; w++) {
if (weights[i - 1] <= w) {
dp[i][w] = max(values[i - 1] + dp[i - 1][w - weights[i - 1]],
dp[i - 1][w]);
} else {
dp[i][w] = dp[i - 1][w];
}
}
}
// Backtrack to find selected items
int res = dp[n][capacity];
int w = capacity;
cout << "Selected items: ";
for (int i = n; i > 0 && res > 0; i--) {
if (res != dp[i - 1][w]) {
cout << "Item " << i << " (weight: " << weights[i - 1]
<< ", value: " << values[i - 1] << ") ";
res -= values[i - 1];
w -= weights[i - 1];
}
}
cout << endl;
}
};
int main() {
vector<int> values = {60, 100, 120};
vector<int> weights = {10, 20, 30};
int capacity = 50;
int n = values.size();
Knapsack ks;
// Test memoization approach
vector<vector<int>> memo(n + 1, vector<int>(capacity + 1, -1));
cout << "Maximum value (Memoization): "
<< ks.knapsackMemo(capacity, weights, values, n, memo) << endl;
// Test bottom-up DP
cout << "Maximum value (Bottom-up DP): "
<< ks.knapsackDP(capacity, weights, values, n) << endl;
// Test optimized version
cout << "Maximum value (Optimized): "
<< ks.knapsackOptimized(capacity, weights, values, n) << endl;
// Print selected items
ks.printSelectedItems(capacity, weights, values, n);
return 0;
}背包DP表存储不同容量和物品子集的最大值。
最长公共子序列 (LCS)
LCS找到两个序列共同的最长子序列。它用于diff算法、DNA分析和抄袭检测。
最长公共子序列
#include <iostream>
#include <vector>
#include <string>
using namespace std;
class LCS {
public:
// Find length of LCS
int lcsLength(string text1, string text2) {
int m = text1.length();
int n = text2.length();
// Create DP table
vector<vector<int>> dp(m + 1, vector<int>(n + 1, 0));
// Build the DP table
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
if (text1[i - 1] == text2[j - 1]) {
dp[i][j] = dp[i - 1][j - 1] + 1;
} else {
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
}
}
}
return dp[m][n];
}
// Find actual LCS string
string lcsString(string text1, string text2) {
int m = text1.length();
int n = text2.length();
vector<vector<int>> dp(m + 1, vector<int>(n + 1, 0));
// Build the DP table
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
if (text1[i - 1] == text2[j - 1]) {
dp[i][j] = dp[i - 1][j - 1] + 1;
} else {
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
}
}
}
// Backtrack to construct LCS
string lcs = "";
int i = m, j = n;
while (i > 0 && j > 0) {
if (text1[i - 1] == text2[j - 1]) {
lcs = text1[i - 1] + lcs;
i--;
j--;
} else if (dp[i - 1][j] > dp[i][j - 1]) {
i--;
} else {
j--;
}
}
return lcs;
}
// Space-optimized LCS (only length)
int lcsOptimized(string text1, string text2) {
int m = text1.length();
int n = text2.length();
// Use only two rows
vector<int> prev(n + 1, 0);
vector<int> curr(n + 1, 0);
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
if (text1[i - 1] == text2[j - 1]) {
curr[j] = prev[j - 1] + 1;
} else {
curr[j] = max(prev[j], curr[j - 1]);
}
}
prev = curr;
}
return curr[n];
}
void printDPTable(string text1, string text2) {
int m = text1.length();
int n = text2.length();
vector<vector<int>> dp(m + 1, vector<int>(n + 1, 0));
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
if (text1[i - 1] == text2[j - 1]) {
dp[i][j] = dp[i - 1][j - 1] + 1;
} else {
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
}
}
}
cout << "DP Table:" << endl;
cout << " ";
for (char c : text2) cout << " " << c;
cout << endl;
for (int i = 0; i <= m; i++) {
if (i == 0) cout << " ";
else cout << text1[i - 1] << " ";
for (int j = 0; j <= n; j++) {
cout << " " << dp[i][j];
}
cout << endl;
}
}
};
int main() {
LCS lcs;
string text1 = "ABCDGH";
string text2 = "AEDFHR";
cout << "Text 1: " << text1 << endl;
cout << "Text 2: " << text2 << endl;
cout << endl;
cout << "LCS Length: " << lcs.lcsLength(text1, text2) << endl;
cout << "LCS String: " << lcs.lcsString(text1, text2) << endl;
cout << "LCS Length (Optimized): " << lcs.lcsOptimized(text1, text2) << endl;
cout << endl;
lcs.printDPTable(text1, text2);
return 0;
}LCS构建一个表,其中dp[i][j]表示text1的前i个字符和text2的前j个字符的LCS长度。