动态规划

       动态规划法的定义:在求解问题中,对于每一步决策,列出各种可能的局部解,再依据某种判定条件,舍弃那些肯定不能得到最优解的局部解,在每一步都经过筛选,以每一步都是最优解来保证全局是最优解,这种求解方法称为动态规划法。    

        动态规划法所针对的问题有一个显著的特征,即它所对应的子问题树中的子问题呈现大量的重复。动态规划法的关键就在于,对于重复出现的子问题,只在第一次遇到时加以求解,并把答案保存起来,让以后再遇到时直接引用,不必重新求解。

       一般来说,适合于用动态规划法求解的问题具有以下特点: 
         1、可以划分成若干个阶段,问题的求解过程就是对若干个阶段的一系列决策过程。 

         2、每个阶段有若干个可能状态 。
         3、一个决策将你从一个阶段的一种状态带到下一个阶段的某种状态。

         4、在任一个阶段,最佳的决策序列和该阶段以前的决策无关。 
         5、各阶段状态之间的转换有明确定义的费用,而且在选择最佳决策时有递推关系(即动态转移方程)。

例题1

        给定有n个整数(可能有负整数)组成的序列(a1,a2,…,an),求该序列从第i个数字到第j个数字的和(i=1,2,…,n;j=1,2,…,n)的最大值,当所有整数均为负整数时,其最大字段和为0.


动态规划算法:

 
    public static void main(String[] args) {
       //测试
    	int[] arr = {2,-3,11,-9,13,-7};
        maxSumDp(arr);      
    }
    
    //动态规划算法
    public static void maxSumDp(int[] arr) { 
        int sum = 0, b = 0, n = arr.length, bestx = 0, besty = 0; 
        for (int i = 1; i <= n; i++) { 
            if (b > 0) { 
                b += arr[i - 1]; 
            } else { 
                b = arr[i - 1]; 
                bestx = i; 
            } 
            if (b > sum) { 
                sum = b; 
                besty = i; 
            } 
        } 
        System.out.println("动态规划算法最优值:" + sum); 
        System.out.println("最优解:" + bestx + "-->" + besty); 
    }   

对比简单的算法:

// 最大子段和简单算法 public static void maxSumSimp(int arr[], int bestx, int besty) { int n = arr.length, sum = 0; for (int i = 1; i <= n; i++) { int thissum = 0; for (int j = i; j <= n; j++) { thissum += arr[j - 1]; if (thissum > sum) { sum = thissum; bestx = i; besty = j; } } } System.out.println("简单算法最优值:" + sum); System.out.println("最优解:" + bestx + "-->" + besty); } 

显然动态规划的算法更好。

例题2

       某国为了防御敌国的导弹袭击,发展中一种导弹拦截系统。但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于等于前一发的高度。某天,雷达捕捉到敌国导弹来袭。由于该系统还在试用阶段,所以只用一套系统,因此有可能不能拦截所有的导弹。

输入

第一行输入测试数据组数N(1<=N<=10)
接下来一行输入这组测试数据共有多少个导弹m(1<=m<=20)
接下来行输入导弹依次飞来的高度,所有高度值均是大于0的正整数

输出 输出最多能拦截的导弹数目
样例输入

2
8
389 207 155 300 299 170 158 65
3
88 34 65

样例输出

6
2
public static void main(String[] args) {
		Scanner scan = new Scanner(System.in);
		//测试数组组数
		int testArr = scan.nextInt();
		//数据个数
		int m = scan.nextInt();
		//保存导弹的高度
		int[] daodan = new int[m];
		while (testArr-- != 0) {
			//输入导弹的高度
			for (int i = 0; i < m; i++) {
				daodan[i] = scan.nextInt();
			}
			//计算拦截导弹的最大数
			int sum = 0;
			//存储下标为i的最大递减序列
			int[] b = new int[m];
			b[0] = 1;
			//这里i从1开始,比较dandao[1]和dandao[0]
			for (int i = 1; i < m; i++) {
				b[i] = 1;
				for (int j = 0; j < i; j++) {
					if (daodan[i] < daodan[j]) {
						b[i] = b[i] < b[j] + 1 ? b[i] + 1 : b[i];						
					}
				}
				sum = sum<b[i]?b[i]:sum;
			}
			System.out.println(sum);
		}
	}
    //以数据 389,207,155,300,299,170,158,65为例,每一次循环b[i]的情况
	//       b[0] b[1] b[2] b[3] b[4] b[5] b[6] b[7]
	//i=1      1    2 
	//i=2      1    2    3
	//i=3      1    2    3    2
	//i=4      1    2    3    2    3    
	//i=5      1    2    3    2    3    4
	//i=6      1    2    3    2    3    4   5 
	//i=7      1    2    3    2    3    4   5    6	

例题3(双线动态规划)

传纸条

问题描述
  小渊和小轩是好朋友也是同班同学,他们在一起总有谈不完的话题。一次素质拓展活动中,班上同学安排做成一个m行n列的矩阵,而小渊和小轩被安排在矩阵对角线的两端,因此,他们就无法直接交谈了。幸运的是,他们可以通过传纸条来进行交流。纸条要经由许多同学传到对方手里,小渊坐在矩阵的左上角,座标(1,1),小轩坐在矩阵的右下角,座标(m,n)。从小渊传到小轩的纸条只可以向下或者向右传递,从小轩传给小渊的纸条只可以向上或者向左传递。
  在活动进行中,小渊希望给小轩传递一张纸条,同时希望小轩给他回复。班里每个同学都可以帮他们传递,但只会帮他们一次,也就是说如果此人在小渊递给小轩纸条的时候帮忙,那么在小轩递给小渊的时候就不会再帮忙。反之亦然。
  还有一件事情需要注意,全班每个同学愿意帮忙的好感度有高有低(注意:小渊和小轩的好心程度没有定义,输入时用0表示),可以用一个0-100的自然数来表示,数越大表示越好心。小渊和小轩希望尽可能找好心程度高的同学来帮忙传纸条,即找到来回两条传递路径,使得这两条路径上同学的好心程度只和最大。现在,请你帮助小渊和小轩找到这样的两条路径。

输入格式
  输入第一行有2个用空格隔开的整数m和n,表示班里有m行n列(1<=m,n<=50)。
  接下来的m行是一个m*n的矩阵,矩阵中第i行j列的整数表示坐在第i行j列的学生的好心程度。每行的n个整数之间用空格隔开。

输出格式
  输出一行,包含一个整数,表示来回两条路上参与传递纸条的学生的好心程度之和的最大值。

样例输入
3 3
0 3 9
2 8 5
5 7 0

样例输出
34
import java.util.Scanner;

public class Test {

	public static void main(String[] args) {
		Scanner scan = new Scanner(System.in);
		int m = scan.nextInt();
		int n = scan.nextInt();
		//a数组表示同学的好心度
		int[][] a = new int[m + 1][m + 1];
		int k = m + n - 1;
		//三维b数组中,第一个表示k第几步,在(1,1)时,k表示1,第二个m表示
		//纸条传到第几行,第二个m表示第二个纸条传到第几行
		//之所以k+1,m+1是方便计算
		//应为K,m确定所以纸条的纵座标也确认为k-m+1
		int[][][] b = new int[k + 1][m + 1][m + 1];
        //输入好心度
		for (int i = 1; i <= m; i++) {
			for (int j = 1; j <= n; j++) {
				a[i][j] = scan.nextInt();
			}
		}
        
		for (int h = 2; h <= m + n - 1; h++) {  //h从2开始是因为,h=1时,纸条处在(1,1)
			int g = h > m ? m : h;        //应为i和j必须小于等于m,而当h小于m时,i和j必须小于等于k
			for (int i = 1; i <= g; i++) {
				for (int j = i; j <= g; j++) {
					if (i == j && h < m + n - 1) { //判断是否交叉,后面h<m+n-1是因为到最后一步必定交叉在一点
						continue;
					}
					int max = b[h - 1][i][j];  //令(右移,右移)最大
					if (b[h - 1][i - 1][j] > max) {//与(下移,右移)比较谁大
						max = b[h - 1][i - 1][j];
					}
					if (b[h - 1][i - 1][j - 1] > max) {//与(下移,下移)比较谁大
						max = b[h - 1][i - 1][j - 1];
					}
					if (b[h - 1][i][j - 1] > max) {//与(右移,下移)比较谁大
						max = b[h - 1][i][j - 1];
					}
					//由h和m确定出来的纵座标必须小于等于n
					if (h - i + 1 <= n && h - j + 1 <= n)
						b[h][i][j] = max + a[i][h - i + 1] + a[j][h - j + 1];

				}
			}
		}
		//输出结果
		System.out.println(b[k][m][m]);
	}
}

 


点赞