![数据结构(C语言实现)](https://wfqqreader-1252317822.image.myqcloud.com/cover/699/43806699/b_43806699.jpg)
3.3.1 递归的定义
递归是指在函数的定义中,在定义自己的同时又出现了对自身的调用。如果一个函数在函数体中直接调用自己,称为直接递归调用。如果经过一系列的中间调用,间接调用自己的函数称为间接递归调用。
1.递归函数
例如,n的阶乘递归定义如下:
![](https://epubservercos.yuewen.com/2EFA35/23083815801896206/epubprivate/OEBPS/Images/85_03.jpg?sign=1739301190-nAXaMVQwE1klpRpFkZJg3KwaSwmNX3pR-0-5d8cfa5e5a2df22ed72dd071cf53c09a)
n的阶乘算法如下:
![](https://epubservercos.yuewen.com/2EFA35/23083815801896206/epubprivate/OEBPS/Images/85_04.jpg?sign=1739301190-UT1cVNwDcFzMM6hGsNK3FbQJN8xdoZ9e-0-e76cb18a0e86c0adcb5ae1e0b99b24cb)
Ackermann函数定义如下:
![](https://epubservercos.yuewen.com/2EFA35/23083815801896206/epubprivate/OEBPS/Images/86_01.jpg?sign=1739301190-7DaXCHMLCevrC6uJFFHV2OpFVn8lPjRL-0-20338a623592ecb74dd9ec666a494572)
Ackerman函数相应算法如下:
![](https://epubservercos.yuewen.com/2EFA35/23083815801896206/epubprivate/OEBPS/Images/86_02.jpg?sign=1739301190-Jz8wAag2Tt6K5XcfgCb8gmcvlD64givz-0-485576087f077014506c003100b04a1c)
2.递归调用过程
递归问题可以分解成规模小且性质相同的问题加以解决。下面以著名的汉诺塔问题为例来说明递归调用的过程。
n阶汉诺塔问题。假设有三个塔座X、Y、Z,在塔座X上放置有n个直径大小各不相同、从小到大编号为1,2,…,n的圆盘,如图3.10所示。要求将X轴上的n个圆盘移动到塔座Z上并要求按照同样的叠放顺序排列,圆盘移动时必须遵循以下规则:
(1)每次只能移动一个圆盘。
![](https://epubservercos.yuewen.com/2EFA35/23083815801896206/epubprivate/OEBPS/Images/86_03.jpg?sign=1739301190-A4HmepjvpMPbT3Be1URD7MgueXJxWZKu-0-3322df8d50767d3a83571807b85afe20)
图3.10 3阶汉诺塔的初始状态
(2)圆盘可以放置在X、Y和Z中的任何一个塔座。
(3)任何时候都不能将一个较大的圆盘放在较小的圆盘上。
如何实现将放在X上的圆盘按照规则移动到Z上呢?当n=1时,问题比较简单,直接将编号为1的圆盘从塔座X移动到Z上即可。当n>1时,需要利用塔座Y作为辅助塔座,如果能将放置在编号为n之上的n-1个圆盘从塔座X移动到Y上,则可以先将编号为n的圆盘从塔座X移动到Z上,然后将塔座Y上的n-1个圆盘移动到塔座Z上。而如何将n-1个圆盘从一个塔座移动到另一个塔座又成为与原问题类似的问题,只是规模减小了1,因此可以用同样的方法解决。这是一个递归的问题,汉诺塔的算法描述如下。
![](https://epubservercos.yuewen.com/2EFA35/23083815801896206/epubprivate/OEBPS/Images/86_04.jpg?sign=1739301190-DdDLVYWB1mpHWDiggizRvR0T1XtKgC8v-0-ed853a068f457f60ade4ff2b8c263bca)
下面以n=3为例来说明汉诺塔的递归调用过程。如图3.11所示。当n>1,经历3个过程移动圆盘。
第1个过程,将编号为1和2的圆盘从塔座X移动到Y。
第2个过程,将编号为3的圆盘从塔座X移动到Z。
第3个过程,将编号为1和2的圆盘从塔座Y移动到Z。
![](https://epubservercos.yuewen.com/2EFA35/23083815801896206/epubprivate/OEBPS/Images/87_01.jpg?sign=1739301190-fG9ng8JoPSQvnA87hU6fv90dN0RhRPwp-0-5600d821dcafb01c605c50c54410e9fe)
图3.11 汉诺塔递归调用过程
第1个过程,通过调用Hanoi(2,x,z,y)实现。Hanoi(2,x,z,y)又调用自己,完成将编号为1的圆盘从塔座X移动到Z,如图3.12所示。将编号为2的圆盘从塔座X移动到Y,编号为1的圆盘从塔座Z移动到Y。如图3.13所示。
![](https://epubservercos.yuewen.com/2EFA35/23083815801896206/epubprivate/OEBPS/Images/87_02.jpg?sign=1739301190-0qaKYGt52TEFkem6IUxBYfrIqrgAzBSB-0-468ace360d53fbfcf8ab580dde2dd5c3)
图3.12 将编号为1的圆盘从塔座X移动到Z
![](https://epubservercos.yuewen.com/2EFA35/23083815801896206/epubprivate/OEBPS/Images/87_03.jpg?sign=1739301190-R3dK1AEsaM7JBy8Q56D25zzl8c9RMImj-0-d6229745f47d64c49de3a5028deaec2c)
图3.13 将编号为2的圆盘从塔座X移动到Y,编号为1的圆盘从塔座Z移动到Y
第2个过程完成编号为3的圆盘从塔座X移动到Z。如图3.14所示。
第3个过程通过调用Hanoi(2,y,x,z)实现圆盘移动。通过再次递归完成将编号为1的圆盘从塔座Y移动到X,如图3.15所示。将编号为2的圆盘从塔座Y移动到Z,将编号为1的圆盘从塔座X移动到Z。如图3.16所示。
![](https://epubservercos.yuewen.com/2EFA35/23083815801896206/epubprivate/OEBPS/Images/87_04.jpg?sign=1739301190-xBcE4TaYB5YSInqy6PAQFJ7Yr5jCYObu-0-f74ab76315947c3dacca5b228c2b0ef0)
图3.14 将编号为3的圆盘从塔座X移动到Z
![](https://epubservercos.yuewen.com/2EFA35/23083815801896206/epubprivate/OEBPS/Images/87_05.jpg?sign=1739301190-mjCoqgTJYL1wzIWrJcawU1DV9zk0OgMm-0-7477f630ff475e101f0647ffd0dc5a81)
图3.15 将编号为1的圆盘从塔座Y移动到X
![](https://epubservercos.yuewen.com/2EFA35/23083815801896206/epubprivate/OEBPS/Images/88_01.jpg?sign=1739301190-qOSVAGJ4f9pX7ChiKo2Qh5ORqgmLGv1l-0-580ee2f8bd2605fe2313527c65d6eee1)
图3.16 将编号为2的圆盘从塔座Y移动到Z,编号为1的圆盘从塔座X移动到Z
在递归调用过程中,运行被调用函数前系统要完成3件事情:
(1)将所有参数和返回地址传递给被调用函数保存。
(2)为被调用函数的局部变量分配存储空间。
(3)将控制转移到被调用函数的入口。
当被调用函数执行完毕,返回到调用函数之前,系统同样需要完成3个任务:
(1)保存被调用函数的执行结果。
(2)释放被调用函数的数据存储区。
(3)将控制转到调用函数的返回地址处。
在多层嵌套调用时,递归调用过程的原则是后调用的先返回,因此,递归调用是通过栈实现的。函数递归调用过程中,在递归结束前,每调用一次,就进入下一层。当一层递归调用结束时,返回到上一层。
为了保证递归调用的正确执行,系统设置了一个工作栈作为递归函数运行期间使用的数据存储区。每一层递归包括实在参数、局部变量及上一层的返回地址等构成一个工作记录。每进入下一层,就产生一个新的工作栈记录被压入栈顶。每返回到上一层,就从栈顶弹出一个工作记录。因此,当前层的工作记录是栈顶工作记录,被称为活动记录。递归过程产生的栈由系统自动管理,类似用户使用的栈。递归的实现本质上就是把嵌套调用变成栈实现。