递归函数解析2020-12-11

自述:第一次接触递归函数时,不理解在函数内部为什么有调用本身?为什么会使用递归函数?有什么优点?调用本身后再次进入递归函数体内,怎么退出第二次甚至是第n次的递归函数,有没有条件来判断退出?退出的顺序是什么样子的?进入递归函数10次,退出是退出10次还是一次就能够退出? 

下面就带着上面的 问题来看下本文章吧。

递归函数

递归函数

程序调用自身的编程技巧称为递归(recursion)。递归作为一种算法在程序设计语言中广泛运用。一个过程或函数再其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原始问题相似的规模较小问题来求解,递归策略只需要少量的程序就可以描述出解题过程所需要的多次重复计算,大大减少了程序的代码量。递归的主要思考方式在于大事化小

注意:

①一个函数在他的函数体内调用他自身称为递归调用,执行递归函数将反复调用其自身,每执行一次进入新的一层。

②为防止递归函数无休止的进行,必须在函数内有终止条件。

③对于一个函数只要知道他的递归定义式边界条件,就可以编递归函数。

注:

递归的时候,每次调用一个函数,计算机都会为这个函数分配新的空间,这就是说,当被调函数返回的时候,调用函数中的变量依然会保持原先的值,否则也不可能实现反向输出。

递归解决的问题:

阶乘、斐波那契数列、汉诺塔、杨辉三角的存取、字符串回文判断、字符串全排列、二分查找、树的深度求解

什么是递归呢? 
递归的精髓(思想)是什么? 
递归和循环的区别是什么? 
什么时候该用递归? 
使用递归需要注意哪些问题? 
递归思想解决了哪些经典的问题? 
这些问题正是笔者准备在本文中详细阐述的问题。

二. 递归的内涵

1、定义 (什么是递归?)

   在数学与计算机科学中,递归(Recursion)是指在函数的定义中使用函数自身的方法。实际上,递归,顾名思义,其包含了两个意思:递 和 归,这正是递归思想的精华所在。

2、递归思想的内涵(递归的精髓是什么?)

   正如上面所描述的场景,递归就是有去(递去)有回(归来),如下图所示。“有去”是指:递归问题必须可以分解为若干个规模较小,与原问题形式相同的子问题,这些子问题可以用相同的解题思路来解决,就像上面例子中的钥匙可以打开后面所有门上的锁一样;“有回”是指 : 这些问题的演化过程是一个从大到小,由近及远的过程,并且会有一个明确的终点(临界点),一旦到达了这个临界点,就不用再往更小、更远的地方走下去。最后,从这个临界点开始,原路返回到原点,原问题解决。

                                           

   更直接地说,递归的基本思想就是把规模大的问题转化为规模小的相似的子问题来解决。特别地,在函数实现时,因为解决大问题的方法和解决小问题的方法往往是同一个方法,所以就产生了函数调用它自身的情况,这也正是递归的定义所在。格外重要的是,这个解决问题的函数必须有明确的结束条件,否则就会导致无限递归的情况。

3、用归纳法来理解递归

   数学都不差的我们,第一反应就是递归在数学上的模型是什么,毕竟我们对于问题进行数学建模比起代码建模拿手多了。观察递归,我们会发现,递归的数学模型其实就是 数学归纳法,这个在高中的数列里面是最常用的了,下面回忆一下数学归纳法。

   数学归纳法适用于将解决的原问题转化为解决它的子问题,而它的子问题又变成子问题的子问题,而且我们发现这些问题其实都是一个模型,也就是说存在相同的逻辑归纳处理项。当然有一个是例外的,也就是归纳结束的那一个处理方法不适用于我们的归纳处理项,当然也不能适用,否则我们就无穷归纳了。总的来说,归纳法主要包含以下三个关键要素:

步进表达式:问题蜕变成子问题的表达式 
结束条件:什么时候可以不再使用步进表达式 
直接求解表达式:在结束条件下能够直接计算返回值的表达式 
事实上,这也正是某些数学中的数列问题在利用编程的方式去解决时可以使用递归的原因,比如著名的斐波那契数列问题。

递归的应用场景

   在我们实际学习工作中,递归算法一般用于解决三类问题:

   (1). 问题的定义是按递归定义的(Fibonacci函数,阶乘,…);

   (2). 问题的解法是递归的(有些问题只能使用递归方法来解决,例如,汉诺塔问题,…);

   (3). 数据结构是递归的(链表、树等的操作,包括树的遍历,树的深度,…)。

  在下文我们将给出递归算法的一些经典应用案例,这些案例基本都属于第三种类型问题的范畴。

 递归与循环

   递归与循环是两种不同的解决问题的典型思路。递归通常很直白地描述了一个问题的求解过程,因此也是最容易被想到解决方式。循环其实和递归具有相同的特性,即做重复任务,但有时使用循环的算法并不会那么清晰地描述解决问题步骤。单从算法设计上看,递归和循环并无优劣之别。然而,在实际开发中,因为函数调用的开销,递归常常会带来性能问题,特别是在求解规模不确定的情况下;而循环因为没有函数调用开销,所以效率会比递归高。递归求解方式和循环求解方式往往可以互换,也就是说,如果用到递归的地方可以很方便使用循环替换,而不影响程序的阅读,那么替换成循环往往是好的。问题的递归实现转换成非递归实现一般需要两步工作:

   (1). 自己建立“堆栈(一些局部变量)”来保存这些内容以便代替系统栈,比如树的三种非递归遍历方式;

   (2). 把对递归的调用转变为对循环处理。

递归优缺点:

优点:

1、代码简洁

2、易于理解

如在树的前/中/后序遍历中,递归的实现明显比循环简单。

缺点:

1、时间和空间的消耗比较大

递归由于是函数调用自身,而函数的调用时消耗时间和空间的,每一次函数调用,都需要在内存栈中分配空间以保存参数,返回值和临时变量,而往栈中压入和弹出数据也都需要时间,所以降低了效率。

2、重复计算

递归中又很多计算都是重复的,递归的本质时把一个问题分解成两个或多个小 问题,多个小问题存在重叠的部分,即存在重复计算,如斐波那契数列的递归实现。

3、调用栈溢出

递归可能时调用栈溢出,每次调用时都会在内存栈中分配空间,而栈空间的容量是有限的,当调用的次数太多,就可能会超出栈的容量,进而造成调用栈溢出。

递归解题三部曲

编写递归函数的步骤,可以分解为三个。

递归第一个步骤:明确函数要做什么

对于递归,一个最重要的事情就是要明确这个函数的功能。这个函数要完成一样什么样的事情,是完全由程序员来定义的,当写一个递归函数的时候,先不要管函数里面的代码是什么,而要先明确这个函数是实现什么功能的。

比如,我定义了一个函数,这个函数是用来计算n的阶乘的。

// 计算n的阶乘(假设n不为0)
int f(int n) {
    // 先不管内部实现逻辑
}

这样,就完成了第一个步骤:明确递归函数的功能。

递归第二个步骤:明确递归的结束(退出递归)条件

所谓递归,就是会在函数的内部逻辑代码中,调用这个函数本身。因此必须在函数内部明确递归的结束(退出)递归条件,否则函数会一直调用自己形成死循环。意思就是说,需要有一个条件(标识符参数)去引导递归结束,直接将结果返回。要注意的是,这个标识符参数需要是可以预见的,对于函数的执行返回结果也是可以预见的。

比如在上面的计算n的阶乘的函数中,当n=1的时候,肯定能知道f(n)对应的结果是1,因为1的阶乘就是1,那么我们就可以接着完善函数内部的逻辑代码,即将第二元素(递归结束条件)加进代码里面。

// 计算n的阶乘(假设n不为0)
int f(int n) {
    if (n == 1) {
        return 1;
    }
}

当然了,当n=2的时候,也可以知道n的阶乘是2,那么也可以把n=2作为递归的结束条件。

// 计算n的阶乘(假设n>=2)
int f(int n) {
    if (n == 2) {
        return 2;
    }
}

这里就可以看出,递归的结束条件并不局限,只要递归能正常结束,任何结束条件都是允许的,但是要注意一些逻辑上的细节。比如说上面的n==2的条件就需要n>2,否则当n=1的时候就会被漏掉,可能导致递归不能正常结束。完善一下就是当n<=2的时候,f(n)都会等于n。

// 计算n的阶乘(假设n不为0)
int f(int n) {
    if (n <= 2) {
        return n;
    }
}

这样,就完成了第二步骤:明确递归的退出条件。

递归的第三个步骤:找到函数的等价关系式

递归的第三个步骤就是要不断地缩小参数的范围,缩小之后就可以通过一些辅助的变量或操作使原函数的结果不变。比如在上面的计算n的阶乘的函数中,要缩小f(n)的范围,就可以让f(n)=n* f(n-1),这样范围就从n变成了n-1,范围变小了,直到范围抵达n<=2退出递归。并且为了维持原函数不变,我们需要让f(n-1)乘上n。说白了,就是要找到一个原函数的等价关系式。在这里,f(n)的等价关系式为n*f(n-1),即f(n)=n*f(n-1)。

// 计算n的阶乘(假设n不为0)
int f(int n) {
    if (n <= 2) {
        return n;
    }
    // 把n打出来看一下,你就能明白递归的原理了
    //System.out.println(n);
    printf("%d",n);
    // 加入f(n)的等价操作逻辑
    return n * f(n - 1);
}

到这里f(n)的功能就基本实现了。每次写递归函数的时候,强迫自己跟着这三个步骤走,能达到事半功倍的效果。另外也可以看出,第三个步骤几乎是最难的一个步骤。

举一个简单的例子

#include "stdio.h"
int  fun(int  n)  //求1+2+3+…+n的结果
{
    if(n==1)            
        return 1;
    return n+fun(n-1);
}
int  main( void )
{
    printf("10及以内的自然数之和是%d", fun(10) );
    return 0;
}

运行结果:

10及以内的自然数之和是55.

我们注意到,函数fun里面调用了一个函数fun,也就是调用了自己,这种函数调用自己的技巧我们就称为递归算法,而这个调用了自己的函数fun就是一个递归函数

函数fun的运行过程是这样的:

大家在设计递归函数时,可能也会像上图这样去思考每一层的变化,这样一来往往让人头大。

其实,根本就不用这样,设计递归函数其实很简单,基本上只考虑它的第一层就可以了,设计它甚至比设计普通的函数还要简单,因为他又便利之处(后面会讲到)。

为了说明方便,函数fun和他调用的fun我们分开称呼,外层的fun称作1号fun(第一次调用自己),被1号调用的fun就称为2号fun,如果调用了多个fun,就3号、4号顺延;如果遇到按上面的递归结构说明跟深层的fun情况,取名也这样顺延。

解析本例:

1、首先要想,将函数设计成一个怎样的功能?这是重要的环节。

主要看需求,满足需求的情况下微调即可。

比如我们需求的是求n以内的自然数之和,那么我们就这样设计fun的功能——给与正整数n,返回n及以内的自然数之和。

函数功能设计好之后,那么要得到10以内的自然数之和也就很简单了,所以在main里面通过fun(10)就能得到答案了。

在决定好函数功能后,接着就实现功能。作为递归函数,需要调用自己,调用时只需当它是一个普通函数就行,因为它的参数、返回值、功能等已明确,不用去想他还没实现这件事,普通的函数怎么用它就怎么用。

但是有一个问题,按下面思路,fun最简单的实现应该是这样:

int  fun(int  n)
{
    return  fun( n );
}

毕竟根据fun功能,直接调用fun(n)就能得到结果了,然后将结果返回不就实现本函数了吗?

到这里如果你认为是正确的。我就呵呵了。这样会死循环的,因为没有终止条件(退出条件),这里需要加点限制——在“当做一个普通函数来用”这个思路的基础上,加上“不能原封不动地把自己的参数给它”。

2、想办法结束递归函数

递归也是一种循环,循环语句有结束循环的条件,递归当然也有。

不过要注意的是:这个结束的条件要放在调用2号之前,因为不这样,仍然是无休止递归,控制流始终无法到这结束条件这里。

那么,如何写结束条件那?分两种情况来考虑,一种是按最简单的方法实现的,此时基本考虑最简单的情况即可;当实现不是最简单时,情况多点考虑。

那么此时最简单的情况就是1,结束条件就这样写:

int  fun(int  n)
{
    if( n==1 )
        return  1;
    return  n+fun( n-1 );
}

当然,你可能会假设n=0时,怎么处理?关键是一般情况下也不会写0进去啊。如果非要防止这种情况的出现可以写:

int  fun(int  n)
{
    if( n==0 )
    {
        return 0;
    }
    if( n==1 )
        return  1;
    return  n+fun( n-1 );
}

还有一种可能,就是在填写n值的时,直接填写了一个小+...于0的数,这是怎么办呢?那就结合上面的条件写下:

int  fun(int  n)
{
    if( n<1 )
    {
        return 0;
    }
    if( n==1 )
        return  1;
    return  n+fun( n-1 );
}

3、怎么样找到函数的等价关系式,在函数里面接着调用自身

fun函数的设计是找到10以内的自然数之和,也就是10+9+...+1+0,第一次调用fun(10),在调用2号fun函数会采用10+fun(10-1),调用3号fun()时,为10+9+fun(9-1),依次类推。

比如fun首先会收到10,那么它调用的2号fun时,就不能给它10,至少要改变一点。

同样的,如果1号fun收到的是a、b、c,那么调用2号fun时 就不能给a、b、c,稍微改变一点,比如a-1、b、c。

收到这样的限制后,最简单的方法就变成了:

int  fun(int  n)
{
    if( n<1 )
    {
        return 0;
    }
    if( n==1 )
        return  1;
    return  n+fun( n-1 );
}

这个方法也就是让自己只计算最简单的一部分,剩下的任务全部交给2号去做。

当然,根据这个理论,实现的方法太多了,这样也是可以的。


int  fun(int  n)
{
    if( n<1 )
    {
        return 0;
    }
    if( n==1 )
        return  1;
    return  n+(n-1)+fun( n-2 );
}

这里仍然是“自己计算一点点,剩下的全部交给2号去做”的思路,只是比起上一个,这次1号计算的稍微多了一点而已。

下面会有常用的列子来说明:

例一、求n的阶乘

程序如下:

#include <stdio.h>

    /* 阶乘的递归实现*/
long fun(int n)
{
        if(n == 1)   // 递归终止条件 
            return 1;    // 简单情景

        return n*f(n-1);  // 相同重复逻辑,缩小问题的规模
}

--------------------------------我是分割线-------------------------------------

    /* 阶乘的非递归实现*/
long fun(int n) {
        long result = n;
        while (n > 1) {
            n--;
            result = result * n;
        }
        return result;
    }
}
int main()
{
    int n;
    scanf("%d",&n);
    printf("%d != %d\n",n,fun(n));
    return 0;
}

例二:求斐波那契数的第n个数的值(除了1,2是原来的数字,其他的都是前两个数相加)

程序如下:

* Title: 斐波纳契数列 

* Description: 斐波纳契数列,又称黄金分割数列,指的是这样一个数列:1、1、2、3、5、8、13、21、…… 
* 在数学上,斐波纳契数列以如下被以递归的方法定义:F0=0,F1=1,Fn=F(n-1)+F(n-2)(n>=2,n∈N*)。 

* 两种递归解法:经典解法和优化解法 
* 两种非递归解法:递推法和数组法 
*

#include <stdio.h>
int fun(int n);//声明写在最外面(外部变量)就相当于全局变量
int mian()
{
    int n;
    scanf("%d",&n);
    printf("fun(%d)=%d\n",n,fun(n));
    return 0;
}

-----------------------------------------------------------------------------
     * 斐波那契数列如下:
     * 
     *  1,1,2,3,5,8,13,21,34,...
     * 
     * *那么,计算fib(5)时,需要计算1次fib(4),2次fib(3),3次fib(2),调用了2次fib(1)*,即:
     * 
     *  fib(5) = fib(4) + fib(3)
     *  
     *  fib(4) = fib(3) + fib(2) ;fib(3) = fib(2) + fib(1)
     *  
     *  fib(3) = fib(2) + fib(1)
     *  
     * 这里面包含了许多重复计算,而实际上我们只需计算fib(4)、fib(3)、fib(2)和fib(1)各一次即可,
     * 后面的optimizeFibonacci函数进行了优化,使时间复杂度降到了O(n).

int fun(int n)
{
    if(1 ==n ||2==n)//边界条件
    {
        return n;
    }
    return fun(n-1)+fun(n-2);//递归定义式
}

//优化后的
int optimizeFibonacci(int first, int second, int n) {
        if (n > 0) {
            if(n == 1){    // 递归终止条件
                return first;       // 简单情景
            }else if(n == 2){            // 递归终止条件
                return second;      // 简单情景
            }else if (n == 3) {         // 递归终止条件
                return first + second;      // 简单情景
            }
            return optimizeFibonacci(second, first + second, n - 1);  // 相同重复逻辑,缩小问题规模
        }
        return -1;
    }
//非递归解法:有去无回
int fibonacci_loop(int n) {

        if (n == 1 || n == 2) {   
            return 1;
        }

        int result = -1;
        int first = 1;      // 自己维护的"栈",以便状态回溯
        int second = 1;     // 自己维护的"栈",以便状态回溯

        for (int i = 3; i <= n; i++) { // 循环
            result = first + second;
            first = second;
            second = result;
        }
        return result;
    }
//使用数组存储斐波那契数列
 int fibonacci_array(int n) {
        if (n > 0) {
            int[] arr = new int[n];   // 使用临时数组存储斐波纳契数列
            arr[0] = arr[1] = 1;

            for (int i = 2; i < n; i++) {   // 为临时数组赋值
                arr[i] = arr[i-1] + arr[i-2];
            }
            return arr[n - 1];
        }
        return -1;
    }

例三:杨辉三角取值

* Title: 杨辉三角形又称Pascal三角形,它的第i+1行是(a+b)i的展开式的系数。

* 它的一个重要性质是:三角形中的每个数字等于它两肩上的数字相加。

* * 例如,下面给出了杨辉三角形的前4行: *

   1 *

 1  1 *

1 2 1 *

1 3 3 1 *

递归获取杨辉三角指定行、列(从0开始)的值 * 注意:与是否创建杨辉三角无关

int getValue(int x, int y) {
        if(y <= x && y >= 0){
            if(y == 0 || x == y){   // 递归终止条件
                return 1; 
            }else{ 
                // 递归调用,缩小问题的规模
                return getValue(x-1, y-1) + getValue(x-1, y); 
            }
        }
        return -1;
    } 

其他例子请参考下面的网址:https://blog.csdn.net/sinat_38052999/article/details/73303111

来看下面更有意思的问题:汉诺塔问题

一、起源:

汉诺塔(又称河内塔)问题是源于印度一个古老传说的益智玩具。大梵天创造世界的时候做了三根金刚石柱子,在一根柱子上从下往上按照大小顺序摞着64片黄金圆盘。大梵天命令婆罗门把圆盘从下面开始按大小顺序重新摆放在另一根柱子上。并且规定,在小圆盘上不能放大圆盘,在三根柱子之间一次只能移动一个圆盘。

二.抽象为数学问题:

  如下图所示,从左到右有A、B、C三根柱子,其中A柱子上面有从小叠到大的n个圆盘,现要求将A柱子上的圆盘移到C柱子上去,期间只有一个原则:一次只能移到一个盘子且大盘子不能在小盘子上面,求移动的步骤和移动的次数

                                         

从上到下圆盘编号为从1~n,A、B、C为圆柱。

一:设计函数功能。

题目要求把n个圆盘从A柱子移到C柱子,那我们就把函数的功能设计为——把n个圆盘从某根柱子移动到另一根柱子上并显示移动过程。

函数原型设为void hanoi( int n, char a, char b, char c )

a表示初始情况圆盘所在的柱子

c表示要转移的柱子

b表示用来中转的柱子

n表示a柱子上的圆盘数

三个char变量存储的是三根柱子的名字。

函数功能设计好后,那么main要得到从'A'柱移动10个圆盘到'C'柱的过程,只需一行即可—— hanoi ( 10 , 'A' , 'B' , 'C' )

二. 结束条件:

当圆盘数为1就是最简情况

if(n==1)
{
    printf("%c--->%c\n",a, c);    //转移一个圆盘直接从起始柱转到目标柱
    return ;    //必须要返回才能结束
}

三:实现函数功能

依照hanoi的功能,可以让它移动n-1个盘子,自己则移动剩下的一个,于是我们只需三步即可完成这个过程:

第一步,让2号hanoi移动n-1个圆盘到B柱子。

由于对1号来说,a是自己的起始柱,b是中转柱,c是目标柱,因此它要让2号把盘子从a移动到b,所以调用hanoi( n-1 , a , c , b ) ,也就是对2号来说,1号的b柱就是它的目标柱。效果如下:

                              

第二步,1号将一个圆盘从A盘移动到C盘。

由于实际上并没有什么盘子的数据,因此输出移动过程就是在移动盘子了——printf("%c--->%c\n", a, c) ,它表示将起始a柱的一个圆盘移动到目标c柱:

                                      

第三步,2号将b柱上n-1个圆盘移动到c柱上——调用hanoi( n-1 , b, a, c ) ,结果如下:

                                     

实现这个算法可以简单分为三个步骤:

    (1)     把n-1个盘子由A 移到 B;

    (2)     把第n个盘子由 A移到 C;

    (3)     把n-1个盘子由B 移到 C;

从这里入手,在加上上面数学问题解法的分析,我们不难发现,移到的步数必定为奇数步:

    (1)中间的一步是把最大的一个盘子由A移到C上去;

    (2)中间一步之上可以看成把A上n-1个盘子通过借助辅助塔(C塔)移到了B上,

    (3)中间一步之下可以看成把B上n-1个盘子通过借助辅助塔(A塔)移到了C上;

解析上面移动的过程:

(1)n == 1

             第1次  1号盘  A---->C       sum = 1 次

       (2)  n == 2

             第1次  1号盘  A---->B    第2次  2号盘  A---->C   第3次  1号盘  B---->C        sum = 3 次 

  (3)n == 3

                             第1次  1号盘  A---->C

        第2次  2号盘  A---->B

        第3次  1号盘  C---->B

        第4次  3号盘  A---->C

        第5次  1号盘  B---->A

        第6次  2号盘  B---->C

        第7次  1号盘  A---->C        sum = 7 次

(3)n == 4

        第1次  1号盘  A---->B         第8次  4号盘  A---->C             第15次  1号盘  B---->C

        第2次  2号盘  A---->C         第9次  1号盘  B---->C 

        第3次  1号盘  B---->C         第10次  2号盘  B---->A

        第4次  3号盘  A---->B         第11次  1号盘  C---->A

        第5次  1号盘  C---->A         第12次  3号盘  B---->C

        第6次  2号盘  C---->B         第13次  1号盘  A---->B 

        第7次  1号盘  A---->B          第14次  2号盘  A---->C          sum = 7 次

不难发现规律:1个圆盘的次数 2的1次方减1

       2个圆盘的次数 2的2次方减1

                         3个圆盘的次数 2的3次方减1

                         4个圆盘的次数 2的4次方减1

                         。  。   。    。   。 

                         n个圆盘的次数 2的n次方减1

 故:移动次数为:2^n - 1,当n为奇数是,第一次移动a-c;n为偶数时,第一次移动a-b;在(3)     把n-1个盘子由B 移到 C,b-c;

#include "stdio.h"
//(圆盘数量,起始柱,中转柱,目标柱)
void hanoi(int n, char a, char b, char c)    
{
    if(n==1)圆盘只有一个时,只需将其从A塔移到C塔
    {
        printf("%c--->%c\n",a, c);  //将编b号为1的圆盘从A移到C
        return ;                        
    }
    hanoi(n-1, a, c, b);  //递归,把A塔上编号1~n-1的圆盘移到B上,以C为辅助塔          
    printf("%c--->%c\n", a, c); //把A塔上编号为n的圆盘移到C上
    hanoi(n-1, b, a, c);     //递归,把B塔上编号1~n-1的圆盘移到C上,以A为辅助塔       
}
int main( void )
{
    hanoi(3, 'A', 'B', 'C');        
    return 0;
}

参考另一篇文章:https://www.cnblogs.com/dmego/p/5965835.html

public class TowersOfHanoi {
    static int m =0;//标记移动次数
    //实现移动的函数
    public static void move(int disks,char N,char M)
    {
        System.out.println("第" + (++m) +" 次移动 : " +" 把 "+ disks+" 号圆盘从 " + N +" ->移到->  " + M);
    }
    //递归实现汉诺塔的函数
    public static void hanoi(int n,char A,char B,char C)
    {
        if(n == 1)//圆盘只有一个时,只需将其从A塔移到C塔
            TowersOfHanoi.move(1, A, C);//将编b号为1的圆盘从A移到C
        else
        {//否则
            hanoi(n - 1, A, C, B);//递归,把A塔上编号1~n-1的圆盘移到B上,以C为辅助塔
            TowersOfHanoi.move(n, A, C);//把A塔上编号为n的圆盘移到C上
            hanoi(n - 1, B, A, C);//递归,把B塔上编号1~n-1的圆盘移到C上,以A为辅助塔
        }
    }
    public static void main(String[] args) {
        Scanner imput = new Scanner(System.in);
        char A = 'A';
        char B = 'B';
        char C = 'C';
        System.out.println("******************************************************************************************");
        System.out.println("这是汉诺塔问题(把A塔上编号从小号到大号的圆盘从A塔通过B辅助塔移动到C塔上去");
        System.out.println("******************************************************************************************");
        System.out.print("请输入圆盘的个数:");
        int disks = imput.nextInt();
        TowersOfHanoi.hanoi(disks, A, B, C);
        System.out.println(">>移动了" + m + "次,把A上的圆盘都移动到了C上");
        imput.close();
    }

}

反转链表的例子  参考:https://www.cnblogs.com/yanggb/p/11138049.html

如链表为1->2->3->4,反转后为4->3->2->1。

链表节点定义如下:

class Node {
    int date;
    Node next; // 存储下一个节点
}

递归第一个步骤:明确函数要做什么

假设函数reverseList(head)的功能是反转单链表,其中head表示链表的头节点。代码如下:

Node reverseList(Node head) {

}

递归第二个步骤:明确递归的结束(退出递归)条件

当链表只有一个节点,或者如果是空链表的话,就直接返回head就行了。

Node reverseList(Node head) {
    if (head == null || head.next == null){
        return head;
    }
}

递归的第三个步骤:找到函数的等价关系式

// 用递归的方法反转链表
public static Node reverseList2(Node head) {
        // 递归结束条件
        if (head == null || head.next == null) {
            return head;
        }
        // 递归反转子链表
        Node newList = reverseList2(head.next);
        // 改变1,2节点的指向
        // 通过head.next获取节点2
        Node t1  = head.next;
        // 让2的next指向 2
        t1.next = head;
        // 1的next指向null
        head.next = null;
        // 把调整之后的链表返回
        return newList;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值