Java的递归算法详解

目录
  • 一、介绍
    • 1、介绍
    • 2、案例
  • 二、迷宫问题
  • 三、八皇后问题
  • 四、汉诺塔问题
    • 1、问题
    • 2、思想
    • 3、代码
  • 总结

一、介绍

1、介绍

递归:递归就是方法自己调用自己,每次调用时传入不同的变量。递归有助于编程者解决复杂的问题,同时可以让代码变得简洁。

迭代和递归区别:迭代使用的是循环结构,递归使用的选择结构。使用递归能使程序的结构更清晰、更简洁、更容易让人理解,从而减少读懂代码的时间。其时间复杂度就是递归的次数。

但大量的递归调用会建立函数的副本,会消耗大量的时间和内存,而迭代则不需要此种付出。

递归函数分为调用和回退阶段,递归的回退顺序是它调用顺序的逆序。

分治:当一个问题规模较大且不易求解的时候,就可以考虑将问题分成几个小的模块,逐一解决。

2、案例

  • 兔子繁殖的问题。(斐波那契数列)。
  • 计算 n! 。
  • 任意长度的字符串反向输出。
  • 折半查找算法的递归实现。
  • 汉诺塔问题
  • 八皇后问题

二、迷宫问题

问题:寻找一条从起始点到达终点的有效路径。

代码示例:迷宫

public class MiGong {
    /**
     * 0:该点没有走过, 1:表示墙, 2:可以走, 3:该点已经走过,但是走不通\
     * 策略: 下->右->上->左, 如果该点走不通,再回溯
     */
    private int[][] map;
    private int desX;
    private int desY;
    /**
     * 构建 row*col的迷宫
     *
     * @param row 行
     * @param col 列
     */
    public MiGong(int row, int col) {
        if (row <= 0 || col <= 0) {
            return;
        }
        map = new int[row][col];
        // 默认 上下左右 全部为墙
        for (int i = 0; i < col; i++) {
            map[0][i] = 1;
            map[row - 1][i] = 1;
        }
        for (int i = 0; i < row; i++) {
            map[i][0] = 1;
            map[i][col - 1] = 1;
        }
    }
    /**
     * 在迷宫内部添加挡板
     *
     * @param i 横坐标
     * @param j 纵坐标
     */
    public void addBaffle(int i, int j) {
        if (map == null) {
            return;
        }
        // 外面一周都是墙
        if (i > 0 && i < map.length - 1 && j > 0 && j < map[0].length - 1) {
            map[i][j] = 1;
        }
    }
    /**
     * 设置迷宫的终点位置
     *
     * @param desX 横坐标
     * @param desY 纵坐标
     */
    public void setDes(int desX, int desY) {
        this.desX = desX;
        this.desY = desY;
    }
    public boolean setWay(int i, int j) {
        // 通路已经找到
        if (map[desX][desY] == 2) {
            return true;
        } else {
            if (map[i][j] != 0) {
                return false;
            }
            // map[i][j] == 0 按照策略 下->右->上->左 递归
            // 假定该点是可以走通.
            map[i][j] = 2;
            if (setWay(i + 1, j)) {
                return true;
            } else if (setWay(i, j + 1)) {
                return true;
            } else if (setWay(i - 1, j)) {
                return true;
            } else if (setWay(i, j - 1)) {
                return true;
            } else {
                // 说明该点是走不通,是死路
                map[i][j] = 3;
                return false;
            }
        }
    }
    // 显示地图
    public void show() {
        for (int i = 0; i < map.length; i++) {
            for (int j = 0; j < map[0].length; j++) {
                System.out.print(map[i][j] + " ");
            }
            System.out.println();
        }
    }
}

  代码示例:测试类

// 测试类
public class Main {
    public static void main(String[] args) {
        MiGong miGong = new MiGong(8, 7);
        miGong.addBaffle(3, 1);
        miGong.addBaffle(3, 2);
        miGong.setDes(6, 5); // 设置目的地
        System.out.println("初始地图的情况");
        miGong.show();
        miGong.setWay(1, 1); // 设置起始位置
        System.out.println("小球走过的路径,地图的情况");
        miGong.show();
    }
}

// 结果
初始地图的情况
1 1 1 1 1 1 1
1 0 0 0 0 0 1
1 0 0 0 0 0 1
1 1 1 0 0 0 1
1 0 0 0 0 0 1
1 0 0 0 0 0 1
1 0 0 0 0 0 1
1 1 1 1 1 1 1
小球走过的路径,地图的情况
1 1 1 1 1 1 1
1 2 0 0 0 0 1
1 2 2 2 0 0 1
1 1 1 2 0 0 1
1 0 0 2 0 0 1
1 0 0 2 0 0 1
1 0 0 2 2 2 1
1 1 1 1 1 1 1

三、八皇后问题

问题:在8×8格的国际象棋上摆放八个皇后,使其不能互相攻击,即:任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。

代码示例:八皇后

public class Queue8 {
    private static final int MAX = 8;
    // 保存皇后放置的位置,比如 arr = {0, 4, 7, 5, 2, 6, 1, 3}
    private final int[] array = new int[MAX];
    public static int count = 0;
    public static int judgeCount = 0;
    public void check() {
        this.check(0);
    }
    // check 是每一次递归时,进入到check中都有 for(int i = 0; i < max; i++),因此会有回溯
    private void check(int n) {
        // n = 8, 表示8个皇后就已经放好
        if (n == MAX) {
            print();
            return;
        }
        for (int i = 0; i < MAX; i++) {
            array[n] = i;

            // 判断当放置第n个皇后到i列时,是否冲突
            // 不冲突
            if (!judge(n)) {
                // 接着放n+1个皇后,即开始递归
                check(n + 1);
            }
        }
    }
    private boolean judge(int n) {
        judgeCount++;
        for (int i = 0; i < n; i++) {
            // 同一列 或 同一斜线
            if (array[i] == array[n] || Math.abs(n - i) == Math.abs(array[n] - array[i])) {
                return true;
            }
        }
        return false;
    }
    private void print() {
        count++;
        for (int i = 0; i < array.length; i++) {
            System.out.print(array[i] + " ");
        }
        System.out.println();
    }

}

代码示例:测试类

// 测试类
public class Main {
    public static void main(String[] args) {
        Queue8 queue8 = new Queue8();
        queue8.check();
        System.out.printf("一共有%d解法", Queue8.count);
        System.out.printf("一共判断冲突的次数%d次", Queue8.judgeCount); // 1.5w
    }
}

四、汉诺塔问题

1、问题

2、思想

如果 n = 1,A -> C

如果 n >= 2,总是看做是两个盘,①最下边的盘。②上面所有的盘。则,步骤:

(1)先把上面所有的盘 A->B

(2)把最下边的盘 A->C

(3)把 B 塔的所有盘 从 B->C

3、代码

代码示例:汉诺塔问题

// 汉诺塔
public class Hanoitower {
    // 使用分治算法
    public static void move(int num, char a, char b, char c) {
        // 如果只有一个盘
        if (num == 1) {
            System.out.println("第1个盘从 " + a + "->" + c);
        } else {
            // n >= 2,总是看做是两个盘,①最下边的盘。②上面所有的盘。则,步骤:
            // 1.先把上面所有的盘 A->B.移动过程会使用到 c
            move(num - 1, a, c, b);
            // 2.把最下边的盘 A->C
            System.out.println("第" + num + "个盘从 " + a + "->" + c);
            // 3.把 B 塔的所有盘 从 B->C.移动过程会使用到 a
            move(num - 1, b, a, c);
        }
    }
}

代码示例:测试类

// 测试类
public class Main {
    public static void main(String[] args) {
        Hanoitower.move(3, 'A', 'B', 'C');
    }
}

// 结果
第1个盘从 A->C
第2个盘从 A->B
第1个盘从 C->B
第3个盘从 A->C
第1个盘从 B->A
第2个盘从 B->C
第1个盘从 A->C

总结

本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注我们的更多内容!

(0)

相关推荐

  • java递归算法实例分析

    递归算法设计的基本思想是: 对于一个复杂的问题,把原问题分解为若干个相对简单类同的子问题,继续下去直到子问题简单到能够直接求解,也就是说到了递推的出口,这样原问题就有递推得解. 在做递归算法的时候,一定要把握住出口,也就是做递归算法必须要有一个明确的递归结束条件.这一点是非常重要的.其实这个出口是非常好理解的,就是一个条件,当满足了这个条件的时候我们就不再递归了. 关键要抓住的是: (1)递归出口 (2)地推逐步向出口逼近 递归就是方法自身调用自身的行为,注意要写好递归头,也就是什么时候退出递归

  • java递归算法的实例详解

    递归三要素: 1.明确递归终止条件: 2.给出递归终止时的处理办法: 3.提取重复的逻辑,缩小问题规模. 1.1+2+3+-+n import java.util.Scanner; public class Recursion { public static void main(String[] args) { Scanner in = new Scanner(System.in); int n = in.nextInt(); System.out.println(sum(n)); } publ

  • JAVA用递归实现全排列算法的示例代码

    求一个n阶行列式,一个比较简单的方法就是使用全排列的方法,那么简述以下全排列算法的递归实现. 首先举一个简单的例子说明算法的原理,既然是递归,首先说明一下出口条件.以[1, 2]为例 首先展示一下主要代码(完整代码在后面),然后简述 //对数组array从索引为start到最后的元素进行全排列 public void perm(int[]array,int start) { if(start==array.length) { //出口条件 for(int i=0;i<array.length;i

  • java利用递归算法实现对文件夹的删除功能

    前提: 集成开发环境(IDE):eclipse jdk版本:8.0 File类的几个方法: 1)isFile() 测试此抽象路径名表示的文件是否为普通文件. 2)list() 返回一个字符串数组,命名由此抽象路径名表示的目录中的文件和目录. 3)delete() 删除由此抽象路径名表示的文件或目录. 4)listFiles() 返回一个抽象路径名数组,表示由该抽象路径名表示的目录中的文件. File类的一个属性: separator 与系统相关的默认名称 - 分隔符字符,以方便的方式表示为字符串

  • Java求解两个非负整数最大公约数算法【循环法与递归法】

    本文实例讲述了Java求解两个非负整数最大公约数算法.分享给大家供大家参考,具体如下: 代码功能: 1.Java实现(完整源码附测试用例): 2.求解两个非负整数p,q(p>=q)的最大公约数: 3.循环法 以及 递归法两种求解思路: 完整源码: /* GCD:Greateast Common Divisor */ public class GCD{ public static void main(String args[]){ /* Test Case */ int p = 32; int q

  • Java利用递归算法实现查询斐波那契数

    package 斐波那契数; import java.util.Scanner; class 斐波那契数 { public static void main(String[] args) { System.out.println("请输入想查询的第几个斐波拉楔数"); long n = new Scanner(System.in).nextLong(); System.out.println(f(n)); } private static int f(long n) { if(n==1

  • Java的递归算法详解

    目录 一.介绍 1.介绍 2.案例 二.迷宫问题 三.八皇后问题 四.汉诺塔问题 1.问题 2.思想 3.代码 总结 一.介绍 1.介绍 递归:递归就是方法自己调用自己,每次调用时传入不同的变量.递归有助于编程者解决复杂的问题,同时可以让代码变得简洁. 迭代和递归区别:迭代使用的是循环结构,递归使用的选择结构.使用递归能使程序的结构更清晰.更简洁.更容易让人理解,从而减少读懂代码的时间.其时间复杂度就是递归的次数. 但大量的递归调用会建立函数的副本,会消耗大量的时间和内存,而迭代则不需要此种付出

  • Java递归算法详解(动力节点整理)

    递归算法是一种直接或者间接调用自身函数或者方法的算法.Java递归算法是基于Java语言实现的递归算法.递归算法的实质是把问题分解成规模缩小的同类问题的子问题,然后递归调用方法来表示问题的解.递归算法对解决一大类问题很有效,它可以使算法简洁和易于理解.    递归算法解决问题的特点: 1)递归就是方法里调用自身.  2) 在使用递增归策略时,必须有一个明确的递归结束条件,称为递归出口.  3)递归算法解题通常显得很简洁,但递归算法解题的运行效率较低.所以一般不提倡用递归算法设计程序.  4)在递

  • Java 数据库连接池详解及简单实例

    Java 数据库连接池详解 数据库连接池的原理是: 连接池基本的思想是在系统初始化的时候,将数据库连接作为对象存储在内存中,当用户需要访问数据库时,并非建立一个新的连接,而是从连接池中取出一个已建立的空闲连接对象.使用完毕后,用户也并非将连接关闭,而是将连接放回连接池中,以供下一个请求访问使用.而连接的建立.断开都由连接池自身来管理.同时,还可以通过设置连接池的参数来控制连接池中的初始连接数.连接的上下限数以及每个连接的最大使用次数.最大空闲时间等等.也可以通过其自身的管理机制来监视数据库连接的

  • Java 回调函数详解及使用

    Java 回调函数详解 前言: C语言中回调函数解释: 回调函数(Callback Function)是怎样一种函数呢? 函数是用来被调用的,我们调用函数的方法有两种: 直接调用:在函数A的函数体里通过书写函数B的函数名来调用之,使内存中对应函数B的代码得以执行.这里,A称为"主叫函数"(Caller),B称为"被叫函数"(Callee). 间接调用:在函数A的函数体里并不出现函数B的函数名,而是使用指向函数B的函数指针p来使内存中属于函数B的代码片断得以执行--听

  • Java 多线程实例详解(三)

    本文主要接着前面多线程的两篇文章总结Java多线程中的线程安全问题. 一.一个典型的Java线程安全例子 public class ThreadTest { public static void main(String[] args) { Account account = new Account("123456", 1000); DrawMoneyRunnable drawMoneyRunnable = new DrawMoneyRunnable(account, 700); Thr

  • java IO 字节流详解及实例代码

    java IO 字节流详解 1.         如何理解输入输出流? 这是我当初在学习Java IO这一块很难理解的一块,输入输出流我们可必须以一个为参照物:我们以内存为参照物,凡是写入内存的我们叫输入流,从内存中写出的我们叫输出流.看下面的示例图 有了这样的一个概念对于我们再学习Java中的IO流我相信就会变得特别简单了. 2.         再看流的分类 流的分类,Java的流分类比较丰富,刚接触的人看了后会感觉很晕.流分类的方式很多: 1.按照输入的方向分,输入流和输出流,输入输出的参

  • java LinkedList类详解及实例代码

    java  LinkedList类详解 LinkedList的特有功能 A:添加功能 public void addFirst(Object e); public void addLast(Object e); B:特有功能 public Object getFirst(); public Object getLast(); C:删除功能 public Object removeFirst(); public Object removeLast(); 实例代码: import java.util

  • Java构造方法实例详解(动力节点java学院整理)

    构造函数是一种特殊的函数.其主要功能是用来在创建对象时初始化对象, 即为v对象成员变量赋初始值,总与new运算符一起使用在创建对象的语句中.构造函数与类名相同,可重载多个不同的构造函数.在JAVA语言中,构造函数与C++语言中的构造函数相同,JAVA语言中普遍称之为构造方法. 使用构造器时需要记住: 1.构造器必须与类同名(如果一个源文件中有多个类,那么构造器必须与公共类同名) 2.每个类可以有一个以上的构造器 3.构造器可以有0个.1个或1个以上的参数 4.构造器没有返回值 5.构造器总是伴随

  • Java中双向链表详解及实例

    Java中双向链表详解及实例 写在前面: 双向链表是一种对称结构,它克服了单链表上指针单向性的缺点,其中每一个节点即可向前引用,也可向后引用,这样可以更方便的插入.删除数据元素. 由于双向链表需要同时维护两个方向的指针,因此添加节点.删除节点时指针维护成本更大:但双向链表具有两个方向的指针,因此可以向两个方向搜索节点,因此双向链表在搜索节点.删除指定索引处节点时具有较好的性能. Java语言实现双向链表: package com.ietree.basic.datastructure.dublin

  • java 可变参数详解及实例

    java 可变参数详解 可变参数(Varargs)使程序员可以声明一个接受可变数目参数的方法. 可变参数也是JDK5.0中出现的新特性. 可变参数本质上就是一个数组,对于某个声明了可变参数的方法来说,我们既可以传递离散的值,也可以传递数组对象. 但如果将方法中的参数定义为数组,那么只能传递数组对象而不能传递离散的值. 注意,可变参数必须是方法声明中的最后一个参数.一个方法不可能具有两个或两个以上的可变参数. 附上例子程序: public class TestVarargs { private s

随机推荐