当前位置: 首页 > news >正文

数据结构与算法--递归

文章目录

    • 回顾
    • 提要
    • 1.递归的定义
      • 1.1 何时使用递归
      • 1.2 定义使用递归
        • 阶乘公式的定义
        • 斐波那契数列的定义
      • 1.3 递归数据结构的应用
        • 单链表结构
        • 查找链表最后一个结点并打印其数值
        • 在链表中查找等于给定值的结点并打印其数值
        • 求一个不带头结点的单链表值域之和
    • 2.递归的执行过程
    • 3.递归的实现
      • 3.1 直接递归与间接递归示例
      • 3.2 递归工作栈
    • 4.递归的设计方法
      • 4.1 示例:汉诺塔问题的算法实现
      • 4.2递归算法的基本特性
    • 5.递归算法到非递归算法的转换
      • 5.1 递归算法与非递归算法的对比
      • 5.2 递归算法的选择建议
    • 6.总结

回顾

  • 栈:后进先出 (LIFO)
  • 队列:先进先出 (FIFO)
  • 链队列的4要素
    • 循环队列的下标运算:( (X+1) % Max )
    • 队空条件:( front == rear )
    • 队满条件:( (rear + 1) % MaxSize == front )
    • 队列长度:( (rear - front + MaxSize) % MaxSize )

提要

  1. 递归的定义
  2. 递归的执行过程
  3. 递归的实现
  4. 递归的设计方法
  5. 递归算法到非递归算法的转换

1.递归的定义

  • 直接递归:函数直接调用自己

  • 在这里插入图片描述

  • 间接递归:函数间接调用自己

  • 在这里插入图片描述

  • 尾递归:求n!(n为正整数)的递归函数。

  • 函数fac(n) 直接调用fac(n-1)自身,是直接递归。

  • 递归调用是最后一条语句,又属于尾递归。

  • 在这里插入图片描述

1.1 何时使用递归

在以下三种情况下,常常用到递归方法:

  1. 定义使用递归:许多数学公式、数列等的定义是递归的。
  2. 数据结构中使用递归:某些数据结构天然支持递归操作。
  3. 问题的解法使用递归:某些问题的解法本质上是递归的。

1.2 定义使用递归

阶乘公式的定义

阶乘公式是一个典型的递归定义的例子,其递归关系可以表示为:

  • ( n! = n \times (n-1)! ) 当 ( n > 1 )
  • ( 1! = 1 )
斐波那契数列的定义

斐波那契数列也是一个递归定义的例子,其递归关系为:

  • ( F(n) = F(n-1) + F(n-2) ) 当 ( n > 1 )
  • ( F(0) = 0 )
  • ( F(1) = 1 )

以下是斐波那契数列的递归实现代码:

int Fib(int n) {if (n <= 1) return n;else return Fib(n - 1) + Fib(n - 2);
}

1.3 递归数据结构的应用

单链表结构

单链表是一种递归数据结构,因为它的每个元素都包含指向下一个元素的指针。
在这里插入图片描述

在这里插入图片描述

查找链表最后一个结点并打印其数值

对于递归数据结构,采用递归的方法编写算法既方便又有效。
以下是使用递归在单链表中查找最后一个结点的代码示例:

void Find(LinkNode *p) {if (p->next == NULL) {std::cout << p->data << std::endl;} else {Find(p->next);}
}
在链表中查找等于给定值的结点并打印其数值

以下是使用递归在单链表中查找等于给定值的结点的代码示例:
在这里插入图片描述

void FindItem(LinkNode *p, ElemType x) {if (p != NULL) {if (p->data == x) std::cout << p->data << std::endl;else FindItem(p->next, x);}
}
求一个不带头结点的单链表值域之和

以下是使用递归求单链表值域之和的代码示例:
在这里插入图片描述

int Sum(LinkNode *p) {if (p == NULL) return 0;else return p->data + Sum(p->next);
}

2.递归的执行过程

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

    • 例如, 阶乘递归算法对应的递归模型如下:

    • fun(1)=1 n=1 (1)

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

    • 第一个式子是递归的终止条件,称为递归出口

    • 第二个式子是fun(n)的值与fun(n-1)的值之间的关系,称为递归体

  • 递归出口:终止条件
  • 递归体:大问题与小问题的关系
  • 递归模型是由递归出口递归体两部分组成。
    • 递归体表示“大问题”与**“小问题”**的关系。
    • 递归出口为“最小问题”,可以直接求得结果。
  • 递归的执行过程由分解和求值两部分构成:
    • (1)分解是指把“最大问题”逐步分解为“小问题”直至“最小问题”;
    • (2)求值是指根据求得的“最小问题”的值重新代入递归体,最终得到“最大问题”的值。

3.递归的实现

  • 递归函数的运行过程类似于多个函数的嵌套调用,差别仅在于“调用函数和被调用函数是同一个函数”。
  • 递归调用在内部实现时并不是把每次调用所需的代码都放入内存,而是采取代码共享的方式。
  • 系统为每次调用开辟一组存储单元,用来存放本次调用的相关数据。

3.1 直接递归与间接递归示例

// 直接递归示例:计算阶乘
int fac(int n) {if (n == 0 || n == 1) return 1;else return n * fac(n - 1);
}// 间接递归示例:斐波那契数列
int Fib(int n) {if (n <= 1) return n;else return Fib(n - 1) + Fib(n - 2);
}

3.2 递归工作栈

  • 为了保证“每一层的递归调用”都是对“本层”的数据进行操作,在执行递归函数的过程中需要一个“递归工作栈”。它的作用是:

  • 将递归调用时的实参和函数返回地址传递给下一层执行的递归函数;

  • 保存本层的参数局部变量,以便从下一层返回时重新使用它们。

  • 在这里插入图片描述

  • 在这里插入图片描述

4.递归的设计方法

  • 递归函数的一般设计格式
    • 递归出口:满足条件直接返回
    • 递归项:返回较小问题的递归调用
    • 在这里插入图片描述

4.1 示例:汉诺塔问题的算法实现

  • 任务:将塔座A上的圆盘移动到塔座C上。
  • 移动规则:1. 每次移动一个圆盘;
    2. 圆盘只能在塔座A,B,C之间移动;
    3. 大圆盘不能放在小圆盘上。
    4. 在这里插入图片描述
void Hanoi(int n, char X, char Y, char Z) {if (n == 1) {std::cout << X << "->";std::cout << Z << "\t";} else {Hanoi(n - 1, X, Z, Y);std::cout << X << "->";std::cout << Z << "\t";Hanoi(n - 1, Y, X, Z);}
}

在这里插入图片描述

汉诺塔问题的求解过程可以通过递归的方式进行,下面我将描述汉诺塔问题的求解流程图。

n == 1
n > 1
开始
确定圆盘数 n
将一个圆盘从源塔移动到目标塔
确定辅助塔
将 n-1 个圆盘从源塔借助目标塔移动到辅助塔
将剩余圆盘从源塔移动到目标塔
将 n-1 个圆盘从辅助塔借助源塔移动到目标塔
完成

这个流程图从确定圆盘数开始,然后根据圆盘数是1还是大于1来决定接下来的步骤。如果只有一个圆盘,就直接移动到目标塔。如果有多个圆盘,就需要确定一个辅助塔,先将除了最大的圆盘之外的所有圆盘移动到辅助塔上,然后将最大的圆盘移动到目标塔,最后将之前移动到辅助塔上的圆盘移动到目标塔。

4.2递归算法的基本特性

  • 分而治之:有效解决复杂问题
  • 时间效率:通常较差,有时需转换为非递归算法

5.递归算法到非递归算法的转换

  • 直接转化:尾递归,用循环结构替代
  • 间接转化:使用栈模拟系统栈,自己用栈模拟系统的运行时栈,通过分析只保存必须保存的信息。
  • 间接转化,需要使用系统栈:利用系统栈保存参数。由于系统不了解程序的业务逻辑,仅从技术上转换,有可能保存不必要的信息。

5.1 递归算法与非递归算法的对比

  • 递归:易于理解,结构清晰,但可能需要大量系统栈操作,压入函数参数和返回地址,递归的层次与系统的栈大小受限有关。
  • 非递归:效率较高,但理解难度大,在从递归向非递归转换的时候需要大量的模仿栈操作。

5.2 递归算法的选择建议

  • 简单问题:尽量使用循环解决
  • 复杂或明显的递归问题:使用递归,易于理解和维护

6.总结

  • 递归的定义和模型
  • 递归的执行过程
  • 递归设计的一般方法
  • 消除递归的基本方法
  • 运用递归算法解决复杂应用问题

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • Terminator的分割终端功能确实非常实用,特别是当你需要同时监控多个任务时,快捷键来分割窗口
  • 计算机专业英语词汇
  • 无人机长生不老秘籍
  • C++之移动语义与左值右值深入学习:从入门到精通!
  • leetcode每日一题46
  • 《数据结构》(C语言版)第1章 绪论(下)
  • C语言 ——— 在控制台实现扫雷游戏(一次展开一片,递归实现)
  • java之静态内部类
  • 国内顶级 AI 的回答令人“贻笑大方”:看来苹果秃头码农们暂时还不会失业吧?
  • vue3+vite全局引入less变量和函数
  • playwrite今日头条自动发帖
  • 未授权访问漏洞
  • 对于泛型以及泛型擦除的理解
  • Ubuntu防火墙相关命令
  • 【轨物推荐】什么是科学?什么是技术?
  • -------------------- 第二讲-------- 第一节------在此给出链表的基本操作
  • Android Volley源码解析
  • ES2017异步函数现已正式可用
  • es6(二):字符串的扩展
  • HTTP 简介
  • java B2B2C 源码多租户电子商城系统-Kafka基本使用介绍
  • JavaScript DOM 10 - 滚动
  • Redis中的lru算法实现
  • TiDB 源码阅读系列文章(十)Chunk 和执行框架简介
  • 不发不行!Netty集成文字图片聊天室外加TCP/IP软硬件通信
  • 基于MaxCompute打造轻盈的人人车移动端数据平台
  • 力扣(LeetCode)357
  • 批量截取pdf文件
  • 事件委托的小应用
  • 小程序滚动组件,左边导航栏与右边内容联动效果实现
  • mysql 慢查询分析工具:pt-query-digest 在mac 上的安装使用 ...
  • # windows 运行框输入mrt提示错误:Windows 找不到文件‘mrt‘。请确定文件名是否正确后,再试一次
  • $LayoutParams cannot be cast to android.widget.RelativeLayout$LayoutParams
  • (2024,Flag-DiT,文本引导的多模态生成,SR,统一的标记化,RoPE、RMSNorm 和流匹配)Lumina-T2X
  • (55)MOS管专题--->(10)MOS管的封装
  • (ZT)一个美国文科博士的YardLife
  • (待修改)PyG安装步骤
  • (附源码)c#+winform实现远程开机(广域网可用)
  • (附源码)计算机毕业设计SSM基于java的云顶博客系统
  • (七)Java对象在Hibernate持久化层的状态
  • (三十)Flask之wtforms库【剖析源码上篇】
  • (转)我也是一只IT小小鸟
  • (转载)在C#用WM_COPYDATA消息来实现两个进程之间传递数据
  • (轉貼) UML中文FAQ (OO) (UML)
  • .libPaths()设置包加载目录
  • .NET 3.0 Framework已经被添加到WindowUpdate
  • .NET COER+CONSUL微服务项目在CENTOS环境下的部署实践
  • .Net 转战 Android 4.4 日常笔记(4)--按钮事件和国际化
  • .net/c# memcached 获取所有缓存键(keys)
  • .NET6实现破解Modbus poll点表配置文件
  • .netcore 如何获取系统中所有session_ASP.NET Core如何解决分布式Session一致性问题
  • .NET简谈互操作(五:基础知识之Dynamic平台调用)
  • .net项目IIS、VS 附加进程调试
  • .NET与java的MVC模式(2):struts2核心工作流程与原理
  • /proc/stat文件详解(翻译)