算法的独家记忆--动态规划

本文深入探讨了动态规划的核心概念——最优子结构与重叠子问题,并通过生产线问题、矩阵连乘及最长公共子串三个典型实例,展示了如何利用动态规划求解实际问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

动态规划通常应用于最优解的问题,即在有多个解的问题中找到“一个”的最优解(这个解并非“确定不变”)。动态规划的核心:最优子结构和重叠子问题。

最优子结构:一个问题的最优解包含了子问题的最优解,通过解决子问题为整个问题的解决提供支持。

重叠子问题:通过每个子问题只解一次,把解保存在一个表中,让递归过程中重复的子问题跟快得到解决。

解题步骤:

1.找到问题的最优解结构。

2.递归定义最优解的值。

3.自底向上计算最优解的值。

4.通过计算的最构造最优解。


大部分动态规划的问题的难点在于发现问题中最优解的递归过程。先承认自己没有天才的头脑,因此记下自己所遇的题的解法以满足一己私欲吧,下文中突兀的符号,不清楚的题意,丑陋的代码……望大家见谅。

《算法导论》

生产线问题:f1[i]表示到生产线1的i装备站的最优时间。a1[i]在生产线1的i装备站上花的时间。t1[i]表示从生产线1的i装备站移到另一条生产线所花的时间。对应还有数据f2[i],a2[i],t2[i]。e,x分别进入生产线和离开生产线的时间。

递归解:f1[j] = e1 + a1[1]                                               j=1;

                       =min(f1[j-1] + a1[j] , f2[j-1] + t2[j-1] + a1[j])      j>=2;

                f1[j] = e2 + a2[1]                                               j=1;

                       =min(f2[j-1] + a2[j] , f1[j-1] + t2[j-1] + a2[j])      j>=2;

实现:

public class ProductLine {
	/**
	 * @author htorc
	 */
	public static void main(String[] args) {
		int a1[] = {7,9,3,4,8,4};
		int a2[] = {8,5,6,4,5,7};
		int t1[] = {2,3,1,3,4};
		int t2[] = {2,1,2,2,1};
		int x[] = {3,2};
		int e[] = {2,4};
		int f1[] = new int[a1.length];
		int f2[] = new int[a1.length];
		
	    f1[0] = a1[0] + e[0];
	    f2[0] = a2[0] + e[1];
	    int i;
	    for(i=0; i<t1.length; i++){
	    	f1[i+1] = Math.min(f1[i] + a1[i+1] , f2[i] + t2[i] + a1[i+1]);
	    	f2[i+1] = Math.min(f1[i] + a2[i+1] + t1[i] , f2[i] + a2[i+1]);
	    }
	    System.out.println(Math.min(f1[i]+x[0], f2[i]+x[1]));
	}
}

矩阵连乘:A0*A1……An适当添加括号使计算次数最少。  p[i],p[i+1]表示矩阵Ai的行数和列数。

递归解:m[i][j]表示Ai……Aj最少计算次数,s[i][j]表示分割点k的位置。

               m[i][j] = 0                                                                               i=j

                          = min{m[i][k] + m[k+1][j] + pi*pk*pj}( i<k<=j)        i<j

实现:

public class Matrix {

	/**
	 * @author ht
	 */
	public static void print(int[][] s, int i, int j){
		if(i == j)
			System.out.print("A" + i);
		else{
			System.out.print("(");
			print(s, i, s[i][j]-1);
			print(s, s[i][j], j);
			System.out.print(")");
		}
		
	}
	
	public static void main(String[] args) {
		int[] p = {30,35,15,5,10,20,25};
		long[][] m = new long[p.length-1][p.length-1];
		int[][] s = new int[p.length-1][p.length-1];	
		long min_value,sum;
		int min_key;
		for(int i=0;i<p.length-1;i++){
			m[i][i] = 0;
			s[i][i] = 0;
		}
		for(int i=1; i<p.length-1; i++){
			for(int j=0; j<p.length-i-1; j++){
				min_value = Long.MAX_VALUE;
				min_key = j+1;
				for(int k=1; k<=i; k++){
					sum = m[j][j+k-1]+m[j+k][i+j] + p[j]*p[j+k]*p[i+j+1];
					if(sum<min_value){
						min_value = sum;
						min_key = j+k;					
					}
				}
				m[j][j+i] = min_value;
				s[j][j+i] = min_key;			
			}
		}
		System.out.println(m[0][p.length-2]);
		for(int i=0; i<p.length-1; i++){
			for(int j=0; j<p.length-1; j++){
				System.out.print(s[i][j] + " ");
			}
			System.out.println();
		}
		print(s,0,p.length-2);
	}
}

最长公共子串:X = {x1,x2,^,xm} 和 Y = {y1,y2,^,ym} 最长的公共子串。例如 :“BCA”是X="ABCBDAB" Y="BDCABA"的公共子串,但不是最长的。

递归解:m[i][j]表示x的前i个数遇到y的前j个数是的最长子串长度。s[i][j]记录通过何种途径时x取到第i个数,y取到第j个数。

            m[i][j] = 0                                           i=0 || j=0;

                       = m[i-1][j-1] + 1                      x[i] = y[j];

                       = Max(m[i-1][j] , m[i][j-1])      x[i] != y[j];

            s[i][j] = 0       x[i-1] = y[j-1] ;            表示有公共元素加入,Zk-1是Xi-1和Yj-1的最长子串。

                     = 1       c[i-1][j] <= c[i][j-1] ;   表示X的前i个元素和Y的前j个组成的公共子集Zk蕴含Xi和Yj-1的一个最长子串。

                     = -1      c[i-1][j] <= c[i][j-1] ;   表示公共子集Zk蕴含Xi-1和Yj的一个最长子串。

实现:

public class LCS {
	/**
	 * @author ht
	 */
	public static void init(char[] x, char[] y, int[][] m, int[][] s){
		int l = x.length;
		int w = y.length;
		for(int i=0; i<=l; i++){
			m[i][0] = 0;
			s[i][0] = 0;
		}
		for(int j=0; j<=w; j++){
			m[0][j] = 0;
			s[0][j] = 0;
		}
		for(int i=1; i<=l; i++){
			for(int j=1; j<=w; j++){
				if(x[i-1] == y[j-1]){
					m[i][j] = m[i-1][j-1] + 1;
					s[i][j] = 0;
				} else {					
					if(m[i][j-1] > m[i-1][j]){
						m[i][j] = m[i][j-1];
						s[i][j] = -1;
					} else {
						m[i][j] = m[i-1][j];
						s[i][j] = 1;
					}
				}
			}
		}
	}
	
	public static void print(char[] x,int[][] s,int i, int j){
		if(i==0 || j==0) return;
		if(s[i][j] == 0){
			print(x,s,i-1,j-1);
			System.out.print(x[i-1] + " ");
		} else if(s[i][j] == 1){
			print(x,s,i-1,j);
		} else {
			print(x,s,i,j-1);
		}
	}
	
	public static void main(String[] args) {
		char[] y = "BDCABA".toCharArray();
		char[] x = "ABCBDAB".toCharArray();
		int[][] m = new int[x.length+1][y.length+1];
		int[][] s = new int[x.length+1][y.length+1];
		init(x,y,m,s);
		
		print(x,s,x.length,y.length);
		/*for(int i=0; i<m.length; i++){
			for(int j=0; j<m[i].length; j++){
				System.out.print(s[i][j] + " ");
			}
			System.out.println();
		}*/			
	}
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值