数据结构与实训
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

3.3 栈的典型应用与递归算法

3.3.1 栈的典型应用——子程序的调用和返回

栈的典型应用之一是子程序的调用和返回,一个程序可以由多个子程序(C语言为函数)构成,其中的一个子程序可以调用另一个子程序,这种调用者和被调用者之间的联系(衔接)就是通过堆栈实现的。程序控制在调用者和被调用者之间转换时,系统要做以下几件事情。

(1)将当前调用者的所有实参、临时(局部)变量、调用子程序后的返回地址等保存起来。

(2)为被调用者的局部变量分配存储空间。

(3)转向被调用者执行。

当程序中存在多层次的嵌套调用时,从被调用者返回调用者的顺序是后调用的先返回,这种调用、返回关系符合堆栈的特点,可以借助于堆栈来实现。当程序从调用者转向被调用者时,系统将调用者的所有实参、临时(局部)变量、调用子程序后的返回地址等压入堆栈,称为保护现场;调用结束,系统再执行出栈操作,称为恢复现场,并根据出栈后得到的返回地址返回到调用者继续运行。

以下C程序中,main函数调用了fun1,fun1又调用了fun2,这三者之间的调用与返回就是通过堆栈的进栈、出栈操作实现的。如图3-6所示是函数的嵌套调用示意图。

int fun1(int p);
int fun2(int p1,int p2);
main()
{ int n,result;
…

   result=fun1(n);
   …
}
fun1(int p)
{ int n1,n2,result;
   …
   result=fun2(n1,n2);
   …
}
fun2(int p1,int p2)
{
   …
}

图3-6 函数的嵌套调用示意图

3.3.2 递归算法

存在直接或间接调用自身(己)的算法称为递归算法,又称为自调用算法。递归过程是借助于堆栈实现的,只不过这个过程是系统内部自动完成的,递归可分为直接递归与间接递归。

(1)直接递归。算法直接调用自身,如图3-7(a)所示。

(2)间接递归。一个算法在调用另一算法时,另一算法又产生了对该算法的调用,如图3-7(b)所示。

以下是根据公式:

求解n!的递归算法。

long fun(int n)
{  long y;
   if(n==1)
       return(1);
   else
   {  y=fun(n-1);
      return(n*y);
   }
} /* fun */

图3-7 直接递归与间接递归

递归算法由递归出口和递归体两部分组成,上述求n!的递归算法中,n!=1(n=1时)为递归出口,n!=n×(n-1)!(n>1时)为递归体。

递归算法的分析设计是自顶向下的求解过程。对于一个不易直接求解的大问题,将其转化成一个或几个有同样求解过程的小问题来解决,再把这些小问题进一步分解为更小的小问题来解决,如此分解,直至每个小问题都可以直接解决(到达递归出口)为止。

3.3.3 递归算法的执行过程

仍然以求n!的算法为例,进一步分析该算法的执行过程。递归算法本质上是函数的嵌套调用,只是此处的被调函数是自己,每一次的嵌套调用改变的只是函数的参数,以fun(4)为例,其执行过程如图3-8所示,相应的系统内部栈的工作过程如图3-9所示。

图3-8 fun(4)的递归执行过程

图3-9 系统内部栈的工作过程

【例3-8】对下面的递归算法,写出调用f(4)的执行结果。

void f(int k)
{
    if(k>0)
        {
          printf(k);
          f(k-1);
          f(k-1);
        }
} /*f*/

f(4)的执行过程可用图3-10表示,图中从左到右的缩进是递归的嵌套调用,同一层次自上而下是顺序关系。由此可知f(4)的执行结果是输出15个值:

4,3,2,1,1,2,1,1,3,2,1,1,2,1,1

图3-10 f(4)执行过程表示