动态规划

来源:互联网 发布:mp3音量调节软件 编辑:程序博客网 时间:2024/06/07 12:33

分治方法将问题划分为互不相交的子问题,递归的求解子问题,再将它们组合起来。

而动态规划应用与子问题重叠的情况(不同子问题具有公共的子子问题)来求解最优化啊问题。

设计动态规划算法的4个步骤:

1、刻画一个最优解的结构特征

2、递归定义最优解的值

3、计算最优解的值,通常采用自底向上的方法

4、利用计算出的信息构造一个最优解


最优子结构性质:问题的最优解有相关子问题的最优解组合而成,而这些子问题可以独立求解。


动态规划和分治策略相似,不同的是,它针对的问题所分解出的小问题数量很多且很多是重复的。

      动态规划就是使得这些重复的小问题只需要计算一次,避免重复计算。

 

钢条切割问题

给定一段长度为n英寸的钢条和一个价格表pi(i=1,2,...,n)求切割钢条方案,使得销售收益rn最大。注意,如果长度为n英寸的钢条的价格pn足够大,最优解可能就是完全不需要切割。

      思路:先将钢条切成两条,有n-1种方案,每一种方案的最优解都等于两个子钢条的最优解。我们从这n-1个伪最优解再挑出最优的解了

      以下是伪代码:

 CUT-ROD(p,n) if n == 0    return 0 q=负无穷 for i = 1 to n    q=max(q,p[i]+CUT-ROD(p,n-i)) return q

时间复杂度:

T(n)=1+T(0)+T(1)+...+T(n-1)

T(n)/T(n-1)=(1+T(0)+T(1)+...+T(n-1))/(1+T(0)+T(1)+...+T(n-2))=1+T(n-1)/(1+T(0)+T(1)+...+T(n-2))=2

所以上述算法时间复杂度为2的n次幂。

上面只用了分治策略,这个算法的性能是很差的T(n)=2n,在子问题的求解中很多都是重复的。


动态规划付出额外的内存空间来节省计算时间,是典型的时空权衡的例子。可能将一个指数时间的解转化为一个多项式时间的解。

自顶向下备忘录法:

 MEM-CUT-ROD let r[0..n] be a new array for i = 0 to n    r[i]=负无穷 return MEM-CUT-ROD-AUX(p,n,r) MEM-CUT-ROD-AUX(p,n,r) if r[n]>=0    return r[n] if n==0 q=0 else q=负无穷    for i=1 to n       q=max(q,p[i]+MEM-CUT-ROD-AUX(p,n-i,r)) r[n]=q  return q
自底向上法:

 BOTTOM-UP-CUT-ROD(p,n) let r[0..n] be a new array r[0]=0 for j=1 to n     q=负无穷     for i=1 to j         q=max(q,p[i]+r[j-i])     r[j]=q return r[n]
这两种方法的时间复杂度都是O(n*n).

下面的伪代码还保留了切割长度

EXTEND-BOTTOM-UP-CUT-ROD(p,n) let r[0..n] and s[0..n] be new arrays r[0]=0 for j = 1 to n     q=负无穷     for i =1 to j         if q < p[i]+r[j-i]            q=p[i]+r[j-i]            s[j]=i            r[j]=q return r and s PRINT-CUT-ROD-SOLUTION(p,n) (r,s)=EXTEND-BOTTOM-UP-CUT-ROD(p,n) while n >0    print s[n]    n=n-s[n]


使用动态规划方法求解的最优化问题应该具备两个要素:最优子结构和子问题重叠.


令h(0)=1,h(1)=1,catalan数满足递推式[1] :
h(n)= h(0)*h(n-1)+h(1)*h(n-2) + ... + h(n-1)h(0) (n>=2)
例如:h(2)=h(0)*h(1)+h(1)*h(0)=1*1+1*1=2
h(3)=h(0)*h(2)+h(1)*h(1)+h(2)*h(0)=1*2+1*1+2*1=5
另类递推式[2] :
h(n)=h(n-1)*(4*n-2)/(n+1);
递推关系的解为:
h(n)=C(2n,n)/(n+1) (n=0,1,2,...)
递推关系的另类解为:
h(n)=c(2n,n)-c(2n,n-1)(n=0,1,2,...)


矩阵链乘法

n个矩阵相乘,A1A2...An,试着找到一种相乘顺序,使得整个相乘的过程中所做的标量乘法次数最少,即相乘的代价最小。

问题分析:

  记矩阵Ai的行列分别为Pi-1和Pi,那么A1*A2所做的乘法的次数为P0*P1*P2

A1A2...An的最小相乘代价为f(1,n),Ai...Aj的最小相乘代价为f(i,j)

则 f(1,n) = min{f(1,k)+f(k+1,n)+P0*Pk*Pn} k=1...n-1

f(i,i)=0  f(i,i+1)=Pi-1*Pi*Pi+1

 

So 可以由此产生递归式

Recursive_Matrix_Multiply(p, i, j){    if(i==j)return 0;f[i][j] = max_int;for(k=i;k<j;k++){q=Recursive_Matrix_Multiply(p,i,k)+Recursive_Matrix_Multiply(p,k+1,j)+p[i-1]*p[k]*p[j];if(q<f[i][j]){f[i][j]=q;s[i][j]=k;}}return f[i][j];}
其中s[i][j]记录的是Ai...Aj相乘的最小代价的分割点,即先计算Ai...Ak,再计算Ak+1...Aj,然后再将结果相乘,Ai...Aj的相乘代价最小

递归的解法看起来思路清晰,但时间复杂度太高

若记n个矩阵的时间复杂度为T(n),则T(n)=∑k=1..n-1{T(k) +T(n-k) + c}, 可以推断T(n)>=2n

对递归算计的改进,可以引入备忘录,采用自顶向下的策略,维护一个记录了子问题的表,控制结构像递归算法。完整程序如下所示:


int memoized_matrix_chain(int *p,int m[N+1][N+1],int s[N+1][N+1]){    int i,j;    for(i=1;i<=N;++i){        for(j=1;j<=N;++j){        m[i][j] = MAXVALUE;        }    }    return lookup_chain(p,1,N,m,s);} int lookup_chain(int *p,int i,int j,int m[N+1][N+1],int s[N+1][N+1]){    if(m[i][j] < MAXVALUE)        return m[i][j]; //直接返回,相当于查表    if(i == j)         m[i][j] = 0;    else    {        int k;        for(k=i;k<j;++k)        {            int temp = lookup_chain(p,i,k,m,s)+lookup_chain(p,k+1,j,m,s) + p[i-1]*p[k]*p[j];  //通过递归的形式计算,只计算一次,第二次查表得到            if(temp < m[i][j])            {                m[i][j] = temp;                s[i][j] = k;             }        }    }    return m[i][j];}
虽然给出了递归解的过程,但是在实现的时候不采用递归实现,而是借助辅助空间,使用自底向上的表格进行实现。设矩阵Ai的维数为pi-1pi,i=1,2.....n。输入序列为:p=<p0,p1,...pn>,length[p] = n+1。使用m[n][n]保存m[i,j]的代价,s[n][n]保存计算m[i,j]时取得最优代价处k的值,最后可以用s中的记录构造一个最优解。书中给出了计算过程的伪代码,摘录如下:

MAXTRIX_CHAIN_ORDER(p)    n = length[p]-1;    for i=1 to n        do m[i][i] = 0;    for t = 2 to n  //t is the chain length        do for i=1 to n-t+1            j=i+t-1;            m[i][j] = MAXLIMIT;            for k=i to j-1                q = m[i][k] + m[k+1][i] + qi-1qkqj;                if q < m[i][j]                    then m[i][j] = q;                    s[i][j] = k;    return m and s;


构造一个最优解

已经计算出来最小代价,并保存了相关的记录信息。因此只需对s表格进行递归调用展开既可以得到一个最优解。书中给出了伪代码,摘录如下:

PRINT_OPTIMAL_PARENS(s,i,j)    if i== j         then print "Ai"    else        print "(";        PRINT_OPTIMAL_PARENS(s,i,s[i][j]);        PRINT_OPTIMAL_PARENS(s,s[i][j]+1,j);        print")";





0 0
原创粉丝点击