备忘录方法

算法定位

该法是动态规划的变形,或者可以理解为动态规划的优化,优化在增强理解和书写简单方面。

直接递归

如果一个问题可以不断拆分成子问题,然后再由小问题的解,一层层归约回大问题。那么,这种问题可以直接采用递归法求解。以上一篇中的矩阵连乘问题为例,可以得到如下的递归求解套路:

public static int recurMatrixChain(int i,int j) {
		/**
		 *输入值对应A[i:j],即给连乘串Ai~Aj加括号
		 *返回该串的最少数乘次数
		 */
		if(i==j) return 0;
		int u=recurMatrixChain(i+1,j)+p[i-1]*p[i]*p[j];
		s[i][j]=i;
		for(int k=i+1;k<j;k++) {
			int t=recurMatrixChain(i,k)+recurMatrixChain(k+1,j)+p[i-1]*p[k]*p[j];
			if(t<u) {
				u=t;
				s[i][j]=k;
			}
		}
		return u;
	}

读代码可以发现:递归的方式是自顶向下,即要求大问题,先求它的小问题,一层层拆下去,就像一个树。但是该法没有保存过程中的子问题解,注意!!这是重点,没有保存,意味着每次遇到同样的问题都要重新求解一遍,浪费时间T(n)=Ω(2^n)下界。

递归+记录表=备忘录方法

想用递归求解,又不想重复计算子问题,备忘录方法就巧妙的建立了备忘录,记录每个子问题的解,求解问题过程中先查表,如果该问题已经有答案,则可以直接拿来用,无需重新求解。代码如下:

//建立备忘录
	public static int memMatrixChain(int n) {
		for(int i=1;i<=n;i++)
			for(int j=i;j<=n;j++)
				m[i][j]=0;//备忘录先初始化都为0
		return lookupChain(1,n);
	}
	//递归求解问题并先查表
	public static int lookupChain(int i,int j) {
		if(m[i][j]>0) return m[i][j];//先查表,如果对应值不为0说明求解过,直接拿来用
		if(i==j) return 0;
		int u=lookupChain(i+1,j)+p[i-1]*p[i]*p[j];
		s[i][j]=i;
		for(int k=i+1;k<j;k++) {
			int t=lookupChain(i,k)+lookupChain(k+1,j)+p[i-1]*p[k]*p[j];
			if(t<u) {
				u=t;
				s[i][j]=k;
			}
		}
		m[i][j]=u;//更新备忘录
		return u;
	}

备忘录方法的时间复杂度和动态规划的一样,T(n)=O(n^3)上界。

选用

动态规划和备忘录方法时间成本都一样,那怎么选用?

一般情况下,若所有子问题需要一个不拉的至少求解一遍时,用动态规划,它遍历整个矩阵空间,没有多余的计算。

若子问题存在有不需要求解的部分(外加条件),可用备忘录方法,从它的递归可以看出,通过判断只求需要解的问题。

参考文献

[1] 算法设计与分析(第2版) 王晓东

猜你喜欢

转载自blog.csdn.net/sinat_32561655/article/details/81382261
今日推荐