动态规划经典例子


基本思想

动态规划算法与分治法类似,其基本思想也是将待求解问题分解成若干个子问题.但是经分解得到的子问题往往不是互相独立的。

不同子问题的数目常常只有多项式量级。在用分治法求解时,有些子问题被重复计算了许多次。

如果能够保存已解决的子问题的答案,而在需要时再找出已求得的答案,就可以避免大量重复计算,从而得到多项式时间算法。

基本思想: 将待求解的问题分解成若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解。
基本要素: 最优子结构性质和重叠子问题性质

与分治法的区别:
分治算法是把原问题分解为若干个子问题,自顶向下求解子问题,合并子问题的解,从而得到原问题的解。动态规划也是把原始问题分解为若干个子问题,然后自底向上,先求解最小的子问题,把结果存在表格中,在求解大的子问题时,直接从表格中查询小的子问题的解,避免重复计算,从而提高算法效率。


矩阵连乘

问题:给定n个矩阵{A1,A2,…,An},其中,Ai与Ai+1是可乘的,(i=1,2 ,…,n-1)。用加括号的方法表示矩阵连乘的次序,不同的计算次序计算量(乘法次数)是不同的,找出一种加括号的方法,使得矩阵连乘的次数最小

解决思路:
A[i:j] 表示矩阵 i 到 j 的连乘
假设在第 k 位置上找到最优解,则问题变成了两个子问题:A[i:k] , A[k+1,j]
用 m[i][j] 表示矩阵连乘的最优值,那么两个子问题对应的最优值变成 m[i][k], m[k+1][j],且原问题的最优值为 m[1][n]。

  • i=j,m[i][j] = 0
  • i<j,m[i][j] = min{m[i][k] + m[k+1][j] + pi-1 * pk * pj}(i<=k<j,加上合并的计算量)

伪代码

		public static void matrixChain(int[] p,int[][] m,int[][] s){
			int n = p.length-1;
			for(int i=1;i<=n;i++)
				m[i][i] = 0;
			for(int r=2;r<=n;r++){//矩阵连乘的规模为r
				for(int i=1;i<=n;i++){
					int j=i+r-1;
					m[i][j] = m[i+1][j] + p[i-1]*p[i]p[j];
					for(int k=i+1;k<j;k++){
						int t = m[i][k]+m[k+1][j] + p[i-1]*p[i]*p[j];
						if(t<m[i][j]){
							m[i][j] = t;
							s[i][j] = k;
						}
					}
				}
			}
		}

最长公共子序列

问题:字符序列的子序列是指从给定字符序列中随意地(不一定连续)去掉若干个字符(可能一个也不去掉)后所形成的字符序列。令给定的字符序列X=“x0,x1,…,xm-1”,序列Y=“y0,y1,…,yk-1”是X的子序列,存在X的一个严格递增下标序列<i0,i1,…,ik-1>,使得对所有的j=0,1,…,k-1,有xij=yj。例如,X=“ABCBDAB”,Y=“BCDB”是X的一个子序列。

解决思路:
对于 X={x1,x2,x3,…,xm},Y = {y1,y2,y3,…,yn}

  • xm=yn,则 zk = xm = yn -> Zk-1 是 Xm-1 和 Yn-1 的最长公共子序列
  • xm =/= yn,则 Z 是 Xm-1 和 Y ,X 和 Yn-1 中较长的子序列。

由上面的分析可以得到如下递归关系,c[i][j] 表示 Xi 和 Yi 的最长公共子序列长度
在这里插入图片描述
结果如下:
在这里插入图片描述
伪代码:

		public static int lcsLength(char[] x,char[] y,int[][] b){//b用来记录路径
			int m=x.length-1'
			int n = y.length-1;
			int[][] c = new int[m+1][n+1];
			for(int i=1;i<=m;i++){
				c[i][0]=0;
			}
			for(int i=1;i<=n;i++){
				c[0][i]=0;
			}
			for(int i=1;i<=m;i++){
				for(int j=1;j<=n;j++){
					if(x[i]==y[j]){
						c[i][j] = c[i-1][j-1]+1;
						b[i][j] = 1;
					}else if(c[i-1][j]>=c[i][j-1]){
						c[i][j] = c[i-1][j];
						b[i][j] = 2;
					}else{
						c[i][j] = c[i][j-1];
						b[i][j] = 3;
					}
				}
				return c[m][n];
			}
		}

凸多边形三角剖分

问题:给定凸多边形P,以及定义在由多边形的边和弦组成的三角形上的权函数w。要求确定该凸多边形的三角剖分,使得该三角剖分中诸三角形上权之和为最小。

解决思路:
和矩阵连乘问题是类似的,若凸(n+1)边形 P = {v0,v1,v2,…,vn} 的最优三角剖分 T 包含三角形 v0vkvn(1<=k<=n-1),则 T 的权分为三个部分权的和:三角形v0vkvn的权、子多边形{v0,v1,…,vk} 和{vk+1,…,vn}。设 t[i][j] 表示凸多边形{vi-1,vi,…,vj}的最优三角剖分所对应的权和,则原问题的最优权值为t[1][n]。

  • i=j,t[i][j] = 0
  • i<j,t[i][j] = min{t[i][k]+t[k+1][j]+w(vi-1vkvj)}(i<=k<j)

伪代码
和上面的矩阵连乘一样,这里就不写了。


电路布线

问题:确定导线集Nets = {i,π(i),1 ≤ i ≤ n}的最大不相交子集
在这里插入图片描述
解决思路:
记N(i,j) = {t|(t, π(t)) ∈ Nets,t ≤ i, π(t) ≤ j }. N(i,j)的最大不相交子集为MNS(i,j)Size(i,j)=|MNS(i,j)|,即最大不相交子集的个数。

  • 当 i=1 时
    在这里插入图片描述
  • 当 i>1 时
    在这里插入图片描述
    伪代码
		public static void mnset(int[] c,int[][] size){
			int n=c.length-1;
			for(int j=0;j<c[1];j++){
				size[1][j] = 0;
			}
			for(int j=c[1];j<=n;j++){
				size[1][j] = 1;
			}
			for(int i=2;i<n;i++){
				for(int j=0;j<c[i];j++){
					size[][] = size[i-1][j];
				}
				for(int j=c[i];j<=n;j++){
					size[][] = Math.max(size[i-1][j],size[i-1][c[i]-1]+1);
				}
			}
			size[n][m] = Math.max(size[n-1][n],size[n-1][c[n]-1]+1);
		}

流水作业调度

问题:n个作业 N={1,2,…,n}要在2台机器M1和M2组成的流水线上完成加工。每个作业须先在M1上加工,然后在M2上加工。M1和M2加工作业 i 所需的时间分别为 ai 和bi,每台机器同一时间最多只能执行一个作业。

流水作业调度问题要求确定这n个作业的最优加工顺序,使得所有作业在两台机器上都加工完成所需最少时间

解决思路:

  1. N = {1,2,3,…,n} 表示 n 个作业,ai 和 bi 表示分别在 M1 和 M2 上加工所需要的时间
    T(S,t) 表示 M1 在加工 S 中的作业时,M2 在加工别的作业还需等待 t 才可利用,原问题的最优解可以表示为 T(N,0),且 T(N,0) = {ai + T(N-{i},bi) } -> T(S,t) = {ai + T(S-{i},bi + max{t-ai,0}) }(bi + max{t,ai}-ai = bi+max{t-ai,0})

由上面的递归式就可以设计出我们的动态规划方法,但是还可以使用 johnson 法则进行简化:

  1. Johnson 法则:对于流水作业调度问题,必定存在一个最优调度π, 使得任意 i < j 满足Johnson不等式 min{bπ(i), aπ(j)} >= min{bπ(j), aπ(i)},所以流水作业调度问题的Johnson算法:
    (1) 令N1 = { i | ai < bi}, N2 = { i | ai >= bi };
    (2) 将N1中作业依ai的非减序排序;将N2中作业依bi的非增序排序;
    (3) N1中作业接N2中作业构成满足Johnson法则的最优调度。

代码:

		public static int flowShop(int a[],int b[],int c[]){//c为最优调度顺序
			int n=a.length-1;
			
			Element d[]=new Element[n];
			for(int i=0;i<n;i++){
				int key=a[i]>b[i]? b[i]:a[i];//找作业在两台机器上处理时间最小的那个作业处理时间
				boolean job = a[i]<=b[i];
				d[i]=new Element(key,i,job);
			}
			Arrays.sort(d);//将所有作业的key进行从小到大排序
			int j=0int k = n-1;
			//将作业按照Johnson法则排序放入c中
			for(int i=0;i<n;i++){
				if(d[i].job) c[j++]=d[i].index;//如果ai<=bi,将其作业序号放入c数组中(从头开始放)
				else c[k--]=d[i].index;//否则
			}
			//真正的动态规划部分!
			j=a[c[0]];//第一个作业在M1上的处理时间
			k=j+b[c[0]];//第一个作业处理完所需时间
			for(int i=1;i<n;i++){
				j+=a[c[i]];//第i个作业在机器上加工完成所需时间
				k=j<k? k+b[c[i]]:j+b[c[i]];
				/*如果此作业在M1上加工完成时间
				(包含前面作业在M1上的所用时间和)小于上一个作业全部完成时间(还得等M2做完),则此作业所需时间
				为k+b[c[i]],否则为j+b[c[i]]*/
		 
			}
			return k; 
		}
		public static class Element{
			public int key;	
			public	int index;
			public	boolean job;
			
			private Element(int kk,int ii,boolean jj){
				key=kk;
				index=ii;
				job=jj;
			}
		}

01-背包

问题:给定 n 种物品和一背包,物品 i 的重量是 wi,其价值为 vi,背包的容量为 C,问应该如何选择装入背包的物品,使得装入背包中物品的总价值最大?

解决思路:

  1. 常规解法:用 m(i,j) 表示背包容量为 j,可选择物品为 i,i+1,…,n 的最优值,则原问题的解为m(1,C)。
    在这里插入图片描述
  2. 跳跃点解法:对于每一个确定的 i 值,都有一个对应的跳跃点集Pi={ ( j, m(i,j) ),……}
    (1) 开始求解时,先求Pi,初始时Pn+1={(0,0)},i=n+1,由此按下列步骤计算Pi-1,Pi-2……P1,即Pn,Pn-1,……P1
    (2) Qi+1 = pi+1 ⊕ (wi,vi) = {(j+wi,m(i,j)+vi) | (j,m(i,j))∈pi+1}
    (3) Pi-1 = Pi∪Qi 然后再去掉受控跳跃点后的点集
    受控跳跃点: 若点(a,b),(c,d)∈Pi∪Qi,且a<=c,b>d,则(c,d)受控于(a,b),所以(c,d)∉Pi-1。
    由此计算得出Pn,Pn-1,……,P1。求得P1的最后那个跳跃点即为所求的最优值m(1,C)

代码: 待补充

发布了96 篇原创文章 · 获赞 57 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/MOKEXFDGH/article/details/93381359