算法分析与设计_2_递归算法设计技术

本文详细介绍了递归算法的设计技术,包括递归的定义、何时使用递归、递归模型、递归算法的执行过程以及如何将递归算法转化为非递归算法。通过实例分析了简单的选择排序、冒泡排序和n皇后问题的递归解决方案,并探讨了递归方程的计算方法,如特征方程求解和递归树方法。
摘要由CSDN通过智能技术生成

第二章 递归算法设计技术

1. 什么是递归

1.1 递归的定义

在定义一个过程或函数时出现调用本过程或本函数的成分,称之为递归

若调用自身,称之为直接递归;若过程或函数p调用过程或函数q,而q又调用p,称之为间接递归。任何间接递归都可以等价地转换为直接递归。

如果一个递归过程或递归函数中递归调用语句是最后一条执行语句,则称这种递归调用为尾递归

  • 一般来说,能够用递归解决的问题应该满足以下三个条件:

    • 需要解决的问题可以转化为一个或多个子问题来求解,而这些子问题的求解方法与原问题完全相同,只是在数量规模上不同
    • 递归调用的次数必须是有限的
    • 必须有结束递归的条件来终止递归
  • 示例:设计求n!(n为正整数)的递归算法

    int fun(int n){
         
        if(n == 1){
         
            return 1;
        }else{
         
            return fun(n-1) * n;
        }
    }
    // 直接递归函数
    // 尾递归函数
    

1.2 何时使用递归

  • 定义是递归的:有许多数学公式、数列等的定义是递归的。例如,求n!Fibonacci数列等。这些问题的求解过程可以将其递归定义直接转化为对应的递归算法

  • 数据结构是递归的

    • 有些数据结构是递归的,例如:单链表就是一种递归数据结构,其结点类型声明如下:

      typedef struct LNode{
             
      	ElemType data;
      	struct LNode *next;
      }LinkList;
      
    • 对于递归数据类型,采用递归的方法编写算法既方便又有效,例如:求一个不带头结点的单链表L的所有data域(假设为int型)之和的递归算法

      int Sum(LinkList *L){
             
          if(L == NULL){
             
              return 0;
      	}else{
             
              return (L->data + Sum(L->next));
          }
      }
      
    • 示例:分析二叉树的二叉链存储结构的递归性,设计非空二叉链bt中所有结点值之和的递归算法,假设二叉链的data域为int性

      // 二叉树采用二叉链存储结构,其结点类型定义如下:
      typedef struct BNode {
             
          int data;
          struct BNode *lchild, *rchild;
      } BTNode;
      
      int Sumbt(BTNode *bt){
             
          if(bt->lchild == NULL && bt->rchild == NULL){
             
              return bt->data;
          }else{
             
              return Sumbt(bt->lchild) + Sumbt(bt->lchild) + bt->data;
          }
      }
      
  • 问题的求解方法是递归的:有些问题的解法是递归的,典型的有Hanoi问题求解(盘片移动时必须遵守以下规则:每次只能移动一个盘片;盘片可以插在X、Y和Z中任一塔座;任何时候都不能将一个较大的盘片放在较小的盘片上)

    Hanoi(n, x, y, z)表示将n个盘片从x通过y移动到z上,递归分解的过程是:

    // “大问题”
    Hanoi(n, x, y, z);
    // 转化成的“小问题”
    Hanoi(n-1, x, z, y);
    move(n, x, z);
    Hanoi(n-1, y, x, z);
    

1.3 递归模型

递归模型是递归算法的抽象,它反映一个递归问题的递归结构

例如前面的递归算法对应的递归模型如下:

fun(1) = 1						(1)
fun(n) = n * fun(n-1)	n>1		(2)

其中,第一个式子给出了递归的终止条件,第二个式子给出了fun(n)的值与fun(n-1)的值之间的关系,我们把第一个式子称为递归出口,把第二个式子称为递归体

一般地,一个递归模型由递归出口和递归体两部分组成,前者确定递归到何时结束,后者确定递归求解时的递推关系

递归出口的一般格式:f(s1) = m1(这里的s1与m1均为常量,有些递归问题可能有多个递归出口)

递归体的一般格式:f(s(n+1)) = g(f(si), f(s(i+1)), ..., f(sn), cj, c(j+1), ..., cm)(其中,n、i、j、m均为正整数;这里的s(n+1)是一个递归“大问题”,si、s(i+1)、…、sn是递归“小问题”;cj、c(j+1)、 …、cm是若干个可以直接(用非递归方法)解决的问题,g是一个非递归函数,可以直接求值)

1.4 递归算法的执行过程

  • 一个正确的递归程序虽然每次调用的是相同的子程序,但它的参量、输入数据等均有变化

  • 在正常的情况下,随着调用的不断深入,必定会出现调用到某一层的函数时,不再执行递归调用而终止函数的执行,遇到递归出口便是这种情况

  • 递归调用是函数嵌套调用的一种特殊情况,即它是调用自身代码,也可以把每一次递归调用理解成调用自身代码的一个复制件

  • 由于每次调用时,它的参量和局部变量均不相同,因而也就保证了各个复制件执行时的独立性

  • 系统为每一次调用开辟一组存储单元,用来存放本次调用的返回地址以及被中断的函数的参量值

  • 这些单元以系统栈的形式存放,每调用一次进栈一次,当返回时执行出栈操作,把当前栈顶保留的值送回相应的参量中进行恢复,并按栈顶中的返回地址,从断点继续执行

  • 示例1:上述求n!的例子,求fun(5)时内部栈的变化及求解过程

    image-20240530160313066

    从以上过程可以得出:每递归调用一次,就需进栈一次,最多的进栈元素个数称为递归深度,当n越大,递归深度越深,开辟的栈空间也越大;每当遇到递归出口或完成本次执行时,需退栈一次,并恢复参量值,当全部执行完毕时,栈应为空

    归纳起来,递归调用的实现是分两步进行的:第一步是分解过程,即用递归体将“大问题”分解成“小问题”,直到递归出口为止;然后进行第二步的求值过程,即已知“小问题”,计算“大问题”

  • 示例2:Fibonacci数列

    int Fib(int n
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值