【动态规划 Dynamic Programming dp】

动态规划以及其与分治的对比

分治,是把一个问题分解成独立的子问题,递归的解决子问题,再组成原问题
动态规划是解决这些子问题不是独立的问题的情况。

斐波那契数列

其子问题不是独立的子问题

f(4)子问题是f(3)与f(2),而f(2)又是f(3)的子问题。

一个分而治之的算法做的工作比必要的多,反复地解决公共的子问题。
动态规划算法对应每个子问题只解决一次,然后将其答案保存在表中,从而避免了每次遇到子问题时重新计算答案的工作。

动态规划通常用于最优解问题

动态规划算法可以分为四个步骤。
1)最优解的递归结构
2)建立最优值的递推公式
3)计算最优值
4)构造最优解
1)——3)是必须的
只有需要求最优解是需要步骤4)

动态规划的要素
①最优子结构性质
②重叠子问题性质

1、最优子结构:当问题的最优解包含了其子问题的最优解时,称该问题具有最优子结构性质。
2、重叠子问题:在用递归算法自顶向下解问题时,每次产生的子问题并不总是新问题,有些子问题被反复计算多次。动态规划算法正是利用了这种子问题的重叠性质,对每一个子问题只解一次,而后将其解保存在一个表格中,在以后尽可能多地利用这些子问题的解。

矩阵连乘问题

考虑pq矩阵A,qr矩阵B
计算矩阵AB需要pqr次乘法


括号的位置不同会有不同的计算量。

idea1:brute force 穷举搜索法
对于n个矩阵的连乘积,设有p(n)个计算次序。我们可以在第k个和第k+1个矩阵之间将原矩阵划分为两个子矩阵序列,然后分别对这两个矩阵子序列完全加括号,最后对所得的结果加括号,则

计算次序太多了!!!穷举的太多

idea2:动态规划
step1:求最优解的递归结构
如果A[1:n]的一个最优解是A[1:k]和A[k+1:n]
那么A[1:k]和A[k+1:n]对于它们的子问题是最优的。
step2:建立最优值的递推公式
设m[i,j]是计算Ai……Aj乘积的最小次数
则:

情况m[i,j]值
i=j0
i<j

step3:计算最优值

①递归方法

public class Matrix_chainMultiplication {
	
	public static void main(String args[]) {
		int p[]= {10,100,5,50};
		int n=p.length;
		int s[][]=new  int[n][n];
		int u=recurMatriChain(1,n-1,p,s);
		System.out.println(u);
		for(int i=1;i<n;i++) {
			for(int j=1;j<n;j++) {
				System.out.print(s[i][j]+"\t");
			}
			System.out.println("");
		}
		
		
	}
	
	public static int recurMatriChain(int i,int j,int p[],int s[][]) {
		
		if(i==j) return 0;
		int u= recurMatriChain(i,i,p,s)+ recurMatriChain(i+1,j,p,s)+p[i-1]*p[i]*p[j];
		s[i][j]=i;
		for(int k=i+1;k<j;k++) {
			int t=recurMatriChain(i,k,p,s)+ recurMatriChain(k+1,j,p,s)+p[i-1]*p[k]*p[j];
		    if(t<u) {
		    	u=t;
		    	s[i][j]=k;
		    }
		}
		return u;
		
	}

}


时间复杂度:O(2n)

②动态规划方法
用动态规划解此问题时,在计算过程中,保存已解决的子问题答案,每个子问题只计算一次,而在后面用到时简单地查一下,从而避免了大量的重复计算

public class Matrix_chainMultiplication {
	
	public static void main(String args[]) {
		int p[]= {10,100,5,50};
		int n=p.length;
		int s[][]=new  int[n][n];
		int m[][]=new  int[n][n];
		
		matrixChain(p,s,m,n-1);
		System.out.println(m[1][n-1]);
		for(int i=1;i<n;i++) {
			for(int j=1;j<n;j++) {
				System.out.print(s[i][j]+"\t");
			}
			System.out.println("");
		}
		
		
	}
	public static void matrixChain(int p[],int s[][],int m[][],int n) {
		for(int i=1;i<=n;i++) m[i][i]=0;
		for(int r=2;r<=n;r++) {
			for(int i=1;i<=n-r+1;i++) {
				int j=i+r-1;
				m[i][j]=m[i+1][j]+p[i-1]*p[i]*p[j];
				s[i][j]=i;
				for(int k=i+1;k<j;k++) {
					int t=m[i][k]+m[k+1][j]+p[i-1]*p[k]*p[j];
					if(t<m[i][j]) {
						m[i][j]=t;
						s[i][j]=k;
					}
				}
			}
		}
		
		
		
	}
}

时间复杂度O(n3)

step4:构造最优解

static void traceback(int i,int j,int s[][]) {
		if(i==j) return ;
		traceback(i,s[i][j],s);
		traceback(s[i][j]+1,j,s);
		System.out.print("multiple A"+i+",A"+s[i][j]+"\t");
		System.out.println("multiple A"+(s[i][j]+1)+",A"+j+"\t");
		
	}

备忘录方法

其思想是记住自然的,但效率低下的递归算法。
与自底向上的方法一样,我们维护一个包含子问题解的表,但填充该表的控制结构更像是递归算法。
每个表条目最初都包含一个特殊值,表示该条目尚未填写。当递归算法展开时第一次遇到子问题时,计算其解,然后将其存储在表中。以后每次遇到这个子问题时,我们只需查找存储在选项卡中的值。

public static int lookupChain(int i,int j,int p[],int s[][],int m[][]) {
	  if(m[i][j]>0) return m[i][j]; 
	  if(i==j) return 0;
	  int u= lookupChain(i,i,p,s,m)+lookupChain(i+1,j,p,s,m)+p[i-1]*p[i]*p[j]; 
	  s[i][j]=i; 
	  for(int k=i+1;k<j;k++) { 
		  int t=lookupChain(i,k,p,s,m)+lookupChain(k+1,j,p,s,m)+p[i-1]*p[k]*p[j]; 
		    if(t<u) { 
		    	u=t; 
		    	s[i][j]=k; 
		    	} 
		    }
	  m[i][j]=u;
	  return u;
	}

0-1背包问题

给定n个物品的重量和价值,将这些物品放在容量C的背包中,以获得背包中的最大总价值。

两个数组v[ ]和w[ ]分别代表价值和重量。
数组x[ ]:当x[i]=0时表示,物品i没有放在包里,当x[i]=1时表示物品i放在包里。
于是问题的最优值就是求最大总容量
最优解就是最大总容量对应的 x[ ]数组
在这里插入图片描述

step1:最优解结构

反证法即可

step2:建立最优值的递推公式
m(i,j)表示背包容量为j,可选物品为从i到n的价值的最大值。

初始值:

step3:计算最优值

public class BagProblem {
	public static void main(String args[]) {
		int w[]= {2,1,3,2};
		int v[]= {12,10,20,15};
		int c=5;
		int n=w.length;
		int[][] m=new int[n+1][c+1];
		knapsack(v,w,c,n,m);
		for(int i=1;i<n+1;i++) {
			for(int j=0;j<c+1;j++) {
				System.out.print(m[i][j]+"\t");
			}
			System.out.println(" ");
		}
	    System.out.print(m[1][c]);
		
		
		
	}
	public static void knapsack(int[] v,int[] w,int c,int n,int[][] m) {
		//赋初值
		for(int j=w[n-1];j<m[0].length;j++) {
			 m[n][j]=v[n-1];
		}
		for(int i=n-1;i>=1;i--) {
			for(int j=0;j<w[i-1];j++) {
				m[i][j]=m[i+1][j];
			}
			for(int j=w[i-1];j<m[0].length;j++) {
				m[i][j]=Integer.max(m[i+1][j],m[i+1][j-w[i-1]]+v[i-1]);
			}
		}
		
	
	}

}


step4:构造最优解

public  void trackback(int[][] m,int w[] ,int c,int n ,int[] x ) {
		for(int i=1;i<n;i++) {
			if(m[i][c]==m[i+1][c]){ x[i]=0;}
			else {
				x[i]=1;
				c=c-w[i-1];
				
			}
		  }
		x[n]=(m[n][c]==0)?0:1;
	}

最优二叉搜索树



这里二叉搜索树的叶子是null;
二叉树中的叶顶点是形如(xi, xi+1) 的开区间。

利用动态规划构造对标识符集合
{a1, a2, …, an}的最优二叉搜索树算法包括成功检索和不成功检索)。

step1:最优子结构
](https://img-blog.csdnimg.cn/20200208140317632.png)
设COST(L) 和COST® 分别是二分检索树T的左子树和右子树的成本
则检索树T的成本是:P(k)+ COST(L) + COST®
若 T 是最优的,则上式及 COST(L) 和COST® 必定都取最小值。

step2:建立最优值的递推公式



step3:计算最优值

public class OptimalBinarySearchTree {
	public static void main(String args[]) {
		double p[]= {0,0.5,0.1,0.05};
		double q[]= {0.15,0.1,0.05,0.05};
		int n=q.length;//n=4
		double m[][]=new double[n+1][n];
		int[][] s=new int[n+1][n];
		double w[][]=new double[n+1][n];
		optimalBinarySearchTree(p,q,n,m,s,w);
		for(int i=1;i<n+1;i++) {
			for(int j=0;j<n;j++) {
				System.out.print(w[i][j]+"\t");
			}
			System.out.println("");
		}
		
		
		
		
	}
	public static void optimalBinarySearchTree(double[] p, double[] q,int n, double[][] m, int[][] s, double[][] w) {
		//初始化对角线
		for(int i=0;i<n;i++) {
			w[i+1][i]=q[i];
			m[i+1][i]=0;
		}//构造没有内部节点的情况
		for(int r=0;r<n;r++) {
			for(int i=1;i<n-r;i++) {
				int j=i+r;
				w[i][j]=w[i][j-1]+p[j]+q[j];
				m[i][j]=m[i+1][j];
				s[i][j]=i;
				for(int k=i+1;k<=j;k++) {
			    	double t=m[i][k-1]+m[k+1][j];
			    	if(t<m[i][j]) {
			    		m[i][j]=t;
			    		s[i][j]=k;
			    		
			    		
			    	}
			    	
			    }
				m[i][j]+=w[i][j];
				
			}
		}
		
	}

	
}

step4:构造最优解

复杂度

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值