递归

一、递归

  说到递归,我不知道该用怎么样词汇去描述它,这说明我对它的理解还是处在朦胧阶段,我想这需要一个过程,今天我们简单来认识下这个让人又爱又恨的家伙吧~

  假如你在祖母的阁楼里发现了一个带锁的神秘宝箱,祖母告诉你,钥匙很可能在这个盒子里。

  于是你查看这个盒子,发现,这个盒子里有盒子,而盒子里的盒子又有盒子。钥匙就在其中的某个盒子里,为了找到钥匙,你该怎么做呢?

  方案一:

    1、创建一个要查找的盒子堆;(就是把这个大盒子里你能看到的盒子全部拿出来)

    2、从盒子堆中取出一个盒子,在里面找;(开始检查每一个盒子)

    3、如果打开这个盒子发现里面还是一个盒子,就将其放入第1步的盒子堆中,以便后续再查找;

       如果是钥匙,则大功告成;

    5、回到第2步继续查找。

  方案二:

    1、检查盒子中的每样东西;

    2、如何是盒子,就回到第一步;

    3、如果是钥匙,大功告成!

  在你看来,哪种方法更容易?

  第一方法使用的是while循环:只要盒子堆不空,就在其中取一个盒子,并在其中仔细查找,伪代码如下:

  

  第二种方法使用递归,函数自己调自己,伪代码如下:

  

  两种方法作用相同,但明显第二种方法看着更舒服,这或许就是递归最大的优势。递归只是让解决方案看着更清晰,并没有性能上的优势。实际上,在某些情况下,循环的性能更好。所以借助Leigh Caldwell的那句话:“如果使用循环,程序的性能可能更高;如果使用递归,程序的可读性更容易理解”。

二、基线条件和递归条件

  因为递归是自己调自己,所以很容易造成死循环,那必须得设置条件告诉它何时停止递归。正因为如此,每个递归都有两部分构成:

  1、基线条件:函数不再调用自己;

  2、递归条件:函数调用自己。

三、栈

  栈又名堆栈,它是一种受限的线性表。其限制是仅允许在表的一端进行插入和删除运算。这一端称为栈顶,相对另一端称为栈底。

  向栈插入新元素,又称为进栈、入栈或压栈,把一个元素从栈顶删除,又称为出栈或退栈。

  说完栈,我们再说一个重量级的人物——调用栈(Call stack),它是深入理解递归的一个重要的概念!!!

  调用栈是计算机科学中存储有关正在运行的子程序的消息的栈。计算机在内部使用调用栈的栈,我们看看计算机是如何来使用调用栈的,一代代码如下:

    

  调用栈代码刨析:

  当你调用函数greet('Lucy')时,计算机为greet函数分配了一块内存;

  然后将变量name设置为Lucy,并保存在内存中;(每当你调用函数时,计算机都是如此将函数调用涉及到的所有变量的值存储到内存中)

  接下来计算机打印出hello,Lucy;

  然后又调用函数greet2('Lucy'),同样,系统此时也为greet2函数调用分配了一块内存;(计算机使用一个栈来表示这些内存块,其中第二个内存块位于第一个内存块上面)

  此时计算机打印how are you,Lucy?然后从函数greet2返回,此时,栈顶的内存(greet2的内存)被弹出(出栈)。

  现在,栈顶的内存块是函数greet的,这意味着你已经返回到greet函数中。此处有一个重要的概念:调用另一个函数时,当前函数暂停并处于未完成状态

  然后计算机又调用函数bye,把函数bye内存块压入到栈中,待bye函数执行完毕,再弹出栈,最终又回到greet函数,因为没有什么事情可做,最后从greet函数返回。

  在这个过程中,这个栈用于存储多个函数的变量,被称为调用栈。

  使用栈虽然很方便,但也要付出代价,因为每个函数的调用都会占用一定的内存,这样将导致栈很高,这意味着计算机需要大量的内存来存储这些函数调用的信息,如果超出系统内存,这将导致栈溢出。对应的解决方案有两种:

  1、使用循环来替代递归;

  2、使用尾递归,这是一个高级递归主题,有兴趣自己研究下,另外不是所有的语言都支持尾递归。

原文地址:https://www.cnblogs.com/zsvslx/p/10564649.html