本文给出了动态规划的简要定义、适用场景、算法实现。并给出了四种经典动态规划:钢条切割求最大收益问题、矩阵链相乘求最小乘法次数问题、最长公共子序列问题、求最小的搜索代价的最优二叉搜索树的c++代码实现。
- 定义
- 性质 适用条件
- 算法实现过程
-
- 首先观察问题是否满足最优子结构性质
- 写出递归等式递归的定义子问题的最优解
- 求解子问题的最优解
- 构造最优解
-
- 四个经典问题的cpp实现
- 1 钢条切割
- 2 矩阵链相乘
- 3 最长公共子序列
- 4 最优二叉搜索树
- 代码下载
1. 定义
动态规划(dynamic programming)与分治法类似,都是通过组合子问题求解原问题。(在这里,programming 指表格法。而非“编程”)。分治法是将原问题分为互不相交
的子问题,递归的求解子问题,再将它们组合起来,求出原问题的解。与之相反,动态规划应用于子问题重叠的情况,即不同的子问题具有公共的子子问题,动态规划算法对每个子子问题只求解一次,将其解保存在表格中,从而不用每次求解该子子问题时都要反复计算。
总结来讲:分治法的子子问题是全新的,动态规划的子子问题是有重叠的。
2. 性质 | 适用条件
使用动态规划必须满足的2条性质:
- 最优子结构性质:问题的最优解由相关子问题的最优解组合而成,而这些子问题可以独立求解。
- 重叠子问题:递归算法会反复求解相同的子问题,而不是一直生成新的子问题,(对比分治法:递归的每一步都生成全新的子问题),动态规划算法能够最每个子问题都只求解一次,将解存表中,需要时直接查表。但是应该注意,同一问题的其中一个子问题的解不能影响另一个子问题的解,即无关。换句话说,当前问题的每一次划分生成的子问题之间不存在重复的资源。
3. 算法实现过程
1. 首先观察问题是否满足最优子结构性质
2. 写出递归等式,递归的定义子问题的最优解
3. 求解子问题的最优解
动态规划的实现有2种等价的方法:
1. 带备忘的自顶向下(递归)
仍按原始的递归形式,只是在递归求解过程中记录每个子问题的解,存储到数组(通常在使用前会初始化值,常用正负无穷)中。当需要一个子问题的解时,首先检查是否保存过此解,如果保存过(非初始值),则直接返回该解,节省计算时间;否则,按常规方式计算该子问题的解并记录。
2. 自底向上
4. 构造最优解
通常根据第2步中的递归等式,可倒推出重构方式。设计递归式,分解子问题时,一般都通过定义一个分割点,那么同样的根据这个分割点分割隐含最优解的矩阵。
4. 四个经典问题的cpp实现
以下给出了四个经典动态规划问题的c++实现:
钢条切割求最大收益问题、矩阵链相乘求最小乘法次数问题、最长公共子序列问题、求最小的搜索代价的最优二叉搜索树。
只给出代码实现,具体算法过程参见《算法导论》P205-231
4.1 钢条切割
#include <iostream>
using namespace std;
class Rod{
private:
int rod_len;
int *memorized_q;
int *s;
int times1,times2,times3; // 统计计算次数
public:
Rod(int n) {
times1 = 0;
times2 = 0;
times3 = 0;
rod_len = n;
s = new int [rod_len+1];
memorized_q = new int [rod_len+1];
for (int i = 0; i <= rod_len; i++)
memorized_q[i] = INT_MIN;
}
~Rod() {
delete memorized_q;
memorized_q = NULL;
}
// 普通递归
int CutRod(int rod_len, const int prices[]) {
if (rod_len == 0)
return 0;
int q = -1;
for (int i = 1; i <= rod_len; i++) {
times1++;
q = max(q, prices[i] + CutRod(rod_len-i, prices));
}
return q;
};
// 自顶向下带备忘
int CutRodMemoized(int rod_len, const int prices[]) {
if (memorized_q[rod_len] >= 0)
return memorized_q[rod_len];
int q =INT_MIN;
if (rod_len == 0)
q = 0;
for (int i = 1; i <= rod_len; i++) {
times2++;
//q = max(q, prices[i] + CutRodMemoized(rod_len-i, prices));
if (q < prices[i] + CutRodMemoized(rod_len-i, prices)) {
q = prices[i] + CutRodMemoized(rod_len-i, prices);
s[rod_len] = i;
}
}
memorized_q[rod_len] = q;
return q;
};
// 自底向上
int BottomUpCutRod(int rod_len, const int prices[]) {
int *r = new int[rod_len+1];
int *s2 = new int [rod_len+1];
r[0] = 0; // 初始化最小的子问题
for (int i = 1; i <= rod_len; i++) {
int q = 0;
for (int j = 1; j <= i; j++) {
times3++;
if (q < prices[j] + r[i-j]) {
q = prices[j] + r[i-j];
s2[i] = j; //最终留下的是 i 子问题 下的q最大时的 j
}
}
r[i] = q;
}
cout << "BottomUpCutRod 解空间:";
int n = rod_len;
while(n > 0) {
cout << s2[n] << " "; // 打印 n 问题 下的q最大时的 j
n -= s2[n];
}
cout << endl;
return r[rod_len];
}
void printTimes() {
cout << "普通递归 CutRod 计算次数:" << times1 << endl;
cout << "CutRodMemoized 计算次数:" << times2 << endl;
cout << "BottomUpCutRod 计算次数:" << times3 << endl;
int n = rod_len;
cout << "CutRodMemoized 解空间:";
while(n > 0) {
cout << s[n] << " "; // 打印 n 问题 下的q最大时的 j
n -= s[n];
}
cout << endl;
}
};
int main()
{
const int prices[]= {0,1,5,8,9,10,17,17,20,24,30};
int rod_len = 8;
Rod rod(rod_len); //sizeof(prices)/sizeof(prices[0])
cout << rod.CutRod(rod_len, prices) << endl;
cout << rod.CutRodMemoized(rod_len, prices) << endl;
cout << rod.BottomUpCutRod(rod_len, prices) << endl;
rod.printTimes();
getchar();
return 0;
}
4.2 矩阵链相乘
#include <iostream>
using namespace std;
class Matrix {
private:
int n;
int **m;
int **s;
public:
Matrix(int len) {
n = len;
m = new int *[n+1];
s = new int *[n+1];
for (int i = 0; i < n+1; i++) {
m[i] = new int[n+1];
s[i] = new int [n+1];
}
}
//===================Recursive Memoized====================
// 算法时间复杂度O(n^3), 空间复杂度O(n^2)
int RecursiveMatrixMulti(const int * &p, int i, int j) {
if (m[i][j] != INT_MAX)
return m[i][j];
if (i == j)
m[i][j] = 0;
for(int k = i; k < j; k++) {
int q = RecursiveMatrixMulti(p, i, k) + RecursiveMatrixMulti(p, k+1, j) + p[i-1]*p[k]*p[j];
if (q < m[i][j]) {
m[i][j] = q;
s[i][j] = k;
}
}
return m[i][j];
}
int MatrixMultiMemoized(const int* p) {
for (int i = 0; i < n+1; i++)
for (int j = 0; j < n+1; j++) {
m[i][j] = INT_MAX;
s[i][j] = 0;
}
return RecursiveMatrixMulti(p, 1, n);
}
//===============Bottom Up========================
// 算法时间复杂度O(n^3), 空间复杂度O(n^2)
void MatrixMulti(const int* p) {
for(int i = 0; i < n+1; i++) {
for (int j = 0; j < n+1; j++)
{
m[i][j] = 0;
s[i][j] = 0;
}
}
for (int l = 2; l <= n; l++) { // l 是矩阵链的长度,l=2代表有2个矩阵的子问题
//得到 i
for (int i = 1; i <= n-l+1; i++) {
// 得到 j (-1 是减去 A_i)
int j = i+l-1;
m[i][j] = INT_MAX;
for (int k = i; k < j; k++) { // i <= k < j
int q = m[i][k] + m[k+1][j] + p[i-1]*p[k]*p[j];
if (q < m[i][j]) {
m[i][j] = q;
s[i][j] = k;
}
}
}
}
}
//=================打印最后括号化的结果====================
void PrintOptimalParens(int i, int j) {
if (i == j)
cout << "A" << i;
else
{
cout << "(";
PrintOptimalParens(i, s[i][j]); // s[i][j] 即 (i, j)最优的k值
PrintOptimalParens(s[i][j]+1, j);
cout << ")";
}
}
//===============打印最优解的结构矩阵==================
void PrintS() {
cout <<endl << "打印 s 矩阵如下:" << endl;
for(int i = 0; i < n+1; i++) {
for (int j = 0; j < n+1; j++)
{
cout << s[i][j] << " ";
}
cout << endl;
}
}
~Matrix() {
for (int i = 0; i < n+1; i++) {
delete m[i];
m[i] = NULL;
delete s[i];
s[i] = NULL;
}
delete m;
delete s;
m = NULL;
s = NULL;
}
};
int main() {
const int p[] = {30,35,15,5,10,20,25};
int n = sizeof(p)/sizeof(p[0]) - 1;
cout << "原始矩阵链:";
for (int i = 1; i <= n; i++)
cout << "A" << i;
cout << endl;
cout << "===============================" << endl;
Matrix matrix(n);
cout << "Bottom Up:" << endl;
matrix.MatrixMulti(p);
matrix.PrintOptimalParens(1, n);
matrix.PrintS();
cout << "===============================" << endl;
cout << "Recursive Memoized: " << endl;
matrix.MatrixMultiMemoized(p);
matrix.PrintOptimalParens(1, n);
matrix.PrintS();
getchar();
return 0;
}
4.3 最长公共子序列
#include <iostream>
#include <vector>
#include <string>
using namespace std;
template<class T>
class Sequence {
private:
int m;
int n;
int **c;
void MatrixInit(int m, int n) {
c = new int *[m];
for (int i = 0; i <m+1; i++) {
c[i] = new int [n+1];
}
for (int i = 0; i <= m; i++) {
for (int j =0; j <= n; j++) {
c[i][j] = 0;
}
}
}
public:
// 时间复杂度 O(mn), 空间复杂度 O(mn)
void LCS(const T&X, const T &Y) {
m = X.size();
n = Y.size();
MatrixInit(m, n);
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
if (X[i-1] == Y[j-1]) { // vector 从 0 开始
c[i][j] = c[i-1][j-1] + 1;
} else if(c[i-1][j] > c[i][j-1]){
c[i][j] =c[i-1][j];
} else {
c[i][j] =c[i][j-1];
}
}
}
}
void PrintLCS(const T &X, int i, int j) {
if (i == 0 || j == 0)
return;
if (c[i][j] == c[i-1][j-1]+1) {
PrintLCS(X, i-1, j-1);
cout << X[i-1];
} else if (c[i][j] == c[i-1][j]) {
PrintLCS(X, i-1, j);
} else
PrintLCS(X, i, j-1);
}
};
int main()
{
const string X_str = "ABCBDAB";
const string Y_str = "BDCABA";
vector<char> X(X_str.begin(), X_str.begin()+X_str.size());
vector<char> Y(Y_str.begin(), Y_str.begin()+Y_str.size());
Sequence<vector<char>> seq;
seq.LCS(X, Y);
cout << endl << "Thle LCS of <" << X_str << "> && <" << Y_str << "> is :" << endl;
seq.PrintLCS(X, X.size(), Y.size());
int A[] = {1,0,0,1,0,1,0,1};
int B[] = {0,1,0,1,1,0,1,1,0};
vector<int> A_(A, A+sizeof(A)/sizeof(A[0]));
vector<int> B_(B, B+sizeof(B)/sizeof(B[0]));
Sequence<vector<int>> seq_int;
seq_int.LCS(A_, B_);
cout << endl << "Thle LCS of <A> && <B> is :" << endl;
seq_int.PrintLCS(A_, A_.size(), B_.size());
getchar();
}
4.4 最优二叉搜索树
#include <iostream>
using namespace std;
class BSTree {
private:
float** e;
float** w;
int** root;
void MatInit(int n) {
e = new float *[n+2]; // + 的是两头
w = new float *[n+2];
root = new int *[n+2];
for (int i = 0; i < n+2; i++) {
e[i] = new float[n+2];
w[i] = new float[n+2];
root[i] = new int[n+2];
}
for (int i = 0; i < n+2; i++)
for (int j = 0; j < n+2; j++)
root[i][j] = 0;
}
public:
// 时间复杂度 O(n^3) 空间复杂度 O(n^2)
void OptimalBST(const float* p, const float* q, int n) {
MatInit(n);
for (int i = 1; i <= n+1; i++) {
e[i][i-1] = q[i-1];
w[i][i-1] = q[i-1];
}
for (int l = 1; l <= n; l++) {
for (int i = 1; i <= n-l+1; i++) {
int j = i+l-1;
w[i][j] = w[i][j-1] + p[j] + q[j];
e[i][j] =INT_MAX;
for (int r = i; r <= j; r++) {
float t = e[i][r-1] + e[r+1][j] + w[i][j];
if (t < e[i][j]) {
e[i][j] = t;
root[i][j] = r;
}
}
}
}
}
void PrintRoot(int n) {
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
if (root[i][j] == 0)
cout << " ";
else
cout << root[i][j] << " ";
}
cout << endl;
}
}
void PrintOptimalBST(int n) {
int r = root[1][n];
cout << "k" << r << " is root." << endl;
PrintOptimalSubTree(1, r-1, r,"left child");
PrintOptimalSubTree(r+1, n, r, "right child");
}
private:
void PrintOptimalSubTree(int i, int j, int r, const char* dir) {
if (i <= j) {
int t = root[i][j];
cout << "k" << t << " is k" << r << "‘s " << dir << endl;
PrintOptimalSubTree(i, t-1, t, "left child");
PrintOptimalSubTree(t+1, j, t, "right child");
}
return;
}
};
int main() {
const float p[] = {0, 0.15, 0.10, 0.05, 0.10, 0.20};
const float q[] = {0.05, 0.10, 0.05, 0.05, 0.05, 0.10};
BSTree bst;
int n = sizeof(p)/sizeof(p[0]) - 1;
bst.OptimalBST(p, q, n);
bst.PrintRoot(n);
bst.PrintOptimalBST(n);
getchar();
}
5. 代码下载
http://download.csdn.net/detail/quzhongxin/8827577
参考资料: 《算法导论》