码迷,mamicode.com
首页 > 编程语言 > 详细

动态规划的简要总结和四个经典问题的c++实现

时间:2015-06-22 11:07:25      阅读:205      评论:0      收藏:0      [点我收藏+]

标签:动态规划

本文给出了动态规划的简要定义、适用场景、算法实现。并给出了四种经典动态规划:钢条切割求最大收益问题、矩阵链相乘求最小乘法次数问题、最长公共子序列问题、求最小的搜索代价的最优二叉搜索树的c++代码实现。

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

参考资料: 《算法导论》

动态规划的简要总结和四个经典问题的c++实现

标签:动态规划

原文地址:http://blog.csdn.net/quzhongxin/article/details/46591815

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!