![数据结构(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=1738933619-IE5hCzkCK5ZeV0WONS1RJAW92oSOL2HY-0-fa99b73b7d70f77ae9e15e9a5344ac5e)
n的阶乘算法如下:
![](https://epubservercos.yuewen.com/2EFA35/23083815801896206/epubprivate/OEBPS/Images/85_04.jpg?sign=1738933619-xnx3junCAdwrLhTdWJWgLXwOkii2vjhx-0-572da01ac89b230ac850d018269903f1)
Ackermann函数定义如下:
![](https://epubservercos.yuewen.com/2EFA35/23083815801896206/epubprivate/OEBPS/Images/86_01.jpg?sign=1738933619-nfLZJClqlhggYcP2euIt8odIDl62hxC6-0-1ba7d004c4a9ace28be170dc8970ff67)
Ackerman函数相应算法如下:
![](https://epubservercos.yuewen.com/2EFA35/23083815801896206/epubprivate/OEBPS/Images/86_02.jpg?sign=1738933619-Ejn4SkYrL5wvu5ErMt2DpGJMDp89C5nB-0-7333ffa2b1357696cd4e55f51c907d9f)
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=1738933619-AsJave0hhesW2b9Mfa0qAzAXNyIRVLlo-0-9fb20c9877fcae8a04bf8b927b12545e)
图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=1738933619-4FOZcQRwm3VHTZPYlvJY8iIzg9thdZdO-0-8e887aa3b0a0fdb8fb0c86747b347e49)
下面以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=1738933619-6Ksxds4S4Rd8kz8NwskAMzcCyFbqx6LI-0-1ba9d2bbaa64db3824f5d5dd9811532a)
图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=1738933619-7DNqiWiCaR4gHdWqNuigzk8I0oMHkQAG-0-de99d5c043ddd254dc8e6828fc94bb96)
图3.12 将编号为1的圆盘从塔座X移动到Z
![](https://epubservercos.yuewen.com/2EFA35/23083815801896206/epubprivate/OEBPS/Images/87_03.jpg?sign=1738933619-FT4N8dXQAOmvF1EvahlUuHjCNA3wHJup-0-da7948c454f3cf8101627a15a5a12e3e)
图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=1738933619-coSkCA7x01ODJrYSYUzZ4UgAs26drKSy-0-c20e29ebace130b2bffd22ead2e35026)
图3.14 将编号为3的圆盘从塔座X移动到Z
![](https://epubservercos.yuewen.com/2EFA35/23083815801896206/epubprivate/OEBPS/Images/87_05.jpg?sign=1738933619-0QIi1bkSx12pzsRnxWzYkx8rHb4EhCWL-0-7259eba6db6f5f77bc733dceedb69911)
图3.15 将编号为1的圆盘从塔座Y移动到X
![](https://epubservercos.yuewen.com/2EFA35/23083815801896206/epubprivate/OEBPS/Images/88_01.jpg?sign=1738933619-L3t5f2Ht58m8nCg2EknnHCI6wyzOEPqL-0-981c36ea1cb1469f3df7fb09b4bd57c6)
图3.16 将编号为2的圆盘从塔座Y移动到Z,编号为1的圆盘从塔座X移动到Z
在递归调用过程中,运行被调用函数前系统要完成3件事情:
(1)将所有参数和返回地址传递给被调用函数保存。
(2)为被调用函数的局部变量分配存储空间。
(3)将控制转移到被调用函数的入口。
当被调用函数执行完毕,返回到调用函数之前,系统同样需要完成3个任务:
(1)保存被调用函数的执行结果。
(2)释放被调用函数的数据存储区。
(3)将控制转到调用函数的返回地址处。
在多层嵌套调用时,递归调用过程的原则是后调用的先返回,因此,递归调用是通过栈实现的。函数递归调用过程中,在递归结束前,每调用一次,就进入下一层。当一层递归调用结束时,返回到上一层。
为了保证递归调用的正确执行,系统设置了一个工作栈作为递归函数运行期间使用的数据存储区。每一层递归包括实在参数、局部变量及上一层的返回地址等构成一个工作记录。每进入下一层,就产生一个新的工作栈记录被压入栈顶。每返回到上一层,就从栈顶弹出一个工作记录。因此,当前层的工作记录是栈顶工作记录,被称为活动记录。递归过程产生的栈由系统自动管理,类似用户使用的栈。递归的实现本质上就是把嵌套调用变成栈实现。