<导航

JAVA递归

  递归是一种应用非常广泛的算法(或者编程技巧)。递归求解问题的分解过程,去的过程叫“递”,回来的过程叫“归”。

  递归的基本思想就是“自己调用自己”,一个使用递归技术的方法将会直接或者间接的调用自己。

  递归需要满足的三个条件:

  1. 一个问题的解可以分解为几个子问题的解;

  2. 这个问题与分解之后的子问题,除了数据规模不同,求解思路完全一样;

  3. 存在递归终止条件。

  注意事项:
          1.要有出口,(是一个判断条件,一般要和我们if语句搭钩);
          2.次数不宜过多(因为方法调用要开栈,栈内存是有限的,很容易溢出);StackOverflowError:当应用程序递归太深而发生堆栈溢出时,抛出该错误。

    3.递归次数和计算机本身的硬件有关系,以及程序本身,

      •     计算机越好,次数越多
      •     程序越简单,次数越多

看几个实例:

用递归把目录下所有的目录及文件全部显示出来

public class B {
    public static void main(String[] args) {
        File file = new File("f:\\123");
        listAllFile(file);
    }

    public static void listAllFile(File file) {
        File[] strs = file.listFiles();
        for (int i = 0; i < strs.length; i++) {
            // 判断strs[i]是不是目录
            if (strs[i].isDirectory()) {
                listAllFile(strs[i]);//递归调用自己
                System.out.println("目录="+strs[i].getName());
            } else {
                System.out.println("文件名="+strs[i].getName());
            }
        }    
    }
}

递归算法的一般形式:

func( mode){
    if(endCondition){      //递归出口
          end;
    }else{
         func(mode_small)  //调用本身,递归
    }
}

利用递归删除多级目录结构

import java.io.File;

public class Demo {

    public static void deleteFile(File file){
        //1:判断目录是否存在
        if(file.exists()){
            //1:判断是否是文件夹
            if(file.isDirectory()){
                //2 获取文件数组
                File[] file2 = file.listFiles();
                //3 遍历文件夹
                for (File file3 : file2) {
                    //4:将遍历出来的文件以及目录传入方法中
                    deleteFile(file3);
                }
            }
            //直接删除文件
            file.delete();
            
        }
    }
    public static void main(String[] args) {
        //创建根目录结构的抽象路径
        File file = new File("a1");
        deleteFile(file);    
    }
}

部门树的递归

部门对象

package com.ytx.demo.tree;

import java.util.ArrayList;
import java.util.List;

public class Department {
private int id;
private String name;
private int parentId;
private List<Department> children = new ArrayList<Department>();

public Department(int id, String name, int parentId) {
    this.id = id;
    this.name = name;
    this.parentId = parentId;
}

public int getId() {
    return id;
}

public void setId(int id) {
    this.id = id;
}

public String getName() {
    return name;
}

public void setName(String name) {
    this.name = name;
}

public int getParentId() {
    return parentId;
}

public void setParentId(int parentId) {
    this.parentId = parentId;
}

public List<Department> getChildren() {
    return children;
}

public void setChildren(List<Department> children) {
    this.children = children;
}
}
View Code

递归实现

package com.ytx.demo.tree;

import java.util.ArrayList;
import java.util.List;

public class DempartmentThree {
public static void main(String[] args) {
    List<Department> departmentList = new ArrayList<>();
    departmentList.add(new Department(1, "研发部门", 0));
    departmentList.add(new Department(2, "研发团队1", 1));
    departmentList.add(new Department(3, "研发团队2", 1));
    departmentList.add(new Department(4, "财务部门", 0));
    departmentList.add(new Department(5, "财务A部门", 4));
    departmentList.add(new Department(6, "财务B部门", 4));
    departmentList.add(new Department(7, "财务A部门团队1", 5));
    departmentList.add(new Department(8, "财务A部门团队2", 5));
    departmentList.add(new Department(9, "财务B部门团队1", 6));
    departmentList.add(new Department(10, "财务B部门团队2", 6));

    List<Department> listTree = getThree(departmentList,0);
    System.out.println(listTree);
}

private static List<Department> getThree(List<Department> list,int parentId){
    //获取所有子节点
    List<Department> childTreeList = getChildTree(list,parentId);
    for (Department dept:childTreeList) {
        dept.setChildren(getThree(list,dept.getId()));
    }
    return childTreeList;
}

private static List<Department> getChildTree(List<Department> list,int id){
    List<Department> childTree = new ArrayList<>();
    for (Department dept:list) {
        if(dept.getParentId() == id){
            childTree.add(dept);
        }
    }
    return childTree;
}
}
View Code

求5的阶乘

例:        
    //求5的阶乘
    public class DiGeiDome {
        public static void main(String[] args) {
            int sum = getSum(5);
            System.out.println(sum);
        }
        
        public static int getSum(int n){//n=3
            if (n==1) {
                return 1;
            }else{
                return n*getSum(n-1);//3*getSum(2);getSum(2)=2*getSum(1)
            }
        }
    }

其他实例

package test.digui;  
  
/** 
 * 所谓递归就是自己调用自己,调用需要有一个出口,否则就成为死循环了。递归和for循环的主要区别是,递归的调用有自己的stack 
 * 而for的lOOP调用共享stack, 另外递归最里层的方法最先执行完成才逐渐返回执行外层的方法(类似于spring的拦截器模式) 
 *  
 * @author LXL 
 * 
 */  
public class TestDiGui {  
  
    public static void main(String[] args) {  
        int result = 0;  
        // result = getAddNum(10);  
        // System.out.println(result);  
        // decimalToBinary(10);  
        // System.out.println(sb.toString());  
//      gongyueshu(15, 21);  
        hanon(3,'a','b','c');  
  
    }  
  
    /** 
     * num以内的数字求和 
     *  
     * @param num 
     * @return 
     */  
    public static int getAddNum(int num) {  
        if (num == 0) {  
            return num;  
        } else  
            return num + getAddNum(num - 1);  
    }  
  
    static StringBuilder sb = new StringBuilder();  
  
    /** 
     * 十进制转成二进制 
     *  
     * @param num 
     */  
    public static void decimalToBinary(int num) {  
        if (num == 0) {  
            return;  
        } else {  
            decimalToBinary(num / 2);  
            // System.out.print(num%2);  
            sb.append(num % 2);  
        }  
    }  
  
    /** 
     * 求两个数的最大公约数 
     *  
     * @param num 
     */  
    public static void gongyueshu(int num1, int num2) {  
        if (num1 == num2) {  
            System.out.println(num1);  
        } else {  
            gongyueshu(abs(num1 - num2), min(num1, num2));  
        }  
    }  
  
    /** 
     * 求两个数的最小的数 
     *  
     * @param num 
     */  
    public static int min(int num1, int num2) {  
        return num1 > num2 ? num2 : num1;  
    }  
  
    /** 
     * 求绝对值 
     *  
     * @param num 
     */  
    public static int abs(int num) {  
        return num > 0 ? num : -num;  
    }  
  
    /** 
     * 递归方法hanon,求汉诺塔算法 
     *  
     * @param num 
     */  
    public static void hanon(int n, char a, char b, char c) {  
        if (n == 1) {  
            move(1, a, c);// 最后一种情况是,把A柱子上盘子移到C柱子上。  
            return;  
        }  
        hanon(n - 1, a, c, b); // 递归,把n-1个盘子从A 盘上借助C盘移到B盘上  
        move(n, a, c);// 调用move()方法  
        hanon(n - 1, b, a, c);// 递归,把把n-1个盘子从B盘上借助A盘移到C盘上  
    }  
  
    public static void move(int n, char a, char c) {  
        System.out.println(n + ":" + a + "-->" + c);// 打印移动盘子情况  
    }  
  
}
View Code

实例分析

  问题:N级台阶(比如100级),每次可走1步或者2步,求总共有多少种走法?

  分析:如果有大于2级的n级台阶,那么假如第一次跳一级台阶,剩下还有n-1级台阶,有f(n-1)种跳法,假如第一次条2级台阶,剩下n-2级台阶,有f(n-2)种跳法。这就表示f(n)=f(n-1)+f(n-2),即斐波那契数列。假设只有一个台阶,那么只有一种跳法,那就是一次跳一级,f(1)=1;如果有两个台阶,那么有两种跳法,第一种跳法是一次跳一级,第二种跳法是一次跳两级,f(2)=2。

编写递归代码的关键是,只要遇到递归,我们就把它抽象成一个递推公式,不用想一层层的调用关系,不要试图用人脑去分解递归的每个步骤。

递归代码要警惕重复计算

为了避免重复计算,我们可以通过一个数据结构(比如散列表)来保存已经求解过的 f(k)。

递归代码要警惕堆栈溢出

我们可以通过在代码中限制递归调用的最大深度的方式来解决这个问题,递归调用超过一定深度(比如 1000)之后,我们就不继续往下再递归了,直接抛出异常。

递归的return

递归中的return只是最里层结束,然后再按照进的步骤,一层层再执行一遍,直到最外层。

看个例子

public static void main(String[] args) {
        //递归
        recursion(1,10);
    }
 
    private static String recursion(int current, int total) {
        if (current > total) {//终结条件
 
            System.out.println("已经停止调用了!");
 
            //返回值
            return "已经停止调用了!";
 
        } else {//递归条件(current <= total)
 
            System.out.println("第" + current + "次调用!");
 
            //(坑)切记:此处一定要在方法前写上return!!!
            return recursion(++current, total);//current不断加1,调用自身
 
            //基本值就是最后一次调用自身时current的值
        }
    }

运行结果

递归一般都是使用if(){}else{}用return结束循环。使用递归能够用简短的代码完成需要多次重复的计算,大大减少了代码量,简而言之:用有限的语句定义对象的无限集合。

下面来说上面代码中提到的“坑”------ return

  场景描述:下图代码,我在递归方法里满足递归条件(current <= total)时,继续调用自身(recursion(++current,total)),在最后return,代码执行没有报错,但是发现System.out.println("我返回了")输出了11次!!!!!!后来发现,return的作用于是recursion这个方法,举例:当current=1的时候进recursion方法,此时走的是else里的代码,是继续调用了自己,并没有return。当current=2的时候进recursion方法,此时走的还是else里的代码,还是继续调用了自己,依然没有return......这样依次执行,当current=11的时候,走的是if(current > total)的代码,执行了“System.out.println("已经停止调用了!");” ,然后执行1次“return “返回值current=11”,但是此时对于current=10,current=9,current=8......时调用的recursion方法还都没有执行return,所以按照current=11.10.9.8......3.2的顺序又依次执行了10次“return '返回值current=11,10,9,8,7,6,5,4,3,2”,最后返回值current=2,明显返回值不是我们想要的,我们想要的返回值应该是“返回值current=11”。

下图为错误代码示例:

 

 

为避免这个return的坑,我们使用递归的时候要按照“正确代码实例”的方式去编写代码。

正确的代码示例:

 

 

 

 

参考文章:

https://www.liangzl.com/get-article-detail-144079.html

https://www.iteye.com/blog/sky-xin-2297246

https://www.jianshu.com/p/3b0b92da124c

https://blog.csdn.net/peacock__/article/details/88064442

posted @ 2020-01-22 14:55  字节悦动  阅读(480)  评论(0)    收藏  举报