递归算法在编程中的重要应用

news2024/11/23 9:06:27

递归算法在编程中的重要应用

  • 引言
  • 一、引言
    • 1.1、什么是递归算法?
    • 1.2、递归算法的特点和优缺点
  • 二、树和图的遍历
    • 2.1、深度优先搜索(DFS)和广度优先搜索(BFS)
    • 2.2、二叉树遍历、树的深度、节点个数等问题
      • 2.2.1、二叉树遍历
      • 2.2.2、树的深度
      • 2.2.3、节点个数
  • 三、排序和查找算法
    • 3.1、快速排序
    • 3.2、归并排序
    • 3.3、二分查找
  • 四、动态规划问题
  • 五、回溯算法
  • 六、分治设计模式
  • 七、总结

引言


💡 作者简介:专注于C/C++高性能程序设计和开发,理论与代码实践结合,让世界没有难学的技术。包括C/C++、Linux、MySQL、Redis、TCP/IP、协程、网络编程等。
👉
🎖️ CSDN实力新星,社区专家博主
👉
🔔 博客主页:https://blog.csdn.net/Long_xu


一、引言

1.1、什么是递归算法?

递归算法的核心思想就是函数自己调用自己。

递归算法包括两个部分:基本情况和递归情况。基本情况是指一些简单、不需要继续拆分的子问题,而递归情况则是通过反复调用自身来处理较为复杂的子问题。

一个典型的递归函数通常包括以下几个要素:

  1. 终止条件:每个递归过程都应该有一个能够使得程序停止执行并返回结果的结束条件。
  2. 逐层拆分:在每次递归中,问题规模应该比上一次缩小,并且与上一次规模相同但数据不同,以便于最终到达基本情况。
  3. 传参调用:在每次调用时将参数传入函数内部,并对参数进行修改或者处理。
  4. 合并结果:当遇到最小子问题时,应该直接返回结果;否则需要通过某种方式合并各级别子任务得到最终答案。
  5. 调用自身:在符合前面几点要求后,在函数内部可以通过调用自身来实现递归过程。

递归算法的优点在于,它可以极大地简化程序的设计和实现,同时能够解决许多复杂的问题。然而,如果不合理使用递归可能会导致内存溢出等问题。因此,在实际编程中需要根据具体情况来选择使用递归或者非递归方法来解决问题。
在这里插入图片描述

递归算法包括三个基本要素:基线条件、递归条件和自我调用。下面分别来介绍一下这三个概念。

  1. 基线条件(Base Case):也称为停止条件,是指递归函数的结束点,当达到此条件时,递归将不再执行。基线条件通常是一个简单的问题,可以直接求解而无需进一步的递归操作。例如,在计算阶乘的过程中,n=0或者n=1时,就可以直接返回结果1作为基线条件。
  2. 递归条件(Recursive Case):是指在每次调用自身之前必须满足的条件。它描述了如何将问题规模缩小,并且保证函数最终能够收敛于基线情况。通常情况下,在此之前需要对原始问题进行分解和转化,并定义好新问题与原问题间的关系。例如,在计算阶乘的过程中,递归条件就是当n>1时需要调用自身并传入参数(n-1)来缩小规模。
  3. 自我调用(Self-call):也叫做“递推”,是指在一个函数内部调用自身来完成相同或者相似的任务。通过自我调用,程序可以重复执行某些任务直至满足某个特定目标而停止。例如,在计算斐波那契数列的过程中,需要使用自我调用来重复计算前两项之和,并不断向后推进。

在这里插入图片描述

1.2、递归算法的特点和优缺点

优点:

  1. 简单明了:递归算法通常比迭代算法更容易理解和实现。特别是对于一些树形结构、图形问题或具有自相似性质的问题,递归思想非常自然。
  2. 代码简洁:使用递归可以大幅减少代码量,同时也降低了程序出错的概率。因为递归代码往往比较清晰易懂,调试起来也相对方便。
  3. 面向问题本身:递归方式更能体现出问题本身的特点和求解方法,尤其是对于那些需要分治或者动态规划等高级算法的问题。

缺点:

  1. 可能导致堆栈溢出:由于递归过程会不断压入函数调用栈,如果遇到深度较大或者规模较大的问题,就可能会导致堆栈溢出(Stack Overflow)的风险。
  2. 效率不高:与迭代算法相比,在时间复杂度和空间复杂度上都存在一定程度上的劣势,尤其是对于大规模问题,递归算法的效率可能会非常低下。
  3. 可读性差:如果递归代码不够清晰明了,就容易让人看不懂、难以理解。而且在嵌套过多的情况下,代码结构也可能变得比较复杂和混乱。

使用递归算法需要注意控制好递归深度和程序运行效率。在实际开发中,可以根据具体情况采用迭代算法、动态规划等其他方式来替代或优化递归算法。

二、树和图的遍历

2.1、深度优先搜索(DFS)和广度优先搜索(BFS)

深度优先搜索(DFS)和广度优先搜索(BFS)是两种常用的图形遍历算法,它们都可以用递归实现。下面分别介绍一下这两种算法的原理、应用场景及如何使用递归来实现。

(1)深度优先搜索(Depth-First Search, DFS)是一种图形遍历算法。从起点出发,访问第一个相邻节点,然后递归地访问该节点的第一个未被访问过的相邻节点,直到所有能够到达的节点都被访问为止。
在这里插入图片描述

深度优先搜索应用场景:DFS 通常用于解决连通性问题,比如求解图中的连通分量、判断是否存在环等。

深度优先搜索的递归实现:

def dfs(node, visited):
    # 如果当前节点已经被访问,则返回
    if node in visited:
        return
    # 标记当前节点为已访问
    visited.add(node)
    # 访问当前节点的所有相邻节点
    for neighbor in node.neighbors:
        dfs(neighbor, visited)

(2)广度优先搜索(Breadth-First Search, BFS)是一种图形遍历算法。从起点出发,按照距离由近及远依次访问每个节点的所有相邻节点,直到所有能够到达的节点都被访问为止。
在这里插入图片描述

BFS 通常用于解决最短路径问题,比如在图中求两点之间的最短路径、求解迷宫等。

广度优先搜索的实现:由于 BFS 本质上是一种“广度优先”的遍历方式,因此使用递归并不是很自然。我们可以使用队列(Queue)来实现非递归的 BFS 算法。

def bfs(start, end):
    queue = [(start, [start])]
    while queue:
        (node, path) = queue.pop(0)
        for neighbor in node.neighbors:
            if neighbor not in path:
                if neighbor == end:
                    return path + [neighbor]
                else:
                    queue.append((neighbor, path + [neighbor]))

2.2、二叉树遍历、树的深度、节点个数等问题

二叉树是一种常见的数据结构,在实际开发中经常会使用到。通过递归算法,可以轻松地解决二叉树遍历、树的深度、节点个数等问题。

2.2.1、二叉树遍历

(1)前序遍历指先访问节点,然后依次访问其左子树和右子树。可以使用递归算法来实现前序遍历。

def preorder_traversal(root):
    if root is None:
        return []
    result = [root.val]
    left = preorder_traversal(root.left)
    right = preorder_traversal(root.right)
    return result + left + right

(2)中序遍历指先访问节点的左子树,然后访问节点本身,最后访问其右子树。同样可以使用递归算法来实现中序遍历。

def inorder_traversal(root):
    if root is None:
        return []
    left = inorder_traversal(root.left)
    result = [root.val]
    right = inorder_traversal(root.right)
    return left + result + right

(3)后序遍历指先访问节点的左子树和右子树,最后访问节点本身。同样可以使用递归算法来实现后序遍历。

def postorder_traversal(root):
    if root is None:
        return []
    left = postorder_traversal(root.left)
    right = postorder_traversal(root.right)
    result = [root.val]
    return left + right + result

2.2.2、树的深度

树的深度指从根节点到最远叶子节点的距离,可以使用递归算法来计算树的深度。

def tree_depth(root):
    if root is None:
        return 0
    left_depth = tree_depth(root.left)
    right_depth = tree_depth(root.right)
    return max(left_depth, right_depth) + 1

2.2.3、节点个数

节点个数指二叉树中所有节点的数量,可以使用递归算法来计算节点个数。

def node_count(root):
    if root is None:
        return 0
    left_count = node_count(root.left)
    right_count = node_count(root.right)
    return left_count + right_count + 1

三、排序和查找算法

快速排序、归并排序等排序算法以及二分查找等查找算法使用了递归思想。

3.1、快速排序

快速排序(Quick Sort)是一种基于分治策略的高效排序算法。具体做法是:从数组中选择一个元素作为“枢轴”(pivot),然后将数组中比枢轴小的元素移到枢轴左边,比枢轴大的元素移到枢轴右边,最后再对左右两部分分别进行同样的操作。通过不断地切割数组,最终得到有序数组。
在这里插入图片描述

可以使用递归算法来实现快速排序。

def quick_sort(arr):
    if len(arr) <= 1:
        return arr
    pivot = arr[0]
    left = [x for x in arr[1:] if x < pivot]
    right = [x for x in arr[1:] if x >= pivot]
    return quick_sort(left) + [pivot] + quick_sort(right)

3.2、归并排序

归并排序(Merge Sort)是一种稳定的、外部排序算法。其基本思想是:将待排数据分成若干个大小相等(或相近)子集合,并对每个子集合进行同样的操作,最后合并所有子集合成为有序数组。

在这里插入图片描述

可以使用递归算法来实现归并排序。

def merge_sort(arr):
    if len(arr) <= 1:
        return arr
    mid = len(arr) // 2
    left = merge_sort(arr[:mid])
    right = merge_sort(arr[mid:])
    return merge(left, right)

def merge(left, right):
    result = []
    i, j = 0, 0
    while i < len(left) and j < len(right):
        if left[i] <= right[j]:
            result.append(left[i])
            i += 1
        else:
            result.append(right[j])
            j += 1
    result += left[i:]
    result += right[j:]
    return result

3.3、二分查找

二分查找算法也可以使用递归思想来实现。下面是一个使用递归思想的二分查找算法:

def binary_search(arr, target):
    if len(arr) == 0:
        return -1

    mid = len(arr) // 2
    if arr[mid] == target:
        return mid
    elif arr[mid] < target:
        res = binary_search(arr[mid+1:], target)
        return res + mid + 1 if res != -1 else -1
    else:
        return binary_search(arr[:mid], target)

在这个算法中,首先判断数组是否为空,如果为空则返回-1。接着计算出数组的中间位置mid,并判断arr[mid]是否等于目标值target。如果相等,则直接返回mid;否则继续进行下一步判断。

如果arr[mid]小于目标值target,则说明目标值可能在右边的部分数组中,此时就需要对右半部分进行递归查找。在递归函数返回后,需要将返回结果加上mid+1(因为右半部分的第一个元素索引为mid+1)得到最终结果。

如果arr[mid]大于目标值target,则说明目标值可能在左边的部分数组中,此时就需要对左半部分进行递归查找。

通过以上递归过程,最终能够找到目标元素所在的位置或者确定该元素不存在于数组中。

四、动态规划问题

动态规划问题需要将大问题拆分为若干个子问题,并通过合并子问题的解得到整个问题的解。这种分治思想与递归思想非常相似,因此可以使用递归来实现。

动态规划问题可以使用递归算法进行求解。实际上,许多动态规划问题都可以被转换为递归问题,并且在递归的过程中进行记忆化搜索(Memorization Search),从而避免重复计算。

下面以一个简单的例子来说明如何使用递归算法求解动态规划问题:

假设有n个物品和一个容量为C的背包,每个物品i的重量是wi,价值是vi。现在需要将这些物品放入背包中,在不超过容量限制的情况下,使得背包中所装物品的总价值最大。

可以定义一个函数f(i,j),表示将前i个物品放入容量为j的背包中所能得到的最大价值。则对于第i个物品,如果将其放入背包,则有两种情况:放或者不放。因此状态转移方程可以表示为:

f(i,j) = max(f(i-1, j), f(i-1, j-wi) + vi)

其中,第一项f(i-1, j)表示不放第i个物品,则只考虑前i-1个物品;第二项f(i-1, j-wi)+vi表示将第i个物品放入背包,则考虑前i-1个物品并且剩余空间为j-wi。

接下来就可以用递归算法来实现:

def knapsack(i, j):
    if i == 0 or j == 0:
        return 0
    elif w[i] > j:
        return knapsack(i-1, j)
    else:
        return max(knapsack(i-1, j), knapsack(i-1, j-w[i]) + v[i])

在这个递归函数中,首先判断i和j是否为0,如果为0则返回0。如果第i个物品的重量大于当前容量j,则只能考虑前i-1个物品,因此递归调用knapsack(i-1,j)。否则就需要比较放和不放两种情况所得到的最大价值,即max(knapsack(i-1,j), knapsack(i-1,j-w[i])+v[i])。

需要注意的是,在上述递归过程中会存在大量的重复计算,因此可以使用记忆化搜索来优化算法性能。具体做法是,在每次计算f(i,j)时,将其结果存储下来并加以利用。当再次需要计算f(i,j)时,直接返回已经计算过的结果即可。

五、回溯算法

回溯算法是一种求解决策问题的通用算法,在很多 NP 难题中都有应用。回溯算法采用试错的思想,需要借助于递归函数进行状态转移。

六、分治设计模式

分治设计模式也是一种常见的编程技巧,它将一个大问题拆分成小问题来求解,并通过合并小问题得到大问题的解。这种思想与递归非常相似。

七、总结

递归算法是一种常用的算法设计技巧,在编程中具有重要性和应用价值:

  1. 适用于复杂问题求解:递归能够将一个大问题分解为若干个小问题,并通过合并子问题的结果得到整个问题的解。这种分治思想尤其适用于复杂问题求解,例如图遍历、排序、搜索等。
  2. 算法简洁高效:相比循环迭代等其他算法,递归算法通常更加简洁高效。这是因为递归代码可以更好地体现出数学归纳的思想,同时也避免了手动维护计数器等繁琐操作。
  3. 可读性强且易于维护:由于递归代码使用了自身调用的方式,使得程序结构清晰明了,并且易于理解和维护。此外,在实际项目开发中,往往需要处理多层嵌套或者复杂的数据结构,使用递归算法也能够带来较好的可读性和可维护性。
  4. 与函数式编程紧密相关:函数式编程语言中采用递归方式进行程序设计已经成为常态。而函数式编程语言的普及也引领了递归算法在编程中的应用和发展。
  5. 有助于提高编程思维能力:递归算法需要在脑海中构建一个完整的函数调用栈,这要求程序员具备一定的抽象思维、分析问题和解决问题的能力。因此,学习并掌握递归技巧可以帮助我们提高编程思维能力和解决复杂问题的能力。

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/625041.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

关于对【oracle索引】的理解与简述

【版权声明】未经博主同意&#xff0c;谢绝转载&#xff01;&#xff08;请尊重原创&#xff0c;博主保留追究权&#xff09; https://blog.csdn.net/m0_69908381/article/details/131094864 出自【进步*于辰的博客】 无论使用的是oracle、mysql&#xff0c;亦或者其他数据库&a…

如何吃透一个Java项目?

现在Austin的文档我觉得还是比较全的&#xff0c;但到了看代码的时候&#xff0c;可能有的同学就不知道应该怎么看&#xff0c;有想知道模块之间的调用链路&#xff0c;有想一点一点把细节给全看了。这时候就很可能在项目里犯迷糊了&#xff0c;绕不出不来了。 Java开源项目消息…

MySQL的下载安装以及环境配置---图文教程

目录 一.下载 二.安装 三.设置环境变量 四.MySQL数据库的使用及注意事项 SQL语句注意事项 一.下载 1.打开 MySQL 数据库的网站。 2.往下滑 3.进入新的页面之后&#xff0c;点击 MySQL Installer for Windows 4.进入新的页面时&#xff0c;就可以下载MySQL数据库了&#x…

数据结构05:树的定义与双亲、孩子表示法[更新中]

参考用书&#xff1a;王道考研《2024年 数据结构考研复习指导》 参考用书配套视频&#xff1a;5.1.1 树的定义和基本术语_哔哩哔哩_bilibili 特别感谢&#xff1a; Chat GPT老师[部分名词解释、修改BUG]、BING老师[封面图]~ 备注&#xff1a;博文目前是未完成的状态&#xff…

Web安全:拿到 Web 服务器 最高权限.(vulntarget 靶场 A)

Web安全&#xff1a;拿到 Web 服务器 最高权限. Web 服务器一般指网站服务器&#xff0c;是指驻留于因特网上某种类型计算机的程序&#xff0c;可以处理浏览器等Web客户端的请求并返回相应响应&#xff0c;也可以放置网站文件&#xff0c;让全世界浏览&#xff1b;可以放置数据…

43 最佳实践-性能最佳实践-IOThread配置

文章目录 43 最佳实践-性能最佳实践-IOThread配置43.1 概述43.2 配置说明 43 最佳实践-性能最佳实践-IOThread配置 43.1 概述 KVM平台上&#xff0c;对虚拟磁盘的读写在后端默认由QEMU主线程负责处理。这样会造成如下问题&#xff1a; 虚拟机的I/O请求都由一个QEMU主线程进行…

基于springboot的数码论坛系统设计(Java、MySQL、B/S)

wx供重浩&#xff1a;创享日记 对话框发送&#xff1a;数码论坛 获取源码源文件论文报告PPT 网络的广泛应用给生活带来了十分的便利。所以把数码论坛与现在网络相结合&#xff0c;利用java技术建设数码论坛系统&#xff0c;实现数码论坛的信息化。则对于进一步提高数码论坛发展…

Linux 下pause函数是如何实现的?

当你在程序中调用 pause() 函数时&#xff0c;它会使得你的程序停止执行&#xff0c;直到有一个信号被捕获。这是通过系统调用实现的。系统调用会使得程序从用户模式切换到内核模式。 这里是 pause() 函数的基本工作原理&#xff1a; 当你的程序调用 pause() 函数时&#xff…

python基础知识(十):类

目录 1. 类和方法的概念2. 类的定义3. 类的继承4. 重写父类的方法 1. 类和方法的概念 类&#xff1a;用来描述具有相同的属性和方法的对象的集合。它定义了该集合中每个对象所共有的属性和方法。对象是类的实例。举个例子&#xff0c;狗类就是狗的集合&#xff0c;每条狗都是狗…

kali学习笔记(二)

一、关闭自动锁屏 关闭自动锁屏对于测试人员来说&#xff0c;可以按照自己的习惯来设置&#xff0c;不然kali会过十分钟就锁屏&#xff0c;有的时候会比较不方便。 1、使用root账号登录&#xff0c;在display设置选项中做如下设置。 2、把休眠选项关掉。 二、创建快照 关机创…

透视Linux内核,BPF 深度分析与案例讲解

本次主要对BPF的部分原理、应用案例上进行一次分析记录。 BPF介绍 当内核触发事件时&#xff0c;BPF虚拟机能够运行相应的BPF程序指令&#xff0c;但是并不是意味着BPF程序能访问内核触发的所有事件。将BPF目标文件加载到BPF虚拟机时&#xff0c;需要确定特定的程序类型&…

SpringBoot中的定时任务@Scheduled的使用

1.Scheduled注解介绍 在spring boot的项目中需要使用到定时任务的时候&#xff0c;可以使用Scheduled注解&#xff0c;这只是在一个JVM进程中很适用&#xff0c;如果涉及到服务器是集群的情况下&#xff0c;建议使用任务调度平台。这样任务调度平台会在多台服务器中选择一台进…

【linux】在Ubuntu下部署nginx——nginx的安装与卸载

介绍 这里是小编成长之路的历程&#xff0c;也是小编的学习之路。希望和各位大佬们一起成长&#xff01; 以下为小编最喜欢的两句话&#xff1a; 要有最朴素的生活和最遥远的梦想&#xff0c;即使明天天寒地冻&#xff0c;山高水远&#xff0c;路远马亡。 一个人为什么要努力&a…

Think PHP6+Swagger3

swagger是一个解决接口规范化、标准化、文档化的一个组件&#xff0c;既可以直接自动生成resutful接口文档又能进行功能测试的一个web服务。本文是think PHP6结合swagger3的一个记录过程。 composer安装ThinkPHP 一般安装最新稳定版本&#xff0c;不一定是最新版本 composer…

怎么通过Fiddler对APP进行抓包?以及高级应用场景分析

目录 前言 简单说下Fiddler的抓包原理&#xff1a; 使用fiddler代理远程捕获APP请求 Fiddler高级应用场景介绍 1、url地址重写 fiddler抓包详细教程&#xff1a;全网抓包天花板教程&#xff0c;B站讲的最详细的Fiddler/Charles抓包教学视频。2小时包你学会_哔哩哔哩_bilibi…

软件测试之路已不再是坦途

去年下半年才跳了槽&#xff0c;过程非常顺利&#xff0c;没有经历大家所说的工作荒的境地&#xff0c;所以一直没有直观地感受到软件测试就业形势到底有多严峻。 近来看到一些机构频频发出某某测试员在糟糕的就业形势下逆袭拿下XXW的某厂offer&#xff0c;然后推荐测试进阶课…

Django学习笔记-配置Docker、Git环境与项目创建

笔记内容转载自AcWing的Django框架课讲义&#xff0c;课程链接&#xff1a;AcWing Django框架课。 CONTENTS 1. 配置Docker环境2. Django项目创建3. Django App创建 1. 配置Docker环境 首先拉取一个 Ubuntu 镜像&#xff1a; docker pull ubuntu:20.04创建容器后进入容器配置…

Splunk:构建安全监控解决方案(第 1 部分)

在我的网络安全训练营的最后几周&#xff0c;我们的最终项目之一是使用 Splunk Enterprise 为一个名为 VSI&#xff08;虚拟空间工业&#xff09;的虚构组织构建安全监控环境&#xff0c;对于那些可能不知道的人来说&#xff0c;它是一个 SIEM&#xff08;安全信息和事件管理器…

科一容易忘、容易混的点——图类

注意行人 和 人行横道区别 注意行人&#xff1a;黄色&#xff0c;里面是什么就注意什么 人行横道&#xff1a;正方形 “不得” xxx 的 就选择 【正确】 点火开关 1、LOCK档&#xff1a;这是一个锁止档&#xff0c;功能是当除了防盗系统和车内小灯以外&#xff0c;电路是完全关…

【Redis应用】用户签到统计连续签到(三)

&#x1f697;Redis应用学习第三站~ &#x1f6a9;本文已收录至专栏&#xff1a;Redis技术学习 签到功能是我们非常常见的一个功能&#xff0c;几乎在每个app中都能碰到&#xff0c;让我们一起看看如何实现吧~ 一.BitMap用法引入 我们针对签到功能完全可以通过mysql来完成&am…