栈与递归

谁说我不能喝 提交于 2020-02-16 00:06:38

递归

若在一个函数、过程或数据结构定义的内部又直接(或间接)出现定义本身的应用,则称它们是递归。

  • 三种常使用递归的情况:
  • 定义是递归的
    例1:阶乘函数
long Fact(long n)
{
  if (n==0) return 1;  //递归终止的条件
  else return n*Fact(n-1);  //递归步骤
}

例2:Fibonacci数列

long Fib(long n)
{
  if(n==1||n==2) return 1;  //递归终止的条件
  else return Fib(n-1) + Fib(n-2);  //递归步骤
}

分解-求解的策略称为分治法
采用分治法进行递归求解问题需要满足的条件:
(1)能将一个问题转变成一个新问题,而新问题与原问题的解法相同或类同,不同的仅是处理的对象,并且这些处理对象更小且变化有规律
(2)可以通过上述转化而使问题简化
(3)必须有一个明确的递归出口,或称递归的边界

分治法:

void p(参数表)
{
  if(递归结束条件成立)可直接求解;  //递归终止条件
  else  p(较小的参数);  //递归步骤
}
  • 数据结构是递归的

链表是一种递归的数据结构,因为其结点LNode的定义由数据域data和指针域next组成,而指针域next是一种指向LNode类型的指针,即LNode的定义中又用到了其自身。
广义表,二叉树等也是典型的具有递归特性的数据结构。

在递归算法中,当递归结束条件条件成立只执行return操作时,分治法求递归问题的算法可简化为:
   void p(参数表)
   {
     if(递归结束条件不成立)
       p(较小的参数)}

例:遍历输出链表中各个结点的递归算法

void TraverseList(LinkList p)
{
  if(p==NULL) return; //递归终止
  else
  {
    cout<<p->data<<endl;  //输出当前结点的数据域
    TraverseList(p->next);  //p指向后继结点继续递归
  }

}
void TraverseList(LinkList p)
{
  if(p)
  {
    cout<<p->data<<endl;
    TraverseList(p->next);
  }
}
  • 问题的解法是递归的

一些问题本身没有明显的递归结构,但用递归求解比迭代求解更简单,例如Hanoi塔问题、八皇后问题、迷宫问题等。

例:n阶Hanoi塔问题
在这里插入图片描述

int m=0;
void move(char A,int n,char C)
{
  cout<<++m<<","<<n<<","<<A<<","<<C<<endl;
}
void Hanoi(int n,char A,char B,char C)
{ //将塔座A上的n个圆盘按规则搬到C上,B做辅助他if(n==1) move(A,1,C); //将编号为1的圆盘从A移到C
   else
   {
     Hanoi(n-1,A,C,B);  //将A上编号为1至n-1的圆盘移到B,C做辅助塔
     move(A,n,C);  //将编号为n的圆盘从A移到C
     Hanoi(n-1,B,A,C);  //将B上编号为1至n-1的圆盘移到C,A做辅助塔
   }
}

函数调用

  • 一个函数运行期间调用另一个函数时,在运行被调用函数之前,系统需先完成:
    (1)将所有的实参、返回地址等信息传递给调用函数保存
    (2)为被调用函数的局部变量分配存储区
    (3)将控制转移到被调函数的入口

  • 从被调函数返回调用函数之前,系统需先完成:
    (1)保存被调函数的计算结果
    (2)释放被调函数的数据域
    (3)依照被调函数保存的返回地址将控制转移到调用函数

递归工作栈

  • 求解 4!活动记录进栈过程
    在这里插入图片描述
  • 求解 4!活动记录出栈过程
    在这里插入图片描述

递归算法的效率分析

1: 时间复杂度的分析

longFact(long n)
{
  long temp; 
  if(n==0) return 1; //活动记录退栈(1)
  else temp=n*Fact(n-1); //活动记录进栈(2)
  return temp;  //活动记录退栈(3)
} 

设Fact(n)的执行时间是T(n)。此递归函数中if(n==0) return 1;的时间复杂度为O(l),递归调用Fact(n-1)的执行时间是T(n-1),所以else temp=n*Fact(n-1);的执行时间是O(l)+T(n-1)。设两数相乘和赋值的时间复杂度都是O(l),则对某常数C、D有如下递归方程
在这里插入图片描述
设n>2,利用上式对T(n-1)展开,即在上式中用n-1代替n得到

T(n-1)=C+T(n-2)

再代入T(n)=C+T(n-1)中,有

T(n)=2C+T(n-2)

同理,n>3时有

T(n)=3C+T(n-3)

以此类推,当n>i时有

T(n)=iC+T(n-i)

最后,当i=n时有

T(n)=nC+T(0)=nC+D

求得递归方程的解为:

T(n)=O(n)

采用这种方法计算Fibonacci数列和Hanoi塔问题递归算法的时间复杂度为O(2^n)

2:空间复杂度的分析
递归函数在执行时,系统会建立一个“递归工作栈”存储每一层递归所需的信息,此工作栈是递归函数执行的辅助空间,因此,分析递归算法的空间复杂度需要分析工作栈的大小。

对于递归算法,空间复杂度
S(n)=O(f(n))

其中,f(n)为“递归工作栈”中工作记录的个数与问题规模n的函数关系。

由此,前面讨论的阶乘问题、Fibonacci数列问题、Hanoi塔问题的递归算法的空间复杂度为O(n)。

递归转换为非递归

  • 利用栈消除递归过程的步骤:
    (1)设置一个工作栈存放递归工作记录(包括实参,返回地址及局部变量等)
    (2)进入非递归调用入口(即被调用程序开始处)将调用程序传来的实在参数和返回地址入栈(递归程序不可以作为主程序,因而可认为初始是被某个调用函数调用
    (3)进入递归调用入口:当不满足递归结束条件时,递层递归,将实参、返回地址及局部变量入栈,此过程可用循环语句实现—模拟递归分解的过程
    (4)递归结束条件满足,将到达递归出口的给定常数作为当前的函数值
    (5)返回处理:在栈不空的情况下,反复退出栈顶记录,根据记录中的返回地址进行题意规定的操作,即逐层计算当前函数值,直至栈空为止—模拟递归求值过程

递归算法优点

结构清晰,程序易读,正确性容易得到证明

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!