JavaSE第五节————方法的使用(下)

系列文章目录

JavaSE第五节————方法的使用(下)

方法的使用

  1. 方法概念及使用
  2. 方法重载
  3. 递归
  4. 递归练习和汉诺塔问题



一、递归

1.生活中的递归

  1. 查字典或者看维基,想知道一个词的意思,就去看它的解释,在看解释中在遇到不会的,再去看这个词的解释解释--最终把每个词都能看懂了,这时候就返回,继续看上一个词的解释。
  2. 传小纸条(假设对方还要传回来一张),一个人传给自己左边,然后那个人再传给自己左边,直到纸条传到了,在写张纸条返回,从右到左一个一个传回来。

上面的两个故事有个共同的特征:自身中又包含了自己,该种思想在数学和编程中非常有用。
递归的思想是把一个大型复杂问题层层转化为一个与原问题规模更小的问题,问题被拆解成子问题后,递归调用继续进行,直到子问题无需进一步递归就可以解决的地步为止。

2.递归的概念

一个方法在执行过程中调用自身, 就称为 “递归”.
递归相当于数学上的 “数学归纳法”, 有一个起始条件, 然后有一个递推公式

例如, 我们求 N! 起始条件: N = 1 的时候, N! 为 1. 这个起始条件相当于递归的结束条件. 递归公式: 求 N! , 直接不好求, 可以把问题转换成 N! => N * (N-1)!

递归的必要条件:

  1. 将原问题划分成其子问题,注意:子问题必须要与原问题的解法相同
  2. 递归出口

代码示例: 递归求 N 的阶乘

 public static void main(String[] args) {
        int n = 5;
        int ret = func(n);
        System.out.println("ret = " + ret);
    }
    public static int func(int n) {
        if (n == 1) {
            return 1;
        }
        return n * func(n - 1); // func 调用函数自身
    }

在这里插入图片描述

3.递归执行过程分析( * *

递归的程序的执行过程不太容易理解, 要想理解清楚递归, 必须先理解清楚 “方法的执行过程”, 尤其是 “方法执行结束之后, 回到调用位置继续往下执行”

代码示例: 递归求 N 的阶乘

public static void main(String[] args) {
        int n = 5;
        int ret = func(n);
        System.out.println("ret = " + ret);
    }
    public static int func(int n) {
        if (n == 1) {
            return 1;
        }
        return n * func(n - 1); // func 调用函数自身
    }

执行过程图
在这里插入图片描述

关于 “调用栈” 方法调用的时候, 会有一个 “栈” 这样的内存空间描述当前的调用关系. 称为调用栈.
每一次的方法调用就称为一个 “栈帧”, 每个栈帧中包含了这次调用的参数是哪些, 返回到哪里继续执行等信息. 后面我们借助 IDEA 很容易看到调用栈的内容.

4.递归练习

代码示例1 按顺序打印一个数字的每一位(例如 1234 打印出 1 2 3 4

public static void fun(int n) {//1234
       if (n < 10){
            System.out.println( n % 10 );
           return;
        }
        fun(n / 10);
        System.out.println(n % 10);
    }
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
        fun(n);
        
    }

在这里插入图片描述

执行过程图
在这里插入图片描述

代码示例2 递归求 1 + 2 + 3 + … + n

private static int sum(int n){
        if(n == 1 ){
            return  1;
        }
        return n + sum(n - 1);
    }


    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
        System.out.println(sum(n));
    }

在这里插入图片描述

执行过程 (当n = 8时)
sum(8)
= 8 + sum(7)
= 8 + 7 + sum (6)
= 8 + 7 + 6 + sum (5)
= 8 + 7 + 6 + 5 + sum (4)
= 8 + 7 + 6 + 5 + 4 +sum (3)
= 8 + 7 + 6 + 5 + 4 + 3 + sum (2)
= 8 + 7 + 6 + 5 + 4 + 3 + 2 +sum (1) = 36

代码示例3 写一个递归方法,输入一个非负整数,返回组成它的数字之和

例如,输入 1729, 则应该返回1+7+2+9,它的和是19

public static int sum(int n) {
        if (n == 0){
            return  n;
        }
        return n % 10 + sum( n / 10);

    }
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
        System.out.println(sum(n));
    }

在这里插入图片描述

执行过程图
在这里插入图片描述

代码示例4 求斐波那契数列的第 N 项

斐波那契数列(Fibonacci sequence),又称黄金分割数列,因数学家莱昂纳多·斐波那契(Leonardoda Fibonacci)以兔子繁殖为例子而引入,故又称为“兔子数列”。
指的是这样一个数列:0、1、1、2、3、5、8、13、21、34、……在数学上,斐波那契数列以如下被以递推的方法定义:F(0)=0,F(1)=1, F(n)=F(n - 1)+F(n - 2)(n ≥ 2,n ∈ N*)

递归方法(有缺陷)
 private static int fib(int n){
        if(n == 1){
            return  0;
        }
        if(n == 2){
            return 1;
        }
        return fib(n - 1) + fib(n - 2);
    }
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
        System.out.println("第" + n + "项斐波那契数是" + fib(n));
    }

在这里插入图片描述
当我们求 fib(5) 的时候发现, 程序执行速度极慢. 原因是进行了大量的重复运算

 public static int count = 0; // 这个是类的成员变量. 后面会详细介绍到
        public static void main(String[] args) {
            System.out.println(fib(45));
            System.out.println(count);
        }
        public static int fib(int n) {
            if (n == 1 || n == 2) {
                return 1;
            } if
            (n == 3) {
                count++;
            }
            return fib(n - 1) + fib(n - 2);
        }

在这里插入图片描述

fib(3) 重复执行了 4 亿次.
在这里插入图片描述

递归方法

可以使用循环的方式来求斐波那契数列问题, 避免出现冗余运算

    private static int fib(int n){
        if(n == 1){
            return  0;
        }
        if(n == 2 || n == 3){
            return 1;
        }
        int f1 = 1;
        int f2 = 1;
        int f3 = 1;
        for (int i = 4; i <= n ; i++) {
            f3 = f1 +f2;
            f1 = f2;
            f2 = f3;
        }
        return  f3;
    }
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
        System.out.println("第" + n + "项斐波那契数是" + fib(n));
    }

在这里插入图片描述

二、汉诺塔

汉诺塔概念

汉诺塔(Tower of Hanoi),又称河内塔,是一个源于印度古老传说的益智玩具,由法国数学家爱德华·卢卡斯于1883年发明。
汉诺塔的玩法是将一个木桩上的圆盘转移到另外一个木桩。移动规则:1、一次只能移动一个圆盘;2、每个桩上只有最顶层的圆盘可以移动,并且所移动的圆盘只能移到空木桩上或者它要比木桩顶层已存在的圆盘小。也就是说,您不能将大圆盘置于小圆盘之上。常见的汉诺塔n=6~10,完成转移需要2^n - 1步。

汉诺塔问题解析

为了方便讲解,我们将 3 个柱子分别命名为A柱、B柱和C柱。实际上,解决汉诺塔问题是有规律可循的:

  1. 当起始柱上只有 1 个圆盘时,我们可以很轻易地将它移动到C柱上;

  2. 当起始柱上有 2 个圆盘时,移动过程是:先将起始柱上的 1 个圆盘移动到辅助柱上,然后将起始柱上遗留的圆盘移动到目标柱上,最后将辅助柱上的圆盘移动到目标柱上

  3. 当起始柱上有 3个圆盘时,移动过程如下图所示在这里插入图片描述

通过分析以上 3 种情况的移动思路,可以总结出一个规律:对于 n 个圆盘的汉诺塔问题,移动圆盘的过程是:

  1. 将起始柱上的 n-1 个圆盘移动到辅助柱上。
  2. 将起始柱上遗留的 1 个圆盘移动到目标柱上。
  3. 将辅助柱上的所有圆盘移动到目标柱上(此过程后原来的辅助柱变为起始柱,起始柱变为辅助柱)。

由此,n 个圆盘的汉诺塔问题就简化成了 n-1 个圆盘的汉诺塔问题。按照同样的思路,n-1 个圆盘的汉诺塔问题还可以继续简化,直至简化为移动 3 个甚至更少圆盘的汉诺塔问题

代码实现

public static void move(char location1,char location2){
        System.out.print(location1 + " --> " + location2 + " ");
    }
    //n 是圆环数目
    //position1是起始柱
    //position2是辅助柱
    //position3是目标柱
    public static void hanio(int n,char position1,char position2,char position3){
       if(n == 1){
           move(position1,position3);
           return;
       }
       hanio(n - 1,position1,position3,position2);//将起始柱上的 n-1 个圆盘移动到辅助柱上
        move(position1,position3);// 将起始柱上遗留的 1 个圆盘移动到目标柱上。
        hanio(n-1,position2,position1,position3);
    }
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
        System.out.println(n + "个圆盘的移动路径");
        hanio( n,'A', 'B', 'C');
        System.out.println();
    }

在这里插入图片描述
执行过程图

在这里插入图片描述

  • 11
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值