ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

一文看懂动态规划

2021-10-26 22:04:59  阅读:331  来源: 互联网

标签:一文 矩阵 次序 算法 计算 最优 动态 规划


目录

1.动态规划的概念

2.动态规划的基本要素

1.最优子结构性质(最优化原理)

2.重叠子结构性质

3.动态规划基本思想

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

4.动态规划算法实例

1.矩阵连乘问题

1.穷举法

2.动态规划法


1.动态规划的概念

    在现实生活中,有一类活动的过程,由于它的特殊性,可将过程分成若干个互相联系的阶段,在它的每一阶段都需要作出决策,从而使整个过程达到最好的活动效果。因此各个阶段决策的选取不能任意确定,它依赖于当前面临的状态,又影响以后的发展。当各个阶段决策确定后,就组成一个决策序列,因而也就确定了整个过程的一条活动路线.

    这种把一个问题看作是一个前后关联具有链状结构的多阶段过程就称为多阶段决策过程,这种问题称为多阶段决策问题。在多阶段决策问题中,各个阶段采取的决策,一般来说是与时间有关的,决策依赖于当前状态,又随即引起状态的转移,一个决策序列就是在变化的状态中产生出来的,故有“动态”的含义,称这种解决多阶段决策最优化的过程为动态规划方法

2.动态规划的基本要素

1.最优子结构性质(最优化原理)

        最优化原理可这样阐述:一个最优化策略具有这样的性质,不论过去状态和决策如何,对前面的决策所形成的状态而言,余下的诸决策必须构成最优策略。简而言之,一个最优化策略的子策略总是最优的。一个问题满足最优化原理又称其具有最优子结构性质。

2.重叠子结构性质

        动态规划算法的关键在于解决冗余,这是动态规划算法的根本目的。动态规划实质上是一种以空间换时间的技术,它在实现的过程中,不得不存储产生过程中的各种状态,所以它的空间复杂度要大于其他的算法。选择动态规划算法是因为动态规划算法在空间上可以承受,而搜索算法在时间上却无法承受,所以我们舍空间而取时间。

3.动态规划基本思想

    动态规划算法通常用于求解具有某种最优性质的问题。在这类问题中,可能会有许多可行解。每一个解都对应于一个值,我们希望找到具有最优值的解。

    动态规划算法与分治法类似,其基本思想也是将待求解问题分解成若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解。

    与分治法不同的是,适合于用动态规划求解的问题,经分解得到子问题往往不是互相独立的。若用分治法来解这类问题,则分解得到的子问题数目太多,有些子问题被重复计算了很多次。如果我们能够保存已解决的子问题的答案,而在需要时再找出已求得的答案,这样就可以避免大量的重复计算,节省时间。

    我们可以用一个表来记录所有已解的子问题的答案。不管该子问题以后是否被用到,只要它被计算过,就将其结果填入表中。这就是动态规划法的基本思路。具体的动态规划算法多种多样,但它们具有相同的填表格式

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

  • 步骤1.找出最优解的性质,并刻画其结构特征
  • 步骤2.递归地定义最优值
  • 步骤3.以自底向上的方式计算最优值
  • 步骤4.根据计算最优值时得到的信息构造最优解

步骤1~3是动态规划算法的基本步骤。在只需要求出最优值的情况下,步骤4可以省略。

若需要求出问题的最优解,则必须执行步骤4。此时,在步骤3中计算最优值时,通常需要记录更多的信息,以便在步骤4中,根据所记录的信息,快速构造出一个最优解。

4.动态规划算法实例

1.矩阵连乘问题

问题描述:

        给定n个矩阵{A1,A2,A3,....,An},其中Ai和Ai+1是可以相乘的(i=1,2,...,n-1),考察这n个矩阵的连乘积A1A2...An

        由于矩阵乘法满足结合律,因此计算矩阵的连乘积可以有不同的计算次序。这种计算次序可以通过加括号的方法来确定。

我们来引出计算代价的概念

矩阵乘法的代价:乘法次数

若A 是p ×q 矩阵,B 是q ×r 矩阵,则A ×B 的计算代价是pqr

举例:A1、A2、A3连乘积

假设这三个矩阵的维数分别为:

A1:20×50

A2:50×5

A3:5×30

(A1A2)A3:计算代价:20×50×5 + 20×5×30 = 8000

A1(A2A3):计算代价:50×5×30 + 20×50×30 = 37500

        我们可以看到第二种加括号的方法差不多是第一种加括号计算量的五倍,由此可见,在计算矩阵连乘积时,加括号的方式即计算次序对计算量有很大影响。

我们今天要解决的问题就是如何确定计算矩阵连乘积A1A2...An的计算次序,使得依此次序计算矩阵连乘积需要的数乘次数(计算代价)最少。

1.穷举法

        解决上面的问题,我们可能最先想到的就是穷举法,就是列出所有可能的计算次序,并计算每种计算次数相应所需要的数乘次数,从中找出一种数乘次数最少的计算次序。但这样做的计算量太大了。

        对于n个矩阵的连乘积,设有不同的计算次序P(n),可以现在第k个(k=1,2,...,n-1)和第k+1个矩阵之间将原矩阵序列分为两个矩阵子序列,然后分别对着两个矩阵子序列完全加括号,最后对所得的结果加括号,得到原矩阵序列的一种完全加括号方式,由此,可以得到关于P(n)的递归式如下:

 我们仔细观察这个递归方程,可以发现P(n)实际上是一个Catalan数,即P(n)=C(n-1),其中

C(n) = \frac{1}{n + 1} \binom{2n}{n} = \Omega(\frac{4^{n}}{n^{3/2}})

关于Catalan数的知识,可以自行百度,这里不多赘述

我们可以发现,P(n)是随n的增长呈指数增长的,因此,穷举法不是一个有效算法。

下面我们考虑用动态规划法来解决这个问题

2.动态规划法

1.分析最优解的结构

        设计求解具体问题的动态规划算法的第一步是刻画该问题的最优解的结构特征。为方便起见,将矩阵连乘积AiAi+1...Aj简记为A[i:j]。

考察计算A[1:n]的最优计算次序。

        设这个计算次序在矩阵Ak(1≤ k<n)和Ak+1之间将矩阵链断开,则其相应的完全加括号方式为((A1...Ak)(Ak+1...An))。依此次序,先计算A[1:k]和A[k+1:n],再将计算结果相乘,得到A[1:n]。

依此计算次序,总计算量为A[1:k]的计算量加上A[k+1:n]的计算量,再加上A[1:k]和A[k+1:n]相乘的计算量。

        这个问题的一个关键特征是:计算A[1:n]的最优次序所包含的计算矩阵子链A[1:k]和A[k+1:n]的次序也是最优的。事实上,如果计算A[1:k]的次序需要的计算量更少,则用此次序替换原来计算A[1:k]的次序,得到的计算A[1:n]的计算量将比最有次序所需计算量更少,这是一个矛盾。同理可知,计算A[1:n]的最优次序包含的计算矩阵子链A[k+1:n]的次序也是最优的。

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

2.建立递归关系

对于矩阵连乘积的最优计算次序问题,设计算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,3…..n)。
当i<j时,可以利用最优子结构性质来计算m[i][j]。事实上,若计算A[i:j]最有次序在Ak和Ak+1之间断开时i<=k<j;m[i][j] = m[i][k]+m[k+1][j]+P(i-1)PkPj;由于在计算时我们并不知道k的具体位置k可以去j-i种可能。所以k是这j-i种可能中使得计算量达到最小的那个位置,从而m[i][j]可以递归定义为:

3.计算最优值

根据计算m[i][j]的递归式,容易写一个递归算法计算m[1][n]。

接下来我们借助填表过程理解递归的过程,现在给出下列矩阵:

在这里插入图片描述
填表过程是按对角线填写的,只利用到了二维数组的右上角一部分。

根据地推公式,我们可以知道,在i=j时m=0,所以先构造出最长的对角线部分的数据,如下图:

在这里插入图片描述
现在我们继续构造,
m(1,2)=min{m[1][1]+m[2][2]+p0p1p2}={0+0+303515}=15750

m(2,3) = min(m[2][2]+m[3][3]+p1p2p3=0+0+35155)=2625

同理,后面不再一一列举;

再多说一点,有时我们会遇到有多个划分,我们取最小值就可以了,

例如:

m[2][5] = min\begin{cases} & \text{ } m[2,2] + m[3,5] + p1p2p5 = 0 + 2500 + 35*15*20 = 13000\\ & \text{ } m[2,3] + m[4][5] + p1p3p5 = 2625 + 1000 +35*5*20 = 7125\\ & \text{ } m[2,4] + m[5][5] + p1p4p5 = 4375 + 0 + 35*10*20 = 11375 \end{cases}

结果图如下:

在这里插入图片描述

 

那么,我们最后如何得知是哪个地方要加括号呢?
根据最后的公式。

例如,假设最后的m[1:6]=m[1,1]+m[2][6]+p0p2p6(笔者构造的,跟上面的例子没关系),那么我们就知道是(A1(A2A3A4A5A6)),再看m[2:6],根据公式找退出括号位置,一直推到最后即可。

我们不难发现,加括号的位置其实就是k 的对应序号的矩阵,在写算法时我们就可以用另外的数组记录下对应位置的k值。在最后输出时把这个数组按逻辑输出。

核心算法:

void MatrixChain(int n)
{
	int r, i, j, k;
	for (i = 0; i <= n; i++)//初始化对角线
	{
		m[i][i] = 0;
	}
	for (r = 2; r <= n; r++)//r个矩阵连乘
	{
		for (i = 1; i <= n - r + 1; i++)//r个矩阵的r-1个空隙中依次测试最优点
		{
			j = i + r - 1;
			m[i][j] = m[i][i]+m[i + 1][j] + A[i - 1] * A[i] * A[j];
			s[i][j] = i;
			for (k = i + 1; k < j; k++)//变换分隔位置,逐一测试
			{
				int t = m[i][k] + m[k + 1][j] + A[i - 1] * A[k] * A[j];
				if (t < m[i][j])//如果变换后的位置更优,则替换原来的分隔方法。
				{
					m[i][j] = t;
					s[i][j] = k;
				}
			}
		}
	}
}

算法MatrixChain的主要计算量取决于程序中的三层for循环,而三层for循环的总次数为O(n^{3}),因此,该算法的计算时间上界为O(n^{3})

 4.构造最优解

        动态规划算法的第4步是构造问题的最优解。算法MatrixChain只是计算除了最优值,并未给出最优解,也就是说,通过MatrixChain的计算,只知道最少数乘次数,还不知道具体应按照什么次序来做矩阵乘法才能达到最小的数乘次数。

        事实上,MatrixChain已记录了构造最优解所需的前部信息。s[i][j]中的数表明,计算矩阵链A[i:j]的最佳方式是在矩阵Ak和Ak+1之间断开,即最优加括号方式为(A[i:k)](A[k+1:j])。从s[1][n]记录的信息可知计算A[1:n]的最优加括号方式为(A[1:s[1][n]])(A[s[1][n]+1:n])。而A[1:s[1][n]]的最优加括号方式为(A[1:s[1][s[1][n]]])(A[s[1][n]]+1:s[1][s[1][n]]])。同理可以确定A[s[1][n]+1:n]的最优加括号方式......以此类推,最终可以确定A[1:n]的最优加括号方式,即构造出一个最优解。

下面是该算法的完整代码:

#include<iostream>
using namespace std;
const int N = 100;
int A[N];//矩阵规模
int m[N][N];//最优解
int s[N][N];
void MatrixChain(int n)
{
	int r, i, j, k;
	for (i = 0; i <= n; i++)//初始化对角线
	{
		m[i][i] = 0;
	}
	for (r = 2; r <= n; r++)//r个矩阵连乘
	{
		for (i = 1; i <= n - r + 1; i++)//r个矩阵的r-1个空隙中依次测试最优点
		{
			j = i + r - 1;
			m[i][j] = m[i][i]+m[i + 1][j] + A[i - 1] * A[i] * A[j];
			s[i][j] = i;
			for (k = i + 1; k < j; k++)//变换分隔位置,逐一测试
			{
				int t = m[i][k] + m[k + 1][j] + A[i - 1] * A[k] * A[j];
				if (t < m[i][j])//如果变换后的位置更优,则替换原来的分隔方法。
				{
					m[i][j] = t;
					s[i][j] = k;
				}
			}
		}
	}
}
void print(int i, int j)
{
	if (i == j)
	{
		cout << "A[" << i << "]";
		return;
	}
	cout << "(";
	print(i, s[i][j]);
	print(s[i][j] + 1, j);//递归1到s[1][j]
	cout << ")";
}
int main()
{
	int n;//n个矩阵
	cin >> n;
	int i, j;
	for (i = 0; i <= n; i++)
	{
		cin >> A[i];
	}
	MatrixChain(n);
	cout << "最佳添加括号的方式为:";
	print(1, n);
	cout << "\n最小计算量的值为:" << m[1][n] << endl;
	return 0;
}

备忘录算法后期再写吧,今晚太累了

标签:一文,矩阵,次序,算法,计算,最优,动态,规划
来源: https://blog.csdn.net/ypxcan/article/details/120953164

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有