数据结构—栈与递归

1.递归的基本思想

  • 所谓递归,就是有去有回。
  • 递归的基本思想,是把规模较大的一个问题,分解成规模较小的多个子问题去解决,而每一个子问题又可以继续拆分成多个更小的子问题。
  • 最重要的一点就是假设子问题已经解决了,现在要基于已经解决的子问题来解决当前问题;或者说,必须先解决子问题,再基于子问题来解决当前问题。
  • 或者可以这么理解:递归解决的是有依赖顺序关系的多个问题。

我们假设一个抽象问题有两个时间点要素:开始处理,结束处理。那么递归处理的顺序就是,先开始处理的问题,最后才能结束处理。
假设如下问题的依赖关系:

  • 【A】----依赖---->【B】----依赖---->【C】
  • 我们的终极目的是要解决问题A,
  • 那么三个问题的处理顺序如下:
  • 开始处理问题A;
  • 由于A依赖B,因此开始处理问题B;
  • 由于B依赖C,开始处理问题C;
  • 结束处理问题C;
  • 结束处理问题B;
  • 结束处理问题A。

2.从函数调用看广义递归

  • 对于软件来说,函数的调用关系就是一个广义递归的过程,如下,
func_A()
{
    func_B();
}
func_B()
{
    func_C();
}
func_C()
{
    /////
}
  • 调用函数A;
  • 调用函数B;
  • 调用函数C;
  • 函数C返回;
  • 函数B返回;
  • 函数A返回;

3.狭义递归函数

  • 有一种特例,就是处理问题A/B/C的方法是一样的,这就是产生了狭义的“递归函数“,即函数内又调用函数自身。
  • 从上述分析看,递归对问题的处理顺序,是遵循了先入后出(也就是先开始的问题最后结束)的规律。
  • 先入后出?栈!
  • 没错,广义递归问题的处理,需要用栈来解决。经典的例子就是函数调用,就是依靠栈来实现的

4.递归函数的非递归化

  • 现在再来深入分析一下狭义的递归函数(也就是函数调用自身)。
  • 我们知道递归函数存在的最大问题是,当递归次数足够大时,会导致函数栈溢出而死机,函数栈的大小一般是一个固定值,对于linux来说一般默认是8M。
  • 因此,编程老司机会教导我们,不得用递归函数!但递归函数的代码实现实在是简洁啊,不让用?臣妾做不到啊!
  • 那么问题来了,所有递归函数都能非递归化吗?答案是肯定的。
  • 本质上讲,对于同一个问题,如果必然要用广义递归的方案来处理,那么狭义递归函数只不过是其中的一种实现方式,如果放弃狭义递归函数的话,我们不得不借助一个额外的数据结构:
  • 如此看来,无论如何都要用到栈,只不过要么让编译器来维护一个栈(函数栈),要么让程序狗来维护一个栈(数据栈)。
  • 这两个栈的区别如下:

  • 举例说明:二叉树的非递归遍历

5.非递归算法的递归化

  • 既然递归算法可以用数据栈来进行非递归化,那么借助数据栈而实现的非递归算法,理论上也可以被递归化。也就是说,两者是可逆的,桥梁就是栈。

6.C语言中常说“局部变量在栈上分配空间”,那么这个地方的栈和栈数据结构有关系吗?

  • 程序中的“函数调用栈”是栈数据结构的一种应用
  • 函数调用栈一般是从高地址向低地址增长
  • 栈底为内存的高地址处
  • 栈顶为内存的低地址处
  • 函数调用栈中存储的数据为活动记录活动记录是函数调用时一系列相关信息的记录)。

7.函数调用过程

8.程序中的栈

  • 程序中的栈空间可看做一个顺序栈的应用,程序栈空间的访问是通过函数调用进行的,程序栈空间仍然遵从后进先出的规则
  • 栈保存了一个函数调用所需的维护信息
    • 函数参教,函数返回地址
    • 局部变量
    • 画数调用上下文
  • 什么是程序的栈溢出
    • 在不断的压栈过程中造成栈空间耗尽而产生栈溢出
    • 栈溢出常由于函数递归过深或局部数组过大造成

9.以下三种情况常常用到递归方法:

  • 递归定义的数学函数
    • 阶乘函数:                   
    • 二阶Fibonaci数列:   
  • 具有递归特性的数据结构:
    • 树 :                   
    • 广义表:                        A=(a,A)
  • 可递归求解的问题:
    • 迷宫问题 Hanoi塔问题

10.用分治法求解递归问题

  • 分治法:对于一个较为复杂的问题,能够分解成几个相对简单的且解法相同或类似的子问题来求解。
  • 必备的三个条件:
    • 1、能将一个问题转变成一个新问题,而新问题与原问题的解法相同或类同,不同的仅是处理的对象,且这些处理对象是变化有规律的
    • 2、可以通过上述转化而使问题简化
    • 3、必须有一个明确的递归出口,或称递归的边界。
  • 分治法求解递归问题算法的一般形式:      
  • void   p (参数表) {        
    • if   (递归结束条件)可直接求解步骤;-----基本项        
    • else  p(较小的参数);------归纳项        

}

猜你喜欢

转载自blog.csdn.net/qq_22847457/article/details/94389271