算法导论--动态规划

自我感觉对于动态规划还比较不熟悉,所以专门看了《算法导论》里面第15章,动态规划这一章。对其中的钢条切割、矩阵链乘法、最长公共子序列和最优搜索二叉树这些问题有一定了解。
动态规划是一种解决最优化问题的方法,而不是一种算法。通常需要解决的这些问题有很多解,这些解都有一个值,找出值最大(最小)的解,通常动态规划可以找出最优的值,以及其中一个最优解,无法找出所有的最优解。
动态规划方法求解问题的四个步奏分别是:
1、刻画一个最优解的结构特征
2、递归的定义最优解的值
3、计算最优解的值,(由于代码中的递归操作堆栈开销大所以)一般采用从底层向上的方法,即先求解最小规模问题的最优解,保存这些过程值(避免重复计算已经算过的问题),逐渐求出原问题的最优解的值。
4、构造出最优解(第三步只是先求出最优值,第三步需要记录最优解,提供给第4步求出解)。

下面以一个矩阵链乘法问题为例子
矩阵链是一串可以相乘的矩阵,这个问题是要找出乘法次数最少的相乘组合,即安排矩阵相乘的先后顺序。
假设矩阵链为

AiAi+1...Aj

假设有个两个相容矩阵行列分别是i*j,j*k,那么乘法次数是i*j*k;

第一步 考虑最优解的结构(要先搞清楚最终解是一个什么样的形式)
可以想到,这个问题是要找出矩阵相乘的组合,比如在这串标号为i到j的矩阵链中,假设中间有个标号k,标号k之前的先计算,k之后的后计算,然后两部分合起来 这么计算乘法次数最少。其实就是加括号的形式,可能括号有很多个。脑子中先有这么一个概念,再进入下一步。
第二步 递归的定义最优解的值。
使用动态规划求解的问题都是可以递归的分割成小问题的,所以要定义最优解的值。如何去定义出这个值呢?这个定义需要包含不止是解的值这么一个信息,而且需要包含是什么问题的解这个信息,所以可以使用一个二维数组来定义最优解的值,m[i][j] = n表示的是矩阵链中标号从i到j需要的乘法次数是n,同时为了第四步能够构造出解,使用另一个二维数组s[i][j] = k表示矩阵链中标号从i到j需要断开(就是加括号)的地方是标号k。当然还要考虑一些边界情况,
比如m[i][j] =
i==j 即只有一个矩阵,那乘法次数为0
i > j 不可能存在这种情况
i < j s[i][j] = k 此时可以递归下去,根据断点k分割成两个子问题,当然最后需要加上k 两边矩阵相乘的乘法次数
所以可以列出一个公式

m[i][j]=0i=j

m[i][j]=min{m[i][k]+m[k+1][j]+pi1pkpj}(i<=k<j)i<j

如此,列出了公式(有的没有公式),而且公式是递归的,算是定义了最优解的值。

第三步计算最优解的值
这一步就涉及到代码如何去实现了,公式是递归的方式定义的,从原始规模开始,不断的变小,这是一种自顶向下的方式,不过考虑到递归次数多的话堆栈开销比较大,所以采取自底向上的方式。
从长度为2(长度为1没有意义)的矩阵链开始考虑,直到整个长度。
需要考虑所有的情况,所以算法中有三层for嵌套。
第一层是矩阵链的长度,
第二层是矩阵链的始末,
第三层是矩阵链的断点,考虑从矩阵链开始,到最后一个的前一个作为断点的情况

使用数组p来表示矩阵链中每个矩阵的行列情况p[i-1]和p[i]分别表示A_i的行和列
矩阵的标号从1开始,矩阵链中矩阵的数量就是数组p元素的个数-1。
下面先写出算法的伪代码

n=p.length-1
let m[1..n,1..n] s[1..n,1..n]be a new tables
for i=1 to n
     m[i,i] = 0
for len= 2 to n//长度从2开始
     for i = 1 to n-len+1//子序列起点
          j = i+len-1;//计算子序列终点
          m[i,j] = +max;//初始化为一个极大值
          for(k = i to j-1)//考虑每一个都作为断点
               t = m[i,k]+m[k+1,j]+p[i-1]*p[k]*p[j]
               if(m[i,j] < t)//有更小的就更新掉,就是传说中的 剪切
                    m[i,j] = t;
                    s[i,j] = k;
return m[1,n] s 

第四步 构造最优解
这一步根据第三步算法中的二维数组s来构造解,这里往往可以采用递归的方式。
找出断点之后可以在断点的两边递归的找下去。输出的结果是矩阵链加括号的形式。
伪代码如下(还是比较绕的)

PRINT(s,i,j)
    if i == j//分不下去了,输出这个矩阵
        print "A"i
    else
        print "("//最开始肯定是左括号
        k = s[i,j]
        PRINT(s,i,k)
        PRINT(s,k+1,j)
        print ")"//结束肯定是右括号

完整代码如下

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

typedef unsigned int uint;
//整形有无符号很重要
const uint max_n = 0xffffffff;
const uint max_row_col = 100;//定义最大的矩阵为100X100

/*
求矩阵链相乘最少的乘法次数
输入:
A 矩阵序列
s 存放最优解的路径
start end 所求矩阵链的 起点到终点 规定矩阵下标从1开始
*/
int matrix_chain_multiply(vector<uint>& A, uint(*s)[max_row_col], uint start, uint end)
{
    if (start >= end)
        return 0;
    uint m[max_row_col][max_row_col];//存放最少的值
    uint n = end-start+1;//矩阵链长度

    for (uint i = start; i <= end; i++)//,初始化
        m[i][i] = 0;

    for (uint len = 2; len <= n; len++){//子序列的长度,从2开始,一直到整个序列
        for (uint i = start; i <= n - len + start; i++){//子序列的起点,注意终点是相对于start的
            uint j = i + len - 1;//子序列的终点(根据子序列的长度确定终点)
            m[i][j] = max_n;
            for (uint k = i; k < j; k++){//k值在起点和终点之间,不包括终点
                uint q = m[i][k] + m[k + 1][j] + (A[i - 1])*(A[k])*(A[j]);
                if (m[i][j] > q){
                    m[i][j] = q;
                    s[i][j] = k;
                }
            }
        }
    }
    return m[start][end];
}

void print(uint(*s)[max_row_col],uint i, uint j)
{
    if (i == j)
        cout << 'A' << i;
    else{
        uint k = s[i][j];
        cout << "(";
        print(s, i, k);
        print(s, k+1,j);
        cout << ")";
    }
}

int main()
{
    vector<uint> A = { 30, 35, 15, 5, 10, 20, 25 };//矩阵序列,分别表示30X35,35X15, ...20X25的矩阵相乘
    uint s[max_row_col][max_row_col];//存放i到j中,左括号为i的左侧,右括号为此值得右侧
    int n = A.size() - 1;//矩阵链的总矩阵个数
    cout << "1:6   " << matrix_chain_multiply(A, s, 1, n) << endl;
    print(s, 1, n);
    return 0;

}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值