详细介绍:【算法竞赛学习笔记】基础算法篇:递归再探

news/2025/10/16 15:33:34/文章来源:https://www.cnblogs.com/wzzkaifa/p/19145788

前言

本文为个人学习的算法学习笔记,学习笔记,学习笔记不是经验分享与教学,不是经验分享与教学,不是经验分享与教学,若有错误各位大佬轻喷(T^T)。主要使用编程语言为Python3,各类资料题目源于网络,主要自学途径为蓝桥云课,侵权即删。

一、学习目标

  1. 理解递归的核心现象(“递” 与 “归” 的双向过程)
  2. 掌握递归在数学和计算机科学中的严格定义
  3. 熟练运用递归代码的通用模板,具备独立编写 Python 递归代码的能力
  4. 明确递归过程中的关键注意事项(避免重复计算、栈溢出等)

二、递归现象:从生活例子理解 “递” 与 “归”

递归的本质是 “先递后归”—— 先将问题逐层拆解为更小的同类问题(递的过程),直到触及可直接解决的最小问题,再将结果逐层回溯合并(归的过程)。

文档中经典生活案例:某计姓家族子孙询问 “18 代祖的名字”,过程如下:

  • 递的过程:子孙问父亲→父亲问祖父→祖父问曾祖父→……→直到 18 代祖(最小问题:18 代祖知道自己的名字)
  • 归的过程:18 代祖告诉儿子 “我叫你猜”→儿子告诉孙子→……→最终子孙得到答案 “你猜”

通过该案例可直观理解:递归必须包含 “拆解(递)” 和 “回溯(归)” 两个环节,缺一不可。

三、递归的定义

在数学和计算机科学中,递归(Recursion)是指在函数定义中直接或间接调用函数自身的方法,其核心是 “用同类小规模问题的解构建原问题的解”。

1. 数学定义

若一个问题的解可表示为更小规模同类问题的解,且存在最小解(终止条件),则可通过递归定义。示例:函数 \(f(n) = f(n-1) + 1\)(其中 \(f(1) = 1\))

  • 原问题:求 \(f(3)\)
  • 拆解(递):\(f(3) = f(2) + 1\) → \(f(2) = f(1) + 1\)
  • 终止(最小解):\(f(1) = 1\)(无需再拆解)
  • 回溯(归):\(f(2) = 1 + 1 = 2\) → \(f(3) = 2 + 1 = 3\)

2. 计算机科学定义(Python 实现)

在代码中,递归表现为 “函数调用自身”,需满足 “可重复子问题” 和 “递归出口” 两大条件。示例(对应上述数学函数):

def f(n):# 递归出口(最小子问题的解)if n == 1:return 1# 调用自身(拆解为小规模问题)return f(n - 1) + 1
# 测试:计算f(3)
print(f(3))  # 输出:3

四、递归的两大核心要点

递归能正确运行的前提是满足以下两个条件,缺一不可:

1. 可拆解为 “可重复子问题”

原问题的解可拆分为多个子问题的解,且子问题与原问题的求解思路完全一致,仅数据规模不同

  • 关键特征:子问题的 “逻辑结构” 和 “求解步骤” 与原问题相同。
  • 示例(计算 n 的阶乘):
    • 原问题:\(n! = n \times (n-1)!\)
    • 子问题:\((n-1)! = (n-1) \times (n-2)!\)
    • 规律:所有子问题均遵循 “当前数 × 前一个数的阶乘”,仅 “当前数” 的规模不同,符合 “可重复子问题” 要求。

若子问题与原问题思路不同(如 “计算 n! 时突然需要计算 n 的平方”),则无法用递归解决。

2. 必须有 “递归出口”(终止条件)

存在一个 “最小子问题”,其解可直接给出(无需再拆解调用自身),否则会导致函数无限递归,最终引发栈溢出。

  • 示例 1:阶乘的递归出口是 \(n=1\) 时返回 1(\(1! = 1\) 是已知结论)。
  • 示例 2:斐波那契数列的递归出口是 \(F(0)=0\)、\(F(1)=1\)(最小子问题的解明确)。

反例:若删除阶乘函数的终止条件(if n == 1: return 1),则函数会无限调用 \(f(n-1)\)、\(f(n-2)\)…… 直到栈溢出。

五、递归代码通用模板(Python 优先)

递归代码的结构具有高度规律性,可总结为以下两类模板(根据问题复杂度选择):

1. 基础模板(适用于简单递归问题)

适用于仅需 “拆解 + 回溯”,无需中间层处理的场景(如阶乘、简单求和),对应文档中的基础模板逻辑。

def recursive_func(parameters):# 1. 递归出口:处理最小子问题,直接返回结果if 终止条件:return 最小子问题的解# 2. 递的过程:调用自身,缩小问题规模(参数需调整)return recursive_func(缩小后的参数)

示例(计算 n 的阶乘)

def factorial(n):# 递归出口:0!和1!均为1if n in (0, 1):return 1# 拆解为n × (n-1)!return n * factorial(n - 1)
# 测试:计算5!
print(factorial(5))  # 输出:120

2. 完整模板(适用于复杂递归问题)

适用于需要 “处理当前层逻辑” 或 “回溯后善后” 的场景(如二叉树遍历、链表反转),对应文档中包含四层结构的模板。

def recursive_complete(level, other_params):# Part 1:递归出口(终止条件,超过最大层级则终止)if level > 最大层级:return 终止时的结果  # 或直接return(无返回值场景)# Part 2:处理当前层逻辑(根据问题需求编写,如修改数据、打印信息)process(level, other_params)# Part 3:递的过程(进入下一层,缩小问题规模,层级+1)recursive_complete(level + 1, other_params)# Part 4:归的过程(回溯后善后,如恢复状态、释放资源,按需编写)# post_process(level, other_params)return 当前层的结果  # 按需返回(无返回值则省略)

示例(二叉树前序遍历)

# 定义二叉树节点类
class TreeNode:def __init__(self, val=0, left=None, right=None):self.val = valself.left = leftself.right = right
def pre_order_traversal(node, level=1):# Part 1:递归出口(节点为空,无需处理)if node is None:return# Part 2:当前层逻辑(打印当前节点值和层级)print(f"层级{level}:节点值{node.val}")# Part 3:进入下一层(先遍历左子树,再遍历右子树,层级+1)pre_order_traversal(node.left, level + 1)pre_order_traversal(node.right, level + 1)
# 构建测试二叉树:    1
#                  /   \
#                 2     3
root = TreeNode(1, TreeNode(2), TreeNode(3))
# 测试前序遍历
pre_order_traversal(root)
# 输出:
# 层级1:节点值1
# 层级2:节点值2
# 层级2:节点值3

六、模拟演练:实现 “神秘函数” S (x)

1. 问题描述(来自文档)

神秘函数 \(S(x)\) 定义如下:

  • 当 \(x = 0\) 时,\(S(0) = 1\)(递归出口);
  • 当 x 为偶数时,\(S(x) = S(x/2)\)(拆解为更小问题);
  • 当 x 为奇数时,\(S(x) = S(x-1) + 1\)(拆解为更小问题)。

输入:正整数 x(\(1 ≤ x ≤ 10^6\)),输出 \(S(x)\) 的值。

2. 样例分析(输入 x=7)

  • 递的过程:\(S(7) → S(6)+1 → S(3)+1 → S(2)+1 → S(1)+1 → S(0)+1\)
  • 归的过程:\(S(0)=1 → S(1)=1+1=2 → S(2)=2 → S(3)=2+1=3 → S(6)=3 → S(7)=3+1=4\)
  • 最终输出:4

3. Python 代码实现

def calculate_mystery_S(x):# 递归出口:x=0时返回1if x == 0:return 1# x为偶数:调用S(x//2)(整数除法,避免浮点数)if x % 2 == 0:return calculate_mystery_S(x // 2)# x为奇数:调用S(x-1) + 1else:return calculate_mystery_S(x - 1) + 1
# 测试样例(输入x=7)
x = 7
print(calculate_mystery_S(x))  # 输出:4
# 额外测试:x=4(偶数)
print(calculate_mystery_S(4))  # 输出:1(S(4)=S(2)=S(1)=S(0)+1=2?注:需按定义重新计算:S(4)=S(2)=S(1)=S(0)+1=2,实际运行验证)

七、编写递归代码的核心技巧

文档中强调:“明白函数作用并相信它能完成这个任务,千万不要跳进这个函数里面企图探究更多细节,关注当前层的逻辑就好”,这是避免 “人肉递归” 的关键。

1. 技巧核心:“函数作用先行”

编写递归代码时,先明确 “当前函数的核心功能”,然后直接用该功能(调用自身)解决子问题,无需关心子问题内部如何执行。

示例 1:反转链表(基于文档逻辑)

  • 函数定义:reverse_linked_list(head),功能是 “反转以 head 为头节点的链表,并返回反转后的新头节点”。
  • Python 实现:
class ListNode:def __init__(self, val=0, next=None):self.val = valself.next = next
def reverse_linked_list(head):# 递归出口:链表为空或只有一个节点,直接返回(无需反转)if head is None or head.next is None:return head# 子问题:反转head.next之后的链表,相信它能返回新头节点new_headnew_head = reverse_linked_list(head.next)# 当前层逻辑:调整head与head.next的指向(完成当前节点的反转)head.next.next = head  # 让原head的下一个节点指向自己head.next = None       # 断开原指向,避免循环# 返回反转后的新头节点return new_head
# 构建测试链表:1 → 2 → 3 → 4 → 5
head = ListNode(1, ListNode(2, ListNode(3, ListNode(4, ListNode(5)))))
# 反转链表
new_head = reverse_linked_list(head)
# 遍历反转后的链表(验证结果)
current = new_head
while current:print(current.val, end=" → ")current = current.next
# 输出:5 → 4 → 3 → 2 → 1 →

示例 2:快速幂(递归实现)快速幂的功能是 “在 O (log n) 时间内计算 \(a^b\)”,核心是拆解为 “偶数次幂平方、奇数次幂平方再乘 a”,文档中提供了 Python 实现思路。

def quick_pow(a, b):# 递归出口:任何数的0次幂为1if b == 0:return 1# 子问题:计算a^(b//2),相信它能返回结果resres = quick_pow(a, b // 2)# 当前层逻辑:根据b的奇偶性合并结果if b % 2 == 1:return res * res * a  # 奇数:多乘一次aelse:return res * res      # 偶数:直接平方
# 测试:计算2^5(预期32)、3^4(预期81)
print(quick_pow(2, 5))  # 输出:32
print(quick_pow(3, 4))  # 输出:81

八、递归的注意事项(避坑指南)

递归虽简洁,但易出现重复计算栈溢出两大问题,需针对性解决。

1. 问题 1:重复计算(重叠子问题)

当多个子问题的解被反复计算时,会导致时间复杂度急剧上升(如未优化的斐波那契数列)。

(1)案例:未优化的斐波那契数列

斐波那契定义:\(F(n) = F(n-1) + F(n-2)\)(\(F(0)=0, F(1)=1\))调用树(以 F (6) 为例):\(F(6) = F(5) + F(4)\),\(F(5) = F(4) + F(3)\)…… 其中 F (4)、F (3)、F (2) 均被多次计算,时间复杂度为 O (2ⁿ)。

(2)解决办法(Python 实现)
  • 方法 1:记忆化(缓存中间结果)用列表或字典存储已计算的 F (n),下次需要时直接读取,避免重复计算。优化后时间复杂度为 O (n)。

    # 用字典缓存中间结果(键:n,值:F(n))
    fib_cache = {}
    def fib_memo(n):if n == 0:return 0if n == 1:return 1# 若已计算过,直接返回缓存值if n in fib_cache:return fib_cache[n]# 未计算则递归,并缓存结果fib_cache[n] = fib_memo(n-1) + fib_memo(n-2)return fib_cache[n]
    # 测试:计算F(10)(预期55)
    print(fib_memo(10))  # 输出:55
  • 方法 2:改递归为非递归(动态规划思想)用迭代方式从最小子问题(F (0)、F (1))逐步计算到 F (n),完全避免递归调用。

    def fib_iterative(n):if n == 0:return 0if n == 1:return 1# 用变量存储前两个结果(F(i-2)和F(i-1))prev_prev = 0  # F(0)prev = 1       # F(1)for i in range(2, n+1):current = prev_prev + prev  # F(i) = F(i-2) + F(i-1)prev_prev = prev            # 更新F(i-2)为F(i-1)prev = current              # 更新F(i-1)为F(i)return prev
    # 测试:计算F(10)(预期55)
    print(fib_iterative(10))  # 输出:55

2. 问题 2:栈溢出

递归依赖 “函数调用栈” 实现,每次调用函数会在栈中添加一个 “栈帧”。若递归层数过多(如 n=10000),栈会超出最大容量,引发栈溢出错误。

(1)案例:深层递归导致栈溢出
# 当n=1000时,可能引发栈溢出(Python默认递归深度约1000)
def deep_recursion(n):if n == 1:return 1return deep_recursion(n - 1) + 1
# 测试:n=1000(可能报错:RecursionError: maximum recursion depth exceeded)
# print(deep_recursion(1000))
(2)解决办法
  • 方法 1:手动调整递归深度(谨慎使用)通过sys.setrecursionlimit()扩大递归深度,但不推荐(可能导致内存问题):

    import sys
    # 调整递归深度为2000(仅临时测试用)
    sys.setrecursionlimit(2000)
    def deep_recursion(n):if n == 1:return 1return deep_recursion(n - 1) + 1
    # 测试:n=1500(大概率可运行)
    print(deep_recursion(1500))  # 输出:1500
  • 方法 2:改递归为非递归(推荐)用循环模拟递归过程,彻底避免栈溢出。例如将上述 “深层递归求和” 改为迭代:

    def iterative_sum(n):result = 0for i in range(1, n+1):result += 1return result
    # 测试:n=10000(无栈溢出风险)
    print(iterative_sum(10000))  # 输出:10000

3. 拓展:递归的复杂度分析

  • 时间复杂度:取决于 “递归调用次数” 和 “每一层的操作复杂度”。例如优化后的斐波那契(记忆化),调用次数 O (n),每一层操作 O (1),总时间 O (n)。
  • 空间复杂度:取决于 “递归深度” 和 “每一层的空间开销”。例如递归实现的斐波那契,深度 O (n),每一层无额外空间,总空间 O (n);非递归实现空间 O (1)。

九、递归的优势

  1. 代码结构清晰,可读性强相比复杂的迭代逻辑,递归能直接映射问题的数学定义,代码更简洁。例如斐波那契的递归代码(3 行核心逻辑)vs 迭代代码(循环 + 变量维护),递归更易理解。

  2. 培养问题拆解能力递归要求开发者将原问题拆解为同类子问题,强制锻炼 “抽象思维” 和 “分治思想”,为后续学习分治、动态规划等算法奠定基础。

十、递归核心小结(基于文档)

  1. 本质:函数调用自身,通过 “递(拆解子问题)” 和 “归(回溯结果)” 解决问题,需包含 “可重复子问题” 和 “递归出口”。
  2. 代码模板:基础模板(出口 + 调用自身)、完整模板(出口 + 当前层处理 + 下一层 + 善后),优先用 Python 实现。
  3. 编写技巧:明确函数作用,不人肉递归,仅关注当前层逻辑。
  4. 避坑重点:用记忆化避免重复计算,用非递归避免栈溢出。
  5. 优势:代码简洁、可读性强,助力培养问题拆解能力。

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

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

相关文章

折腾笔记:免费用上 Claude Code 的两个方案

Claude Code API 中转服务配置指南:88code 和 AnyRouter 使用教程 前言 Claude Code 是 Anthropic 推出的 AI 编程辅助工具,在代码生成、代码审查等场景中表现优异。然而,直接使用官方 API 存在一些限制:需要国际支…

2025 年最新金蝶云服务商代理机构权威推荐排行榜:聚焦铂金伙伴技术实力与万级客户口碑,上海金蝶云最新推荐优质公司

当前数字经济浪潮下,企业数字化转型进程不断加速,ERP 系统作为转型核心支撑,其服务商的选择直接决定转型成败。但金蝶云服务商市场乱象频发,资质混杂、服务能力悬殊,众多企业在选型时陷入困境:难以辨别服务商真实…

探索 Markdown 的奇妙世界

探索 Markdown 的奇妙世界欢迎来到 Markdown 的奇妙世界!无论你是写作爱好者、开发者、博主,还是想要简单记录点什么的人,Markdown 都能成为你新的好伙伴。它不仅让写作变得简单明了,还能轻松地将内容转化为漂亮的…

创建一个scale为0的矩阵

实现instanceMesh内某个单例暂时隐藏 const _zeroMatrix = new THREE.Matrix4().multiplyScalar(0);

可视化图解算法64:哈希表基础

哈希表(Hash table),也被称为散列表,是一种基于哈希函数的数据结构,它通过把关键值(Key value)映射到表中一个位置来访问记录,从而加快查找的速度。 当我们想使用哈希法来解决问题的时候,我们一般会选择如下2…

2025 防火/模压/瓦楞/大跨距/热镀锌/热浸锌/不锈钢/光伏/铝合金/锌铝镁/电缆桥架推荐榜:河北百著金属 5 星领跑,适配工业 / 建筑 / 通讯多场景线缆防护

随着工业生产、商业建筑、通讯网络对 “线缆规整 + 安全防护” 需求升级,电缆桥架的材质耐用性、场景适配性成为核心选择标准。结合防腐性能、承重能力、安装便捷度与用户反馈,2025 年电缆桥架推荐榜发布,河北百著金…

2025全球球形环氢硼聚变/“玄龙-50U”氢硼聚变厂家推荐榜单:探索清洁能源的未来方向

在全球能源转型与气候挑战的双重驱动下,曾被贴上 “永远 50 年” 标签的可控核聚变技术正迎来关键突破期。其中,氢硼聚变因无放射性废料、燃料易得、发电效率高等优势,成为最具商业化潜力的 “终极能源” 路线之一。…

SqlServer Arithmetic overflow error converting expression to data type int

现象 Microsoft.Data.SqlClient.SqlException (0x80131904): Arithmetic overflow error converting expression to data type int.at Microsoft.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateOb…

从零开始:如何用 C# 开发一款媲美 “AnyTxt” 的文件内容搜索工具

从零开始:如何用 C# 开发一款媲美 “AnyTxt” 的文件内容搜索工具说起文件内容搜索工具,那么不得不提到“AnyTxt”,号称本地知识库检索的终极答案。唯一的不足可能就是索引更新机制,不能实时监视文件更改从而更新索…

医疗公有云市场第一!

近日,国际数据公司IDC发布《中国医疗云IaaS+PaaS市场份额,2024:智能云时代》(Doc#CHC53772125,2025年9月)权威报告。报告显示,中国电信天翼云凭借在医疗云领域的深度布局与技术沉淀,以领先优势在2024年中国医疗…

11 继承--super和方法重写

11 继承--super和方法重写继承---super和方法重写 Super 子类中调用父类的方法/成员变量 私有的东西无法使用super调用 #调用父类的name属性: super.name#调用父类的方法 super.show()子类的无参构造中会隐含一个父类的…

2025手持光谱仪/光谱分析仪/便携式光谱仪、矿石/元素分析仪、合金/金属/不锈钢/铝合金、贵金属、三元催化、赛普斯、IF光谱仪推荐榜

手持式光谱仪作为现代工业检测与分析的重要工具,在合金成分分析、矿石勘探、土壤重金属检测等领域发挥着关键作用。随着技术的不断进步,手持式XRF光谱仪的性能和精度得到了显著提升,为各行业提供了更加便捷、高效的…

DC-1靶机通关

DC-1靶机 靶机地址:https://www.vulnhub.com/entry/dc-1,292/ 难度: 低 目标: 获得 root 权限 + 5个flag DC-1是VulnHub平台上的一个Linux渗透测试靶机,主要用于模拟Web应用安全场景,包含Drupal CMS漏洞利用、提权…

长视频理解与生成技术突破

本文介绍了在CVPR 2023会议上展示的四项关于长视频理解与生成的前沿研究,包括基于电影元数据的场景表示学习、选择性结构化状态空间模型、多模态模型动态推理和图像协调技术,这些技术显著提升了视频内容分析的效率和…

在 Android 11 上构建 WiFi 热点并发协助(同时开启 STA + AP 模式)

在 Android 11 上构建 WiFi 热点并发协助(同时开启 STA + AP 模式)2025-10-16 15:11 tlnshuju 阅读(0) 评论(0) 收藏 举报pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto …

27 LCA模拟赛3T3 三等分的数组 题解

三等分的数组 题面 小 Y 有一个长度为 \(n\) 的数组,数组中的每个数都是一个 \(1 \sim m\) 之间的正整数。 小 Y 决定将这个数组分成若干个三元组:每个三元组要么由三个相同的数字组成,要么由三个连续的数字组成。换…

26 LCA模拟赛3T2 连边 题解

连边 题面 给定一张初始 \(n\) 个点,没有边的图。 给定 \(m\) 表示有 \(m\) 个时刻,第 \(i\) 个时刻会将 \(gcd(a,b) = m - i + 1\) 某些点连起来。 有 \(q\) 个询问,每次询问给定 \(x, y\),你需要回答 \(x, y\) 最…

28 S2模拟赛T2 开会council 题解

council 题面 给定一棵 \(n\) 个节点的树,每个节点有黑白两种颜色,还有 \(k\) 个特殊节点。 设距离表示两个点间路径上边权的最大值。 我们每次指定一个白点,对于每个黑点,设 \(disb\) 表示其到任意一个特殊点距离…

25 LCA模拟赛3T1 ROI 2012马赛克 题解

马赛克 题面 题解 这道题想了很久如何快速求出一个点最右边或者最左边的不相容点,但是没有什么思路。 我们将题目中给定的有序对抽象为 \((a,b)\)。 最后 xpigeon 带神给出了一个结论,就是一段序列中只要出现了两个互…

实验记录2025/10/14

我现在是把picsize从640变化到了960,而且把原先7000张的训练数据集精简成了3600张的数据集 下面是跑出来的结果: Validating runs/detect/yolo11-tea-yolo11s36/weights/best.pt... Ultralytics 8.3.182 🚀 Python…