动态规划算法及代码

动态规划 

算法总体思想 

动态规划算法与分治法类似,其基本思想也是将待求解问题分解成若干个子问题。

但是经分解得到的子问题往往不是互相独立的。不同子问题的数目常常只有多项式量级。在用分治法求解时,有些子问题被重复计算了许多次。

如果能够保存已解决的子问题的答案,而在需要时再找出已求得的答案,就可以避免大量重复计算,从而得到多项式时间算法。

动态规划基本步骤:

(1)找出最优解的性质,并刻划其结构特征。

(2)递归地定义最优值。

(3)以自底向上的方式计算出最优值。

(4)根据计算最优值时得到的信息,构造最优解。

实例一、完全加括号的矩阵连乘积 

问题可递归定义: 

(1)单个矩阵是完全加括号的;

(2)矩阵连乘积A是完全加括号的 ,则A可表示为2个完全加括号的矩阵连乘积B和C的乘积并加括号,即 A = (BC)。

设有四个矩阵A,B,C,D它们的维数分别是: A = 50*10 , B = 10*40 , C = 40*30 , D = 30*5

总共有五种完全加括号的方式:

 

例如:((A(BC))D): 10 * 40 * 30 + 10 * 30 * 50 + 50 * 30 * 5 = 34500

    给定矩阵{A1, A2, A3,..., An},其中Ai与A(i+1)是可乘的。i = 1,2,3, ..., n - 1。考察这n个矩阵的连乘积A1*A2*A3...An.

由于矩阵乘法满足结合律,所以计算矩阵的连乘可以有许多不同的计算次序。这种计算次序可以用加括号的方式来确定。

若一个矩阵连乘积的计算次序完全确定,也就是说该连乘积已完全加括号,则可以依此次序反复调用2个矩阵相乘的标准算法计算出矩阵连乘积。

矩阵连乘问题 

    给定矩阵{A1, A2, A3,..., An},其中Ai与A(i+1)是可乘的。i = 1,2,3, ..., n - 1。考察这n个矩阵的连乘积A1*A2*A3...An. 如何确定计算矩阵连乘积的计算次序,使得依此次序计算矩阵连乘积需要的数乘次数最少。

穷举法:列举出所有可能的计算次序,并计算出每一种计算次序相应需要的数乘次数,从中找出一种数乘次数最少的计算次序。

算法复杂度分析:

对于n个矩阵的连乘积,设其不同的计算次序为P(n)

由于每种加括号方式都可以分解为两个子矩阵的加括号问题

(A1...Ak)(A(k+1)…An)可以得到关于P(n)的递推式如下:

动态规划:将矩阵连乘积A(i)A(i+1)…A(j)简记为A[i:j],这里 i <= j。

考察计算A[i:j]的最优计算次序。设这个计算次序在矩阵A(k)和A(k+1)之间将矩阵链断开,i <= k < j, 则其相应完全加括号方式为(A(i)A(i+1)...A(k)) * (A(k+1)A(k+2)...A(j))。

计算量:A[i:k]的计算量加上A[k+1,j],再加上A[i:k] * A[k+1][j]的计算量。

分析最优解的结构 

特征:计算A[i:j]的最优次序所包含的计算矩阵子链 A[i:k]和A[k+1:j]的次序也是最优的。

矩阵连乘计算次序问题的最优解包含着其子问题的最优解。这种性质称为最优子结构性质。问题的最优子结构性质是该问题可用动态规划算法求解的显著特征。

建立递归关系 

设计算A[i:j],1 <= i <= j <= n,所需要的最少数乘次数m[i,j],则原问题的最优值为m[1,n]

当i = j时,A[i:j]=Ai,因此,m[i,i] = 0,i = 1,2,…,n

当i < j时,m[i,j] = m[i,k] + m[k+1,j] + p(i-1)p(k)p(j)

这里A(i)的维数为p(i-1)*p(i)(注:p(i-1)为矩阵A[i]的行数,p(i)为矩阵A[i]的列数)

可以递归地定义m[i,j]为:

k的位置只有j - i种。

计算最优值 

对于1 <= i <= j <= n不同的有序对(i,j)对应于不同的子问题。因此,不同子问题的个数最多只有:

(大括号表示C(n,2),组合的意思。后面的符号表示 “紧渐近界记号”)

但是,在递归计算时,许多子问题被重复计算多次。这也是该问题可用动态规划算法求解的又一显著特征。

用动态规划算法解此问题,可依据其递归式以自底向上的方式进行计算。在计算过程中,保存已解决的子问题答案。每个子问题只计算一次,而在后面需要时只要简单查一下,从而避免大量的重复计算,最终得到多项式时间的算法。

用动态规划法求最优解 

连乘矩阵假如为:

计算过程为:

从m可知最小连乘次数为m[1][6] = 15125

从s可知计算顺序为((A1(A2A3))((A4A5))A6))

实现:

/* 主题:矩阵连乘问题
* 作者:chinazhangjie
* 邮箱:chinajiezhang@gmail.com
* 开发语言:C++
* 开发环境:Mircosoft Virsual Studio 2008
* 时间: 2010.11.14
*/

#include <iostream>
#include <vector>
using namespace std ;

class matrix_chain
{
public:
    matrix_chain(const vector<int> & c) {
        cols = c ;
        count = cols.size () ;
        mc.resize (count) ;
        s.resize (count) ;
        for (int i = 0; i < count; ++ i) {
            mc[i].resize (count) ;
            s[i].resize (count) ;
        }
        for (int i = 0; i < count; ++ i) {
            for (int j = 0; j < count; ++ j) {
                mc[i][j] = 0 ;
                s[i][j] = 0 ;
            }
        }
    }

    // 使用备忘录方法计算
    void lookup_chain () {
        __lookup_chain (1, count - 1) ;
        min_count = mc[1][count - 1] ;
        cout << "min_multi_count = "<< min_count << endl ;
        // 输出最优计算次序
        __trackback (1, count - 1) ;
    }

    // 使用普通方法进行计算
    void calculate () {
        int n = count - 1; // 矩阵的个数
        // r 表示每次宽度
        // i,j表示从从矩阵i到矩阵j
        // k 表示切割位置
        for (int r = 2; r <= n; ++ r) {
            for (int i = 1; i <= n - r + 1; ++ i) {
                int j = i + r - 1 ;
                // 从矩阵i到矩阵j连乘,从i的位置切割,前半部分为0
                mc[i][j] = mc[i+1][j] + cols[i-1] * cols[i] * cols[j] ;
                s[i][j] = i ;
                for (int k = i + 1; k < j; ++ k) {
                    int temp = mc[i][k] + mc[k + 1][j] + 
                        cols[i-1] * cols[k] * cols[j] ;
                    if (temp < mc[i][j]) {
                        mc[i][j] = temp ;
                        s[i][j] = k ;
                    }
                } // for k
            } // for i
        } // for r
        min_count = mc[1][n] ;
        cout << "min_multi_count = "<< min_count << endl ;
        // 输出最优计算次序
        __trackback (1, n) ;

    }

private:
    int __lookup_chain (int i, int j) {
        // 该最优解已求出,直接返回
        if (mc[i][j] > 0) {
            return mc[i][j] ;
        }
        if (i == j) {
            return 0 ;    // 不需要计算,直接返回
        }

        // 下面两行计算从i到j按照顺序计算的情况
        int u = __lookup_chain (i, i) + __lookup_chain (i + 1, j) 
            + cols[i-1] * cols[i] * cols[j] ;
        s[i][j] = i ;
        for (int k = i + 1; k < j; ++ k) {
            int temp = __lookup_chain(i, k) + __lookup_chain(k + 1, j) 
                + cols[i - 1] * cols[k] * cols[j] ;
            if (temp < u) {
                u = temp ;
                s[i][j] = k ;
            }
        }
        mc[i][j] = u ;
        return u ;
    } 

    void __trackback (int i, int j) {
        if (i == j) { 
            return ; 
        }
        __trackback (i, s[i][j]) ;
        __trackback (s[i][j] + 1, j) ;
        cout <<i << "," << s[i][j] << " " << s[i][j] + 1 << "," << j << endl; 
    }

private:
    vector<int>    cols ;    // 列数
    int            count ;    // 矩阵个数  + 1
    vector<vector<int> >    mc;    // 从第i个矩阵乘到第j个矩阵最小数乘次数
    vector<vector<int> >    s;    // 最小数乘的切分位置
    int            min_count ;        // 最小数乘次数
} ;

int main() 
{
    // 初始化
    const int MATRIX_COUNT = 6 ;
    vector<int>    c(MATRIX_COUNT + 1) ;
    c[0] = 30 ;
    c[1] = 35 ;
    c[2] = 15 ;
    c[3] = 5 ;
    c[4] = 10 ;
    c[5] = 20 ;
    c[6] = 25 ;

    matrix_chain mc (c) ;
    // mc.calculate () ;
    mc.lookup_chain () ;
    return 0 ;
}

算法复杂度分析: 

算法matrixChain的主要计算量取决于算法中对r,i和k的3重循环。循环体内的计算量为O(1),而3重循环的总次数为O(n^3)。因此算法的计算时间上界为O(n^3)。算法所占用的空间显然为O(n^2)。

动态规划算法的基本要素 

一、最优子结构 

矩阵连乘计算次序问题的最优解包含着其子问题的最优解。这种性质称为最优子结构性质。

在分析问题的最优子结构性质时,所用的方法具有普遍性:首先假设由问题的最优解导出的子问题的解不是最优的,然后再设法说明在这个假设下可构造出比原问题最优解更好的解,从而导致矛盾。

利用问题的最优子结构性质,以自底向上的方式递归地从子问题的最优解逐步构造出整个问题的最优解。最优子结构是问题能用动态规划算法求解的前提。

同一个问题可以有多种方式刻划它的最优子结构,有些表示方法的求解速度更快(空间占用小,问题的维度低)

二、重叠子问题

 递归算法求解问题时,每次产生的子问题并不总是新问题,有些子问题被反复计算多次。这种性质称为子问题的重叠性质

 动态规划算法,对每一个子问题只解一次,而后将其解保存在一个表格中,当再次需要解此子问题时,只是简单地用常数时间查看一下结果。

通常不同的子问题个数随问题的大小呈多项式增长。因此用动态规划算法只需要多项式时间,从而获得较高的解题效率。

三、备忘录方法

备忘录方法的控制结构与直接递归方法的控制结构相同,区别在于备忘录方法为每个解过的子问题建立了备忘录以备需要时查看,避免了相同子问题的重复求解。

实现(见矩阵连乘源码)

实例二、最长公共子序列 

    若给定的序列X = {x1,x2,…,xm},则另一序列Z = {z1,z2,…,zk},是X的子序列是指存在一个严格下表序列{i1,i2,…,ik}使得对于所有的j = 1,2,…k有zj = xij。例如,序列Z = {B,C,D,B}是序列X = {A,B,C,B,D,A,B}的子序列,相应的递增下标序列为{2,3,5,7}。

给定2个序列X和Y,当另一序列Z既是X的子序列又是Y的子序列时,称Z是序列X和Y的公共子序列。

问题表述:给定2个序列X={x1,x2,…,xm}和Y = {y1,y2,…,yn},找出X和Y的最长公共子序列。

最长公共子序列的结构 

设序列X = {x1,x2,…,xm}和Y = {y1,y2,…,yn}的最长公共子序列为Z = {z1,z2,…,zk} ,则

(1)若xm = yn,则zk = xm = yn,且z(k-1)是x(m-1)和y(n-1)的最长公共子序列。

(2)若xm != yn且zk != xm,则Z是x(m-1)和Y的最长公共子序列。

(3)若xm != yn且zk != yn,则Z是X和y(n-1)的最长公共子序列。

由此可见,2个序列的最长公共子序列包含了这2个序列的前缀的最长公共子序列。因此,最长公共子序列问题具有最优子结构性质。

子问题的递归结构 

由最长公共子序列问题的最优子结构性质建立子问题最优值的递归关系。用c[i][j]记录序列Xi和Yi的最长公共子序列的长度。其中, Xi={x1,x2,…,xi};Yj={y1,y2,…,yj}。当i = 0或j = 0时,空序列是Xi和Yj的最长公共子序列。故此时C[i][j] = 0。其它情况下,由最优子结构性质可建立递归关系如下:

由于在所考虑的子问题空间中,总共有θ(mn)个不同的子问题,因此,用动态规划算法自底向上地计算最优值能提高算法的效率

计算最优值和构造最长公共子序列(见源码) 

实现: 

/* 主题:最长公共子序列
* 作者:chinazhangjie
* 邮箱:chinajiezhang@gmail.com
* 开发语言:C++
* 开发环境:Microsoft Visual Studio 2008
* 时间: 2010.11.14
*/
#include <iostream>
#include <vector>
using namespace std ;

// longest common sequence
class LonComSequence
{
public:
    typedef vector<vector<int> >    LCS_Type ;
    typedef vector<vector<int> >    MarkType ;

public:
    LonComSequence (const vector<char>& vSeq1,
                    const vector<char>& vSeq2) 
        : mc_nEqual (1), mc_nSq1move(2), mc_nSq2move(3)
    {
        m_vSeq1 = vSeq1 ;
        m_vSeq2 = vSeq2 ;
        m_nLen1 = vSeq1.size() ;
        m_nLen2 = vSeq2.size() ;

        // 初始化最长公共子序列的长度
        m_lcsLen.resize (m_nLen1 + 1) ;
        m_mtMark.resize (m_nLen1 + 1) ;
        for (int i = 0; i < m_nLen1 + 1; ++ i) {
            m_lcsLen[i].resize (m_nLen2 + 1) ;
            m_mtMark[i].resize (m_nLen2 + 1) ;
        }
    }

    // 计算最长公共子序列的长度
    int calLcsLength ()    
    {
        for (int i = 1; i <= m_nLen1; ++ i) {
            m_lcsLen[i][0] = 0 ; // 序列二的长度为0,公共子序列的长度为0
        } 
        for (int i = 1; i <= m_nLen2; ++ i) {
            m_lcsLen[0][i] = 0 ; // 序列一的长度为0,公共子序列的长度为0
        }
    
        for (int i = 0; i < m_nLen1; ++ i) {
            for (int j = 0; j < m_nLen2; ++ j) {
                if (m_vSeq1[i] == m_vSeq2[j]) {
                    m_lcsLen[i+1][j+1] = m_lcsLen[i][j] + 1 ;
                    m_mtMark[i+1][j+1] = mc_nEqual ;
                }
                else if (m_lcsLen[i][j+1] >= m_lcsLen[i+1][j]) {
                    m_lcsLen[i+1][j+1] = m_lcsLen[i][j+1] ;
                    m_mtMark[i+1][j+1] = mc_nSq1move ;
                }
                else {
                    m_lcsLen[i+1][j+1] = m_lcsLen[i+1][j] ;
                    m_mtMark[i+1][j+1] = mc_nSq2move ;
                }
            }
        }
        return m_lcsLen[m_nLen1][m_nLen2] ;
    }
    // 构造最长公共子序列
    void LCS() {
        cout << "LCS is : " ;
        __LCS(m_nLen1, m_nLen2);
        cout << endl ;
    }

private:
    void __LCS (int i, int j) 
    {
        if (i == 0 || j == 0) {
            return ;
        }

        if (m_mtMark[i][j] == mc_nEqual) {
            __LCS (i - 1, j - 1) ;
            cout << m_vSeq1[i - 1] << " " ;
        }
        else if (m_mtMark[i][j] == mc_nSq1move) {
            __LCS (i - 1, j) ;
        }
        else {
            __LCS (i, j - 1) ;
        }
    }

private:
    vector<char>    m_vSeq1 ;    // 序列一
    vector<char>    m_vSeq2 ;    // 序列二
    int                m_nLen1 ;    // 序列一的长度
    int                m_nLen2 ;    // 序列二的长度
    LCS_Type        m_lcsLen ;    // 最长公共子序列的长度
    MarkType        m_mtMark ;    // 记录m_lcsLen
    const int mc_nEqual ;        // 相等的标志
    const int mc_nSq1move ;        // 序列一左移的标志
    const int mc_nSq2move ;        // 序列二左移的标志
} ;


int main()
{
    vector<char> s1 ; 
    s1.push_back ('A') ;
    s1.push_back ('B') ;
    s1.push_back ('C') ;
    s1.push_back ('D') ;
    s1.push_back ('E') ;
    s1.push_back ('F') ;

    vector<char> s2 ;
    s2.push_back ('B') ;
    s2.push_back ('D') ;
    s2.push_back ('F') ;
    s2.push_back ('G') ;
    s2.push_back ('H') ;

    LonComSequence lcs(s1, s2) ;
    cout << lcs.calLcsLength () << endl ;
    lcs.LCS();

    return 0 ;
}

算法的改进

在算法lcsLength和lcs中,可进一步将数组b省去。事实上,数组元素c[i][j]的值仅由c[i-1][j-1],c[i-1][j]和c[i][j-1]这3个数组元素的值所确定。对于给定的数组元素c[i][j],可以不借助于数组b而仅借助于c本身在时间内确定c[i][j]的值是由c[i-1][j-1],c[i-1][j]和c[i][j-1]中哪一个值所确定的。

如果只需要计算最长公共子序列的长度,则算法的空间需求可大大减少。事实上,在计算c[i][j]时,只用到数组c的第i行和第i-1行。因此,用2行的数组空间就可以计算出最长公共子序列的长度。进一步的分析还可将空间需求减至O(min(m,n))。

实例三、最大子段和 

问题表述 

n个数(可能是负数)组成的序列a1,a2,…an.求该序列

例如:  序列(-2,11,-4,13,-5,-2) ,最大子段和:

       11 - 4 + 13=20。

(1)穷举算法: O(n3), O(n2)

(2)分治法:

将序列a[1:n]从n/2处截成两段:a[1:n/2], a[n/2+1:n]

实例三、最大子段和

问题表述

n个数(可能是负数)组成的序列a1,a2,…an.求该序列 子序列的最大值。

也就是

 

例如:  序列(-2,11,-4,13,-5,-2) ,最大子段和:

     11 - 4 + 13=20。

(1)穷举算法: O(n3), O(n2)

(2)分治法:

将序列a[1:n]从n/2处截成两段:a[1:n/2], a[n/2+1:n]

一共存在三种情况:

a.最大子段和出现在左边一段

b.最大子段和出现在右边一段

c.最大子段和跨越中间的断点

对于前两种情况,只需继续递归调用,而对于第三种情况:

那么S1+S2是第三种情况的最优值。

(3)动态规划法:

定义b[j]:

含义:从元素i开始,到元素j为止的所有的元素构成的子段有多个,这些子段中的子段和最大的那个。

那么:

如果:b[j-1] > 0, 那么b[j] = b[j-1] + a[j]

如果:b[j-1] <= 0,那么b[j] = a[j]

这样,显然,我们要求的最大子段和,是b[j]数组中最大的那个元素。

实现:

/* 主题:最大子段和
* 作者:chinazhangjie
* 邮箱:chinajiezhang@gmail.com
* 开发语言:C++
* 开发环境:Microsoft Virsual Studio 2008
* 时间: 2010.11.15
*/

#include <iostream>
#include <vector>
using namespace std ;

class MaxSubSum 
{
public:
    MaxSubSum (const vector<int>& intArr) 
    {
        m_vIntArr = intArr ;
        m_nLen = m_vIntArr.size () ;
    }

    // use divide and conquer 
    int use_DAC () 
    {
        m_nMssValue = __use_DAC (0, m_nLen - 1) ;
        return m_nMssValue ;
    }

    // use dynamic programming
    int use_DP () 
    {
        int sum = 0 ;
        int temp = 0 ;

        for (int i = 0; i < m_nLen; ++ i) {
            if (temp > 0) {
                temp += m_vIntArr[i] ;
            }
            else {
                temp = m_vIntArr[i] ;
            }
            if (temp > sum) {
                sum = temp ;
            }
        }
        m_nMssValue = sum ;
        return sum ;
    }

private:
    int __use_DAC (int left, int right) 
    {
        // cout << left << "," << right << endl ;
        if (left == right) {
            return (m_vIntArr[left] > 0 ? m_vIntArr[left] : 0) ;
        }

        // 左边区域的最大子段和
        int leftSum = __use_DAC (left, (left + right) / 2) ;
        // 右边区域的最大子段和
        int rightSum = __use_DAC ((left + right) / 2 + 1, right) ;
        // 中间区域的最大子段和
        int sum1 = 0 ;
        int max1 = 0 ;
        int sum2 = 0 ;
        int max2 = 0 ;
        for (int i = (left + right) / 2; i >= left; -- i) {
            sum1 += m_vIntArr[i] ;
            if (sum1 > max1) {
                max1 = sum1 ;
            }
        }
        for (int i = (left + right) / 2 + 1; i <= right; ++ i) {
            sum2 += m_vIntArr[i] ;
            if (sum2 > max2) {
                max2 = sum2 ;
            }
        }
        int max0 = max1 + max2 ;
        max0 = (max0 > 0 ? max0 : 0) ;
        // cout << max0 << ", " << leftSum << ", " << rightSum << endl ;
        return max (max0 , max (leftSum, rightSum)) ;
    }

private:
    vector<int>    m_vIntArr ;    // 整形序列
    int            m_nLen ;    // 序列长度
    int            m_nMssValue;// 最大子段和
} ;

int main()
{
    vector<int> vArr ;
    vArr.push_back (-2) ;
    vArr.push_back (11) ;
    vArr.push_back (-4) ;
    vArr.push_back (13) ;
    vArr.push_back (-5) ;
    vArr.push_back (-2) ;

    MaxSubSum mss (vArr) ;
    cout << mss.use_DP () << endl ;
    return 0 ;
}

实例四、多边形游戏

多边形游戏是一个单人玩的游戏,开始时有一个由n个顶点构成的多边形。每个顶点被赋予一个整数值,每条边被赋予一个运算符”+”或”*”。所有边依次用整数从1到n编号。

游戏第1步,将一条边删除。

随后n-1步按以下方式操作:

(1)选择一条边E以及由E连接着的2个顶点V1和V2;

(2)用一个新的顶点取代边E以及由E连接着的2个顶点V1和V2。将由顶点V1和V2的整数值通过边E上的运算得到的结果赋予新顶点。

最后,所有边都被删除,游戏结束。游戏的得分就是所剩顶点上的整数值。

问题: 对于给定的多边形,计算最高得分。

最优子结构性质

按照顺时针顺序,多边形和顶点的顺序可以写成:

    op[1], v[1], op[2], v[2], …, op[n], v[n]

在所给多边形中,从顶点i(1 <= i <= n)开始,长度为j(链中有j个顶点)的顺时针链p(i,j) 可表示为

v[i], op[i+1], v[i+1],…, op[i+j-1], v[i+j-1]

如果这条链在op[i + s]处进行最后一次合并运算(1 <= s <= j-1),则可在op[i+s]处将链分割为2个子链:

从i开始长度为s的链:  p(i,s)

从i + s开始,长度为j - s的链:p(i + s,j-s)。

设:

m1是对子链p(i,s)的任意一种合并方式得到的值,而a和b分别是在所有可能的合并中得到的最小值和最大值。

m2是p(i+s,j-s)的任意一种合并方式得到的值,而c和d分别是在所有可能的合并中得到的最小值和最大值。

依此定义有a <= m1 <= b,c <= m2 <= d

(1)当op[i+s] = ‘+’时,显然有a + c <= m <= b + d

(2)当op[i+s] = ’*’时,有

min {ac,ad,bc,bd} <= m <= max {ac,ad,bc,bd}

换句话说,主链的最大值和最小值可由子链的最大值和最小值得到。

实现:

/* 主题:多边形游戏
* 作者:chinazhangjie
* 邮箱:chinajiezhang@gmail.com
* 开发语言:C++
* 开发环境:Vicrosoft Visual Studio
* 时间: 2010.11.15
*/

#include <iostream>
#include <vector>
using namespace std ;

struct SegInfo
{
public:
    SegInfo () 
        : m_nMaxValue (0), m_nMinValue(0) 
    {}
    SegInfo (int maxValue, int minValue) 
        : m_nMaxValue (maxValue), m_nMinValue (minValue) 
    {}
public:
    int m_nMaxValue ;
    int m_nMinValue ;
} ;

class PolyGame 
{
public:
    PolyGame (const vector<char>& op, const vector<int>& vertex) 
    {
        m_vcOp = op ;
        m_vnVertex = vertex ;
        m_nCount = m_vcOp.size () ;

        m_vSeg.resize (m_nCount) ;
        for (int i = 0; i < m_nCount; ++ i) {
            m_vSeg[i].resize (m_nCount) ;
        }
    }
    
    int beginCalulate () 
    {
        // 初始边界
        for (int i = 1; i < m_nCount; ++ i) {
            m_vSeg[i][1].m_nMaxValue = m_vnVertex[i] ;
            m_vSeg[i][1].m_nMinValue = m_vnVertex[i] ;
        }

        // i: 起点
        // j: 长度
        // s: 子切分位置
        for (int j = 2; j < m_nCount ; ++ j) {
            for (int i = 1; i < m_nCount; ++ i) {
                for (int s = 1; s < j; ++ s) {
                    SegInfo si = __calMinAndMax(i, s, j) ;
                    if (m_vSeg[i][j].m_nMinValue > si.m_nMinValue) {
                        m_vSeg[i][j].m_nMinValue = si.m_nMinValue ;
                    } 
                    if (m_vSeg[i][j].m_nMaxValue < si.m_nMaxValue) {
                        m_vSeg[i][j].m_nMaxValue = si.m_nMaxValue ;
                    }
                }
            }
        }
        // 找到最大值
        int temp = m_vSeg[1][m_nCount - 1].m_nMaxValue ;
        for (int i = 2; i < m_nCount; ++ i) {
            if (temp < m_vSeg[i][m_nCount - 1].m_nMaxValue) {
                temp = m_vSeg[i][m_nCount - 1].m_nMaxValue ;
            }
        }
        m_nResult = temp ;
        return m_nResult ;
    }

private:
    // 从i开始,长度为j,s为切分位置
    SegInfo __calMinAndMax (int i, int s, int j) 
    {
        int minL = 0 ;
        int maxL = 0 ;
        int minR = 0 ;
        int maxR = 0 ;
        minL = m_vSeg[i][s].m_nMinValue ;
        maxL = m_vSeg[i][s].m_nMaxValue ;
        int r = (i + s - 1) % (m_nCount - 1) + 1 ;
        minR = m_vSeg[r][j - s].m_nMinValue ;
        maxR = m_vSeg[r][j - s].m_nMaxValue ;

        SegInfo si ;
        // 处理加法
        if (m_vcOp[r] == '+') {
            si.m_nMinValue = minL + minR ;
            si.m_nMaxValue = maxL + maxR ;
        }
        else {    // 处理乘法
            vector<int> mm ;
            mm.push_back (minL * minR) ;
            mm.push_back (minL * maxR) ;
            mm.push_back (maxL * minR) ;
            mm.push_back (maxL * maxR) ;
            int min = 0 ;
            int max = 0 ;
            for (vector<int>::iterator ite = mm.begin(); 
                ite != mm.end() ; ++ ite) {
                if (*ite < min) {
                    min = *ite ;
                }
                if (*ite > max) {
                    max = *ite ;
                } 
            }
            si.m_nMinValue = min ;
            si.m_nMaxValue = max ;
        }
        return si ;
     }

private :
    vector<char>    m_vcOp ;    // 运算符(下标从1开始)
    vector<int>        m_vnVertex ;// 顶点值(下标从1开始)
    int                m_nCount ;    // 边的个数
    int                m_nResult ;    // 结果
    vector<vector<SegInfo> > m_vSeg ;// 合并后的信息
} ;

int main()
{
    const int cnCount = 5 ;
    vector<char> op (cnCount + 1);
    vector<int>     vertex (cnCount + 1);
    op[1] = '+' ;
    op[2] = '*' ;
    op[3] = '+' ;
    op[4] = '*' ;
    op[5] = '*' ;

    vertex[1] = 10 ;
    vertex[2] = -8 ;
    vertex[3] = 3;
    vertex[4] = -2 ;
    vertex[5] = -1 ;

    PolyGame pg (op, vertex) ;
    cout << pg.beginCalulate () << endl ;
}


  • 5
    点赞
  • 61
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
动态规划是一种解决多阶段决策问题的优化方法。它将问题分解为多个子问题,并利用递推关系来计算每个子问题的最优,最终得到整体问题的最优解。 动态规划算法通常包含以下步骤: 1. 定义状态:将问题抽象为一个状态集合,每个状态代表一个子问题的解。 2. 定义状态转移方程:找出每个状态之间的递推关系,即当前状态与前一状态之间的关系。 3. 初始化:设置初始状态的值,通常是已知的边界条件。 4. 递推计算:根据状态转移方程,从初始状态开始逐步计算出所有状态的值。 5. 最优解:根据问题的要求,从最终状态中选取最优解。 下面是一个动态规划算法的示例,用于解决背包问题: ```python def knapsack(weights, values, capacity): n = len(weights) # 初始化动态规划表 dp = [[0] * (capacity + 1) for _ in range(n + 1)] for i in range(1, n + 1): for j in range(1, capacity + 1): if weights[i-1] <= j: # 选择当前物品 choose = values[i-1] + dp[i-1][j-weights[i-1]] # 不选择当前物品 not_choose = dp[i-1][j] dp[i][j] = max(choose, not_choose) else: # 当前物品无法放入背包 dp[i][j] = dp[i-1][j] # 返回最优解 return dp[n][capacity] ``` 在这个示例中,`weights` 是物品的重量列表,`values` 是物品的价值列表,`capacity` 是背包的容量。函数 `knapsack` 返回背包可以装下的最大价值。 这个算法使用一个二维的动态规划表 `dp` 来保存每个子问题的最优解。通过填充这个表,最后可以得到背包问题的最优解。 注意,这只是一个简单的示例,实际应用中可能会有更复杂的状态定义和状态转移方程。动态规划算法的关键是找到递推关系和合适的状态定义,以及正确地填充动态规划表。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值