zoukankan      html  css  js  c++  java
  • 左神算法第八节课:介绍递归和动态规划(汉诺塔问题;打印字符串的全部子序列含空;打印字符串的全排列,无重复排列;母牛数量;递归栈;数组的最小路径和;数组累加和问题,一定条件下最大值问题(01背包))

    暴力递归:

    1,把问题转化为规模缩小了的同类问题的子问题

    2,有明确的不需要继续进行递归的条件(base case)

    3,有当得到了子问题的结果之后的决策过程

    4,不记录每一个子问题的解

    动态规划

    1,从暴力递归中来

    2,将每一个子问题的解记录下来,避免重复计算

    3,把暴力递归的过程,抽象成了状态表达

    4,并且存在化简状态表达,使其更加简洁的可能

    一:递归

    1. 汉诺塔问题

    汉诺塔问题(不能大压小,只能小压大),打印n层汉诺塔从最左边移动到最右边的全部过程。

    左中右另称为 from、to、help。

    划分子问题:

    1、先把1~n-1从from移动到help;

    2、把单独的n移动到to;

    3、1~n-1从help移动到to;

    时间复杂度就是:T(n) = T(n-1) + 1 + T(n-1) = 2T(n-1)+1(一个等比公式)。T(n-1)是移动到help;1是从from直接移动到to;T(n-1)是把全部n-1挪回去。总的步数是2的N次方减一。

    2. 打印一个字符串的全部子序列,包括空字符串

    例如:一个字符串awbcdewgh

    他的子串:awbc,awbcd,wbcde...很多个子串,但是都是连续在一起

    他的子序列:abc,abcd,abcde...很多个子序列,但是子序列中的字符在字符串中不一定是连在一起的

    所以 子串!=子序列

    思路:穷举,例如“abc”,一开始返回串res是空字符串,经过第0次,产生有’a’字符的路径和没有’a’字符的路径,经过第1次时,也决定是否有‘b’字符的路径,依次往复,列举所有路径。

     1 public class Code_03_Print_All_Subsquences {
     2 
     3     public static void printAllSubsquence2(String str) {
     4         char[] chs = str.toCharArray();
     5         process(chs,0);
     6     }
     7     
     8     private static void process(char[] chs, int i) {
     9         if (i == chs.length) {
    10             System.out.print(String.valueOf(chs)+"|");
    11             return;
    12         }
    13         process(chs, i+1);//有该字符
    14         char temp = chs[i];
    15         chs[i] = 0;//将该字符设置成null
    16         process(chs, i+1);
    17         chs[i] = temp;//复原该字符
    18     }
    19 
    20     public static void printAllSub1(char[] str, int i, String res) {
    21         if (i==str.length) {
    22             System.out.print(res+"|");
    23             return;
    24         }
    25 //        printAllSub1(str, i+1, res);放在这也可以
    26         //有该字符的路
    27         printAllSub1(str, i+1, res+String.valueOf(str[i]));
    28         //没有该字符的路
    29         printAllSub1(str, i+1, res);
    30     }
    31     
    32     private static void test() {
    33         char[] chs = {'a','b','c','d'};
    34         String res = "";
    35         System.out.print("printAllSub1:");
    36         printAllSub1(chs, 0, res);
    37         System.out.print("
    printAllSubsquence2:");
    38         printAllSubsquence2("abcd");
    39     }
    40     public static void main(String[] args) {
    41         test();
    42 
    43     }
    44 }

    结果:

    3. 打印一个字符串的全部排列,没有重复排列

                                                            

     1 /** 打印一个字符串的全部排列*/
     2 public class Code_04_Print_All_Permutations {
     3 
     4     public static void printAllPermutations1(String str) {
     5         char[] chs = str.toCharArray();
     6         process1(chs, 0);
     7     }
     8     
     9     public static void process1(char[] chs, int i) {
    10         if (i==chs.length) {
    11             System.out.println(String.valueOf(chs));
    12             return;
    13         }
    14         for (int j = i; j < chs.length; j++) {
    15             swap(chs,i,j);
    16             process1(chs, i+1);
    17             swap(chs, i, j);//要交换过来;
    18         }
    19     }
    20     public static void printAllPermutations2(String str) {
    21         char[] chs = str.toCharArray();
    22         process1(chs, 0);
    23     }
    24     
    25     private static void swap(char[] chs, int i, int j) {
    26         char temp = chs[i];
    27         chs[i] = chs[j];
    28         chs[j] = temp;
    29     }
    30     
    31     private static void test() {
    32         String test1 = "abc";
    33         printAllPermutations1(test1);
    34         System.out.println("======");
    35 //        printAllPermutations2(test1);
    36 //        System.out.println("======");        
    37     }
    38     public static void main(String[] args) {
    39         // TODO Auto-generated method stub
    40         test();
    41     }
    42 }

    4. 母牛数量

    母牛每年生一只母牛,新出生的母牛成长三年后也能每年生一只母牛,假设不会死。求N年后,母牛的数量。

     

     1 /*
     2  * 母牛每年生一只母牛,新出生的母牛成长三年后也能每年生一只母牛,假设不会死。
     3  * 求N年后,母牛的数量。
     4  * 可得每年的:1,2,3,4,6,9...
     5  * F(n) = F(n-1) + F(n-3)
     6  * 复杂度O(N)
     7  * 
     8  * 改进:矩阵运算O(logN)
     9  * 
    10  */
    11 public class Code_05_Cow {
    12 
    13     //递归
    14     public static int cowNumber1(int n) {
    15         if (n < 1) {
    16             return 0;
    17         }
    18         if (n == 1 || n == 2 || n == 3) {
    19             return n;
    20         }else {
    21             return cowNumber1(n-1)+cowNumber1(n-3);
    22         }        
    23     }
    24     //非递归
    25     public static int cowNumber2(int n) {
    26         if (n < 1) {
    27             return 0;
    28         }
    29         if (n == 1 || n == 2 || n == 3) {
    30             return n;
    31         }
    32         int cur = 3;
    33         int pre = 2;
    34         int prepre = 1;
    35         int temp1 = 0;
    36         int temp2 = 0;
    37         for (int i = 4; i <=n ; i++) {
    38             temp1 = cur;
    39             temp2 = pre;
    40             cur = cur + prepre;
    41             pre = temp1;
    42             prepre = temp2;
    43         }
    44         return cur;
    45     }
    46     private static void test() {
    47         int i = 10;
    48         System.out.print("cowNumber1:");
    49         System.out.println(cowNumber1(i));
    50         System.out.print("cowNumber2:");
    51         System.out.println(cowNumber2(i));
    52         
    53     }
    54     public static void main(String[] args) {
    55         test();
    56 
    57     }
    58 }

    5. 递归栈

     1 public class Code_06_ReverseStackUsingRecursive {
     2 
     3     public static void reverse(Stack<Integer> stack) {
     4         if (stack.isEmpty()) {
     5             return;
     6         }
     7         int temp = stack.pop();
     8         reverse(stack);
     9         stack.push(temp);
    10 
    11     }
    12     private static void test() {
    13         Stack<Integer> stack = new Stack<>();
    14         stack.add(1);
    15         stack.add(2);
    16         stack.add(3);
    17         stack.add(4);
    18         stack.add(5);
    19         stack.add(6);
    20         printStack(stack);
    21         reverse(stack);
    22         printStack(stack);
    23     }
    24     private static void printStack(Stack<Integer> stack) {
    25         for (Integer i : stack) {
    26             System.out.print(i+" ");
    27         }
    28         System.out.println();
    29     }
    30     public static void main(String[] args) {
    31         test();
    32 
    33     }
    34 }

    二:动态规划

    1. 数组的最小路径和

    给你一个二维数组,二维数组中的每个数都是正数,要求从左上角走到右下角,每一步只能向右或者向下。沿途经过的数字要累加起来。返回最小的路径和。

    解题思路:到达右下角时,返回右下角的值;如果到达最后一行,则只能往右走(返回该点处值+右走的值);如果到达最后一列,则只能往下走(返回该点处值+下走的值);计算向右走的总值(该点处值+右走的值),计算向下走的总值(该点处值+下走的值),返回比较值。问题划分为了:向下或者向右的结果,从中选最小的路径,就是最后的答案。

                                       

     1 //运用递归
     2 //从左上角到右下角寻找路径
     3     public static int minPath1(int[][] matrix, int i, int j) {
     4         if (i == matrix.length - 1 && j == matrix[0].length - 1) {
     5             return matrix[i][j];
     6         }
     7         if (i == matrix.length - 1) {//到最后一行
     8             return matrix[i][j] + minPath1(matrix, i, j + 1);
     9         }
    10         if (j == matrix[0].length - 1) {//到最后一列
    11             return matrix[i][j] + minPath1(matrix, i + 1, j);
    12         }
    13         int right = matrix[i][j] + minPath1(matrix, i, j + 1);
    14         int down = matrix[i][j] + minPath1(matrix, i + 1, j);
    15         return Math.min(right, down);
    16     }
    17     //从右下角到左上角寻找路径
    18     public static int minPath2(int[][] matrix) {
    19         return process2(matrix, matrix.length - 1, matrix[0].length - 1);
    20     }
    21     private static int process2(int[][] matrix, int rl, int cl) {
    22         if (rl == 0 && cl == 0) {
    23             return matrix[rl][cl];
    24         }
    25         if (rl == 0 && cl != 0) {//到第一行
    26             return matrix[rl][cl] + process2(matrix, rl, cl-1);
    27         }
    28         if (rl != 0 && cl == 0) {//到第一列
    29             return matrix[rl][cl] + process2(matrix, rl-1, cl);
    30         }        
    31 //        return matrix[rl][cl] + Math.min(process2(matrix, rl, cl-1), process2(matrix, rl-1, cl));
    32         //等同于以下三句
    33         int left = matrix[rl][cl] + process2(matrix, rl, cl-1);
    34         int up = matrix[rl][cl] + process2(matrix, rl-1, cl);
    35         return Math.min(left,up);
    36     }

    以上是递归,但是复杂度过高,暴力枚举有待优化:有大量的重复解产生,很多部分都重复计算。如果把重复计算的部分缓存起来,重复的时候直接调用就能省时间,这就是动态规划。如图,当计算到(0,1)位置时,需要计算(0,2)和(1,1),而在计算(1,0)时,需要计算(1,1)和(2,0),此时,(1,1)被重复计算。

    什么样的尝试版本递归可以改成动态规划?

    当把递归过程展开,发现有重复的状态,与到达它的路径是没有关系的,那么它一定能改成动态规划(无后效性问题)。就本题来说,当到达某点(x,y)时,不管是从上还是从左来的,对于自己来说,从自己到达最右下角的点的最短路径是固定不变的,而与到达(x,y)该处的来源是无关的。

    有后效性的是,汉罗塔、N皇后问题(前面的举动会影响后面的结果)。

     

    【暴力递归转动态规划过程】对于法1

    1. 先写出尝试版本;(以从左上角到右下角为例);
    2. 分析可变参数,那几个可变参数可以代表返回值的状态,可变参数是几维的,dp表就是几维的(该题是i和j二维表);
    3. 看看最终要的状态是哪一个,在表中点出来(该题是(0,0)位置,五角星处);
    4. 回到basecase中,把完全不依赖位置的值设置好(这题是最后一行和最后一列);basecase代表一个问题划分到什么程度就不用再划分了(该题是最右下角的位置)。
    5. 分析一个普遍位置需要哪些位置(该题需要右边的值和下边的值),然后逆着回去,就是填表的顺序。依次计算,推到顶部就是答案。像搭积木一样,堆积到一定条件就能出现答案。

                                    

     1 //动态规划
     2     public static int minPath3(int[][] matrix) {
     3         if (matrix==null || matrix.length == 0 
     4                 || matrix[0].length == 0 || matrix[0]==null) {
     5             return 0;
     6         }
     7         int row = matrix.length;
     8         int col = matrix[0].length;
     9         int[][] dp = new int[row][col];
    10         dp[0][0] = matrix[0][0];
    11         //先把dp表的边界先依次求和填充进去;
    12         for (int i = 1; i < row; i++) {
    13             dp[i][0] = dp[i-1][0] + matrix[i][0];
    14         }
    15         for (int i = 1; i < col; i++) {
    16             dp[0][i] = dp[0][i-1]+matrix[0][i];
    17         }
    18         //在填充里面的各项;挑选dp表中左边和上边两者中较小的加上matrix[i][j]当前位置,填充dp[i][j]位置;
    19         for (int i = 1; i < row; i++) {
    20             for (int j = 1; j < col; j++) {
    21                 dp[i][j] = Math.min(dp[i-1][j], dp[i][j-1]) + matrix[i][j];
    22             }
    23         }
    24         return dp[row-1][col-1];
    25     }

    2. 数组累加问题

    给你一个数组arr,和一个整数aim。如果可以任意选择arr中的数字,能不能累加得到aim,返回true或者false。

    思路:无后效性,类似于打印所有子序列,就是该位上数字加或不加;

    通过分析发现,arr[]和aim是不变的,而i和sum 是变化的,i的范围是数的个数,sum是所有的数的和

                                    

     1 public class Code_08_Money_Problem {
     2 
     3     public static boolean isSum(int[] arr, int i,int sum,int aim) {
     4         if (i == arr.length) {
     5             return sum == aim;
     6         }
     7         return isSum(arr, i+1, sum, aim) || isSum(arr, i+1, sum+arr[i], aim);
     8     }
     9     private static void test() {
    10         int[] arr = {1,4,8};
    11         int aim = 12;
    12         System.out.println(isSum(arr, 0, 0, aim));    
    13     }
    14     public static void main(String[] args) {
    15         test();
    16     }
    17 }

    如:arr[] = [3,2,5]

    Return  i+1, sum || i+1, sum+arr[i]

    推理:

    当(i,sum)时,需要的是(i+1,sum)和(i+1,sum+arr[i])

    当(2,0),需要的是(3,0)和(3,0+arr[2]),

    …像搭积木一样

    最后一行是多出来的存放结果,只有aim那一列上才

    是true,其他都是false。

    3. 一定条件下最大值问题

    给定两个数组w和v,两个数组长度相等,w[i]表示第i件商品的重量,v[i]表示第i件商品的价值。 再给定一个整数bag,要求你挑选商品的重量加起来一定不能超 过bag,返回满足这个条件下,你能获得的最大价值。

     1 public class Code_09_Knapsack {
     2 
     3     public static int maxValue1(int[] c, int[] p, int bag) {
     4         return process1(c, p, 0, 0, bag);
     5     }
     6 
     7     public static int process1(int[] weights, int[] values, int i, int alreadyweight, int bag) {
     8         if (alreadyweight > bag) {
     9             return 0;
    10         }
    11         if (i == weights.length) {
    12             return 0;
    13         }
    14         return Math.max(
    15                 
    16                 process1(weights, values, i + 1, alreadyweight, bag),
    17                 
    18                 values[i] + process1(weights, values, i + 1, alreadyweight + weights[i], bag));
    19     }
    20 
    21     public static int maxValue2(int[] c, int[] p, int bag) {
    22         int[][] dp = new int[c.length + 1][bag + 1];
    23         for (int i = c.length - 1; i >= 0; i--) {
    24             for (int j = bag; j >= 0; j--) {
    25                 dp[i][j] = dp[i + 1][j];
    26                 if (j + c[i] <= bag) {
    27                     dp[i][j] = Math.max(dp[i][j], p[i] + dp[i + 1][j + c[i]]);
    28                 }
    29             }
    30         }
    31         return dp[0][0];
    32     }
    33 
    34     public static void main(String[] args) {
    35         int[] c = { 3, 2, 4, 7 };
    36         int[] p = { 5, 6, 3, 19 };
    37         int bag = 11;
    38         System.out.println(maxValue1(c, p, bag));
    39         System.out.println(maxValue2(c, p, bag));
    40     }
    41 
    42 }
  • 相关阅读:
    ipmitool常用命令
    linux系统/var/log目录下的信息详解
    查看vnc server的日志
    Hp服务器 iLO3 使用方法
    phalcon builder 用法
    cobbler pxe-menu
    几种session存储方式比较
    parse arguments in bash
    No breeds found in the signature, a signature update is recommended
    Mybatis-plus使用分页进行分页查询
  • 原文地址:https://www.cnblogs.com/gjmhome/p/11370930.html
Copyright © 2011-2022 走看看