• Java算法


    一、递归与循环

    • 理论上任何的循环都可以改写为递归的形式。
      • 有时候因为栈的限制,需要“尾递归”(C可以用goto语句模拟尾递归);
      • java不支持尾递归。
    • 有些语言没有循环语句,只能使用递归(LISP)。

    循环改递归的关键

    • 发现循环的逻辑相似性。
    • 不要忘记递归“出口”。

    以下是一个简单循环改造成递归的例子:

     1 /**
     2  * 采用循环的方式打印0~n
     3  */
     4 private static void print1(int n) {
     5 
     6     for (int i = 0; i <= n; i++)
     7         System.out.print(i + "	");
     8 }
     9 
    10 /**
    11  * 打印从0到n【第一种递归方式】
    12  */
    13 private static void print2(int n) {
    14 
    15     if (n > 0){
    16         print2(n - 1); // 打印0~n-1
    17     }
    18     System.out.print(n + "	"); // 打印n
    19 }
    20 
    21 /**
    22  * 从begin打印到end【第二种递归方式】
    23  */
    24 private static void print3(int start, int end) {
    25 
    26     if (start > end)
    27         return;
    28     System.out.print(start + "	");    // 打印start
    29     print3(start + 1, end);            // 打印start+1~end
    30 }

      在第一种递归方式中,我们实际上应用到了“分治算法”,将问题分解为两部分:①打印0~n-1(递归处理);②打印n(可以直接处理),先是处理的n;思考:是否可以先处理前面的内容?如果递归方法的参数仍然只有一个参数:我们首先应该处理打印0,由于没有一个相似的结构,我们无法进行下一次的递归操作——我们可以给打印方法增加一个参数(让它从start打印到end),那么该问题就可以分解为:①打印start(直接处理);②打印start+1~end(递归处理)。

      关于递归出口处理

      我们可以在满足条件的时候递归(以上代码的第一种递归方式),或者在不满足条件的时候使用return语句(以上代码的第二种递归方式)。

     构造相似性

      如果没有相似性,需要主动构造;不能相似的原因很可能是缺少参数;递归和数学上的递推公式很相似。

      以下是一个新的循环改造成递归的例子(实现对数组元素的求和)。

     1 /**
     2  * 利用循环取得数组元素的累加和
     3  */
     4 public static int addAll1(int[] arr){
     5     
     6     int sum = 0;
     7     for (int i = 0; i < arr.length; i++)
     8         sum += arr[i];
     9     return sum;
    10 }
    11 
    12 /**
    13  *求arr数组中从第start项到数组最后的元素和
    14  */
    15 public static int addAll2(int[] arr,int start){
    16     
    17     if (start==arr.length)  // 递归出口,刚刚越界的时候返回0
    18         return 0;
    19     return arr[start] + addAll2(arr, start+1);
    20 }
    21 
    22 public static int addAll3(int[] arr,int end){
    23     
    24     if (end==-1) 
    25         return 0;
    26     return addAll3(arr, end-1) + arr[end];
    27 }
    28 
    29 /**
    30  * 将问题划分两个子问题,整个数组求和等于前面的一半元素额和加上后面一半元素的和
    31  */
    32 public static int addAll4(int[] arr,int start,int end){
    33     
    34     int mid = (start + end) / 2;
    35     if (start == end) {
    36         return arr[start]; // 递归出口,此时的start=end
    37     }
    38     return addAll4(arr, start, mid) + addAll4(arr, mid + 1, end);
    39 }

      实例三:不调用javaAPI的equals()方法比较两个字符串是否相等。

    1 public static boolean isSameString(String str1,String str2){
    2     
    3     if (str1.length()!=str2.length()) 
    4         return false;
    5     if (str1.length()==0) // 这里是需要判断str1,因为如果str1和str2的长度不相等在前面就已经返回了
    6         return true;      // 这个是递归出口,空字符串
    7     return str1.charAt(0)==str2.charAt(0) && isSameString(str1.substring(1), str2.substring(1));
    8 }

      1.先比较两个字符串的长度,长度不相等直接返回false,否则跳到步骤二;

      2.比较两个字符串的首字符是否相等,如果不相等返回false,否则执行步骤三;

      3.递归比较比较两个新的字符串(重复步骤1~3)是否相等(新字符串是原来的字符串去掉首字符)。

      递归出口:两个字符串为空串时返回true。

    递归调用

    • 递归调用仅仅是被调函数恰好为主调函数;
    • 注意每次调用的层次不同;
    • 注意每次分配的形参并不是同一个变量;
    • 注意返回的次序。

     经典的递归问题

    问题一:在n个球中,任意取出m个(不放回),有多少种不同的取法?

      算法思想:分治。假设在n个球中有一个幸运球,我们要取出m个球有2种取法:①取出此幸运球,再从n-1个球中取出m-1个球;②不取出此幸运球(此幸运球被排除在外),我们需要从n-1个球中取出m个球。

     1 /**
     2  * 从n个球中取出m个球
     3  * 算法思想:分治
     4  * @param n
     5  * @param m
     6  * @return 取出方法的个数
     7  */
     8 private static int getBall(int n,int m){
     9     
    10     /* 注意以下3句话的顺序 */
    11     if(n < m) return 0;        // 从4个球中取出5个球(无解)
    12     if(n == m) return 1;    // 从3个球中取出3个球(1种)
    13     if(m == 0) return 1;    // 从一定个数的球中取出0个球只有1种方式(不取)
    14     
    15     return getBall(n - 1, m - 1) + getBall(n - 1, m);
    16 }

       以上的这种算法我们叫做“平地起风雷”。

    问题二:生成n个元素的全排列(例如abc的全排列有6种:abc、acb、bac、bca、cab、cba)。

      利用减治的思想,我们可以看到每个元素都可以放到第一个位置,然后将剩下的2个元素进行全排列——原来规模为n的问题划分成了一个退化的子问题和一个规模为n-1的子问题!

     1 /**
     2  * 打印数组中元素的全排列
     3  * @param data
     4  * @param current 当前的交换位置(关注点),与其后的元素进行交换
     5  */
     6 private static void printAllAssignment(char[] data, int current) {
     7 
     8     if(current == data.length){
     9         for (int i = 0; i < data.length; i++) {
    10             System.out.print(data[i] + " ");
    11         }
    12         System.out.println();
    13     }
    14     
    15     for (int i = current; i < data.length; i++) {
    16         // 试探(交换)
    17         swap(data, current, i);
    18         // 递归
    19         printAllAssignment(data, current + 1);
    20         // 回溯(换回来)
    21         swap(data, current, i);
    22     }
    23 }
    24 
    25 /**
    26  * 交换数组中指定索引的元素
    27  * @param data
    28  * @param pos1
    29  * @param pos2
    30  */
    31 public static void swap(char[] data,int pos1,int pos2){
    32     
    33     char temp = data[pos1];
    34     data[pos1] = data[pos2];
    35     data[pos2] = temp;
    36 }

      以上算法中的回溯就是回复到初始状态。

    问题三:求2个串的最长公共子序列(CLS)的长度。串“abcdef”的子序列定义为从串的左边向右扫描,任意取出的字符构成的串,例如abc、acd、c等,注意:子串的定义比子序列严格(子串必须要连着)。abc和xbacd的最长公共子序列就是bc。

      上面问题的基本思路就是利用减治的思想,先取得2个字符串的头元素,如果头元素相同,则最长公共子序列的长度就是1+将两个字符串的截短1之后的最长公共子序列的长度。如果两个元素的头元素不相同,则最长公共子序列的解就是两个最长公共子序列解的最大值。

    /**
     * 求2个字符串的最长公共子序列的长度
     * @param str1
     * @param str2
     * @return
     */
    public static int cls(String str1,String str2){
        
        // 递归基
        if (str1.length() == 0 || str2.length() == 0) {
            return 0;
        }
    
        // 头元素是否相同
        if (str1.charAt(0) == str2.charAt(0)) {
            return cls(str1.substring(1), str2.substring(1)) + 1;
        } else {
            return Math.max(cls(str1, str2.substring(1)),cls(str1.substring(1), str2));
        }
    }

    问题四:求一个字符串的反转字符串。

    /**
     * 求一个字符串的反转字符串
     * @param str
     * @return
     */
    public static String getReverseString(String str) {
    
        return str.length() < 1 ? str : getReverseString(str.substring(1)) + str.charAt(0);
    }

      同样利用减治的思想我们可以将一反转之后的字符串想象成长度为n-1的反转字符串和字符串的第一个字符进行反转操作,递归的出口就是子串的长度小于1.

    问题五:打印杨辉三角。

     1 public static void main(String[] args) {
     2     
     3     for(int i = 0;i <= 5;i++){
     4         
     5         printPascalTriangle(i);
     6         System.out.println();
     7     }
     8 }
     9 
    10 /**
    11  * 打印杨辉三角第level层的元素
    12  * @param level 三角形层数
    13  */
    14 public static void printPascalTriangle(int level){
    15     
    16     for(int i = 0;i <= level;i++){
    17         System.out.print(getElement(level,i) + " ");
    18     }
    19     
    20 }
    21 
    22 /**
    23  * 取得杨辉三角的第level层的第i个元素
    24  * @param level
    25  * @param i
    26  * @return
    27  */
    28 private static int getElement(int level, int i) {
    29     
    30     if(i == 0 || i == level) return 1;
    31     
    32     return getElement(level - 1, i) + getElement(level - 1, i - 1);
    33 }

      上面的if判断分别是该层的第一个元素和最后一个元素。

    问题五:整数划分问题。例如:6 = 6 = 5+1 = 4+2 = 4+1+1 = 3+3 = 3 + 2 + 1 = 3+1+1+1 = …… = 1+1+1+1+1+1.

     1 /**
     2  * 打印整数的加法划分
     3  * @param number 整数
     4  * @param arr 缓存,临时存储
     5  * @param pos 当前的位置(调用的时候传入0)
     6  */
     7 public static void printIntegerDivide(int number,int[] arr,int pos){
     8     
     9     // 递归出口
    10     if(number == 0){
    11         for (int i = 0; i < pos; i++) {
    12             System.out.print(arr[i] + " ");
    13         }
    14         System.out.println();
    15         return;
    16     }
    17     
    18     for(int i = number ; i > 0;i--){
    19         if(pos > 0 && i > arr[pos -1]) continue; // 不允许出现后一项比前一项大的情况
    20         arr[pos] = i;
    21         printIntegerDivide(number-i, arr, pos + 1);
    22     }
    23 }
  • 相关阅读:
    Android在onCreate()中获得控件尺寸
    Java根据出生年月日获取到当前日期的年月日
    Oracle 取随机数
    filter,orderBy等过滤器
    ng-repeat 指令
    三元表达式、逻辑表达式 与 &&、||的妙用
    ng-switch 指令
    sublime text 3.0 安装 HTML-CSS-JS Prettify
    JS相关环境搭建:Nodejs、karma测试框架、jsDuck、Express
    关于VSS上的项目源码管理的注意问题
  • 原文地址:https://www.cnblogs.com/happyfans/p/4441175.html
Copyright © 2020-2023  润新知