http://www.nhyz.org/nhxi/nhxi/jiegousuanfa/jiegou22.htm
§2.2 栈的简单应用和递归
栈是一种应用范围广泛的数据结构,适用于各种具有"后进先出"特性的问题。本节着重讨论与过程调用、特别是与递归这间的关系,力图在说明如何用栈解决实际问题的同时阐明递归的基本概念,这一概念是下一章的重要基础。
一、栈与过程调用
考虑下述三个过程:
procedure A1(…); procedure A2(…); procedure A3(…);
begin begin begin
┇ ┇ ┇
r:A2(…); t:A3(…); end;
r1:┇ t1: ┇
end; end;
这三个过程这间存在下述调用关系:过程A1在其过程体的某一处调用过程A2,A2以在其过程体的某一处调用过程A3,A3不调用其他过程。因此,当过程A1执行到的r处时,它自己实际上被"挂起来,而被调用过程A2开始运行。一直等到A2执行完毕这后才返回过程A1的r1处继续执行A1剩下部分。 在过程A2的上述运行中,由于调用了A3,A2同样在t处"挂"起并一直等到A3执行结束后返回t1处才能继续执行后继语句。这三个过程这间的调用——返回关系及运行次序如图3-4(a)所示。
A1 |
|
A2 |
②┌ ┌┈┘ ┈┘ |
A3 →┐ │ │ |
r1┌ |
←┐④ |
t1┌ |
←┐③ └┈┐ └ |
│ ↓ ┈┘ |
图3-4(a) 嵌套过程调用─返回次序
|
|
|
|
|
|
|
|
|
||||||||||||||||
top→ |
调用A2前 |
调用A2后 |
调用A3后 |
返回A2后 |
top→ |
返回A1后 |
图3-4(b) 相应的工作栈状态变化
当一个普通用户用某种高级程序设计语言写出具有上述关系的三个过程并上机运行时,他无需考虑这三个过程之间的上述调用——返回关系是如何控制实现的。有关的控制工作由编译系统自动完成。显然,实现这种控制所要解决的主要问题是在每调用一个过程(如A2)时如何保存返回位置(如位置r1),使得能调用其他过程(如A3),保存的返回位置便不止一个(如除r1外还有t1)。这就产生了如何安排它们之间的次序的问题。仔细观察会发现:上述"多重嵌套"的过程的调用次序与恰好相反。从图3-4(a)可看出,A1对A2的调用(由箭头①表示)先于A2对A3的调用(箭头②);但从A到A1的返回(箭头④)迟于从A3到A2的返回(箭头③)。这就说明:先保存的位置后返回,后保存的位置先返回。因此,应该用栈作哦数据结构。
事实上,编译系统也下正是这么做的。所用的栈称哦"工作栈"。每遇到一个过程调用便立刻将相应的返回位置(及其它有用的信息)进栈;每当一被调用过程执行结束时,工作栈栈顶元素下好是此过程的返回位置。对于上述过程A1、A2和A3的执行,相应的工作栈状态变化如图3-4(b)所示(这里假定A1是主程序;否则调用A1时相应的返回位置也要进栈)。
二、递归与栈
递归是一个重要的概念,同时也是一种重的程序设计方法。简单地说,如果在一个函数、过程或数据结构的定义中又应用了它自身(作为定义项之一),那么这个函数、过程或数据结构称为是递归的定义的,简称递归。例如,阶乘函数可递归定义如下:
必须注意,递归定义不能是"循环定义"。为此要求任可递归定义必须同时满足如下两个条件:
①被定义项在定义中的应用(即作为定义项的出现)具有更小的"尺度";
②被定义项在最小"尺度"上的定义不是递归的。
例如,在上述阶乘函数的定义中,被定义项n!在定义中的应用(n-1)!具有比原来(即n)更小的"尺度"(即n-1)。同时,n!在最小"尺度"(即0)上的定义不是递归的(由自然数1直接定义)。这两个条件实际上构成了递归程序设计的基本原则。此外,通常将反映条件②的部分写在递归过程的开头。
很多实际递归定义的。对于这些问题很容易写出求解它们的递归算法。计算阶乘函数的递归算法如下:
function f(n:integer):integer;
//计算n!的递归算法。假定输入n≥0//
begin
if n=0
then retuen(1)
else return(n*f(n-1))
end;
递归过程的运行引起递归调用。例如,f(3)的执行中出现的递归调用——返回过程如图3-5(a)所示。
f3 |
|
f2 |
|
f1 |
|
f0 →┐ │ │ |
r1┌ |
←┐ |
t1┌ |
←┐ |
t1┌ |
←┐┈┈ |
│ ↓ ┈┘1 |
图3-5(a) f(3)执行递归调用─返回次序
|
|
|
|
|
|
|
|
|
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
top→ |
调用f2前 |
调用f2后 |
调用f1后 |
调用f0后 |
返回f1后 |
返回f2后 |
top→ |
返回 f3后 |
图3-5(b) 相应的工作栈状态变化
递归调用——返回的控制与非递归过程的控制并无本质区别,同样可由一个工作栈实现。对上述过程f,返回位置r应设在内、外层return语句这间。为了区别返回到哪一级递归,可在返回位置进栈的同时将该次调用的参数一起保存。图3-5(b)所示为与(a)相应的工作栈状态变化过程。