python coding with ChatGPT 专题2| 全解递归算法

文章目录

  • 递归与栈的关系
  • 如何思考递归
    • 汉诺塔
  • 经典题目
    • 入门:斐波那契数列
    • 分治法:归并排序
    • 树的递归遍历
    • 组合问题:子集
    • 搜索问题:N皇后
  • 拓展
    • 阶乘的迭代法
    • 斐波那契数列迭代法
    • 青蛙跳
  • 参考文献

掌握递归是解决许多编程问题的关键,尤其是那些涉及树形结构、组合问题、搜索问题等领域的题目。递归方法的核心在于将问题分解成更小的子问题,直到达到一个基本情况(base case),然后再逐层返回解决整个问题。理解和掌握递归,最好的方式是通过实践一系列逐渐增加难度的问题。

递归与栈的关系

递归的过程就是出入栈的过程

我们以阶乘为例:

def Factorial(n):if n == 0:return 1return factorial(n-1) * n

取 n=3,则过程如下:
在这里插入图片描述 第 1~4 步,都是入栈过程,Factorial(3)调用了Factorial(2),Factorial(2)又接着调用Factorial(1),直到Factorial(0);
第 5 步,因 0 是递归结束条件,故不再入栈,此时栈高度为 4,即为我们平时所说的递归深度;
第 6~9 步,Factorial(0)做完,出栈,而Factorial(0)做完意味着Factorial(1)也做完,同样进行出栈,重复下去,直到所有的都出栈完毕,递归结束。
在这里插入图片描述

每一个递归程序都可以把它改写为非递归版本。我们只需利用栈,通过入栈和出栈两个操作就可以模拟递归的过程,二叉树的遍历无疑是这方面的代表。

如何思考递归

我们怎么判断这个递归计算是否是正确的呢?Paul Graham 提到一种方法,如下:

如果下面这两点是成立的,我们就知道这个递归对于所有的 n 都是正确的。· 当 n=0,1 时,结果正确;
· 假设递归对于 n 是正确的,同时对于 n+1 也正确。

这种方法很像数学归纳法,也是递归正确的思考方式,上述的第 1 点称为基本情况,第 2 点称为通用情况。

在递归中,我们通常把第 1 点称为终止条件,因为这样更容易理解,其作用就是终止递归,防止递归无限地运行下去。

下面我们用1个例子来具体说明这种数学归纳法:

汉诺塔

在这里插入图片描述问题描述为:有三根杆子 A,B,C。A 杆上有 N 个穿孔圆盘,盘的尺寸由上到下依次变大,B,C 杆为空。要求按下列规则将所有圆盘移至 C 杆:

  • 每次只能移动一个圆盘;
  • 大盘不能叠在小盘上面。

问:如何移?最少要移动多少次?

首先看下基本情况,即终止条件:N=1 时,直接从 A 移到 C。

再来看下通用情况:当有 N 个圆盘在 A 上,我们已经找到办法将其移到 C 杠上了,我们怎么移动 N+1 个圆盘到 C 杠上呢?很简单,我们首先用将 N 个圆盘移动到 C 上的方法将 N 个圆盘都移动到 B 上,然后再把第 N+1 个圆盘(最后一个)移动到 C 上,再用同样的方法将在 B 杠上的 N 个圆盘移动到 C 上,问题解决。

def Hanoi(n, a='A', b='B', c='C'):steps = 0if n == 0:return stepsif n==1:steps += 1print(a, '-->', c)return stepssteps += Hanoi(n-1,a,c,b)steps += Hanoi(1,a,b,c)steps += Hanoi(n-1,b,a,c)return steps

在这里插入图片描述

经典题目

在这里插入图片描述

入门:斐波那契数列

斐波那契数列是通过如下递归式定义的:

  • F(0) = 0, F(1) = 1
  • 对于 n > 1,F(n) = F(n-1) + F(n-2)

这意味着,斐波那契数列从0和1开始,之后的每个数字都是前两个数字的和。例如,斐波那契数列的前几个数字是:0, 1, 1, 2, 3, 5, 8, 13, 21…

def fib(n):if n<2:return nreturn  fib(n-1) + fib(n-2)

在这里插入图片描述

分治法:归并排序

归并排序是一种高效的排序算法,基于分治法的一个典型应用。它将一个数组分成两半,对每部分递归地应用归并排序,然后将两个排序好的部分合并成一个最终的排序数组。

步骤:

  1. 分解:将当前区间一分为二,递归地对这两个子区间进行归并排序。
  2. 合并:将两个排序好的子区间合并成一个最终的排序区间。

实现提示
归并过程需要额外的空间来合并两个有序数组,这是归并排序空间复杂度为O(n)的原因。
实现归并排序时,考虑如何优雅地合并两个有序的子数组。
双指针法

def merge_sort(arr):length = len(arr)if length <2:return arrmid = length//2left_half = arr[:mid]right_half = arr[mid:]left_sort = merge_sort(left_half)right_sort = merge_sort(right_half)i = j = k = 0while i < len(left_sort) and j < len(right_sort):if left_sort[i] <= right_sort[j]:arr[k] = left_sort[i]i += 1k += 1else:arr[k] = right_sort[j]j += 1k += 1if i < len(left_sort):arr[k:] = left_sort[i:]if j < len(right_sort):arr[k:] = right_sort[j:]return arr

在这里插入图片描述

树的递归遍历

关于二叉树的深度优先所有(DFS)问题,我们在前面的文章中有详细的讲解。

前序遍历(Preorder Traversal):首先访问根节点,然后递归地做前序遍历左子树,接着递归地做前序遍历右子树。
中序遍历(Inorder Traversal):首先递归地做中序遍历左子树,然后访问根节点,最后递归地做中序遍历右子树。
后序遍历(Postorder Traversal):首先递归地做后序遍历左子树,然后递归地做后序遍历右子树,最后访问根节点。

在Python中,二叉树节点可以通过下面的TreeNode类来表示:

class TreeNode:def __init__(self, val=0, left=None, right=None):self.val = valself.left = leftself.right = right
def preorderTraversal(root):res = []if not root:return resres.append(root)res.extend(preorderTraversal(root.left))res.extend(preorderTraversal(root.right))return resdef inorderTraversal(root):res = []if not root:return resres.extend(inorderTraversal(root.left))res.append(root)res.extend(inorderTraversal(root.right))return resdef postorderTraversal(root):res = []if not root:return resres.extend(postorderTraversal(root.left))res.extend(postorderTraversal(root.right))res.append(root)return res

组合问题:子集

给定一组不同的整数,返回所有可能的子集(幂集)。解集不能包含重复的子集。

例子

输入:
nums = [1,2,3]

输出:

[  [3],[1],[2],[1,2,3],[1,3],[2,3],[1,2],[]
]

这个问题可以通过递归(回溯算法)来解决。我们之前的文章也讲到过回溯的理论基础和模板。本题的基本思路是遍历所有元素,对于每个元素,我们可以选择将其包含到当前子集中,或者不包含它。每当我们到达数组的末尾时,我们就找到了一个可能的子集,并将其添加到结果列表中。

def subsets(nums):res = []def backtrack(start, path):res.append(path[:])for i in range(start, len(nums)):path.append(nums[i])backtrack(i+1, path)path.pop()# returnbacktrack(0, [])return res

在这里插入图片描述

搜索问题:N皇后

N皇后问题简介
N皇后问题要求在一个N×N的棋盘上放置N个皇后,使得它们互不攻击。这意味着任何两个皇后都不能处于同一行、同一列或同一对角线上。

解题思路
这个问题可以通过回溯法解决,核心思想是逐行放置皇后,并在每一行中尝试所有列,直到找到一个合法的位置。我们需要一个方法来检查在放置每个新的皇后时,棋盘的状态是否仍然合法。

在这里插入图片描述

def solveNQueens(n):board = [["."] * n for _ in range(n)]res = []def isValid(board, row, col):for i in range(row):# 检查列是否合法if board[i][col] == 'Q':return False# 检查左上对角线if col - i - 1 >= 0 and board[row - i - 1][col - i - 1] == 'Q':return False# 检查右上对角线if col + i + 1 < n and board[row - i - 1][col + i + 1] == 'Q':return Falsereturn Truedef backtrack(board, row):if row == n:res.append(["".join(row) for row in board])returnfor col in range(n):if not isValid(board, row, col):continueboard[row][col] = "Q"backtrack(board, row + 1)board[row][col] = "."backtrack(board, 0)return res

拓展

迭代法可以避免递归,并且在实际应用中,迭代通常比递归有更好的性能,尤其是在防止栈溢出方面。

阶乘的迭代法

def factorial(n):if n == 0:return 1pre = 1for i in range(2, n+1):result = i * prepre = resultreturn result

斐波那契数列迭代法

def fib(n):if n==0:return 0if n==1:return 1pre_n_1 = 1pre_n_2 = 0for i in range(2,n+1):result = pre_n_1 + pre_n_2pre_n_2 = pre_n_1pre_n_1 = resultreturn result

青蛙跳

一只青蛙可以一次跳 1 级台阶或者一次跳 2 级台阶,例如:

跳上第 1 级台阶只有一种跳法:直接跳 1 级即可。 跳上第 2 级台阶有两种跳法:每次跳 1 级,跳两次;或者一次跳 2 级。 问要跳上第 n 级台阶有多少种跳法?

递归法

def jump(n):if n <= 2:return nreturn jump(n - 1) + jump(n - 2)

迭代法

def jump(n):if n <= 2:return npre_n_1 = 2pre_n_2 = 1for i in range(3,n+1):result = pre_n_1 + pre_n_2pre_n_2 = pre_n_1pre_n_1 = resultreturn result

参考文献

一文读懂递归算法
一文看懂什么是递归

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

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

相关文章

Ethernet修改网卡名称

如何修改Ethernet网卡名称 kernel/common/net/core/dev.c ---------------------------------------------------------------------- 8849/** 8850 * register_netdev - register a network device 8851 * @dev: device to register 8852 * 8853 * Take a completed network d…

基于java实现的沙县小吃点餐系统

开发语言&#xff1a;Java 框架&#xff1a;ssm 技术&#xff1a;JSP JDK版本&#xff1a;JDK1.8 服务器&#xff1a;tomcat7 数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09; 数据库工具&#xff1a;Navicat11 开发软件&#xff1a;eclipse/myeclip…

“探秘数据结构:栈的奇妙魔力“

每日一言 兰有秀兮菊有芳&#xff0c;怀佳人兮不能忘。 —刘彻- 栈 栈的概念及结构 栈(Stack) &#xff1a;一种特殊的线性表&#xff0c;其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶&#xff0c;另一端称为栈底。栈中的数据元素遵守…

表面沉降位移监测仪的工作原理

TH-WY1表面沉降位移监测仪是一种专门用于监测地表沉降和位移的仪器。它通过精确测量地表的竖直位移和水平位移&#xff0c;来评估结构物的沉降情况和稳定性。这种监测仪通常应用于水利、交通、矿山等领域的边坡监测工作中&#xff0c;以确保工程设施的安全运营。 表面沉降位移监…

Node.js-知识点学习总结归纳

Node.js-知识点学习总结归纳 安装nodenode运行方式通过Node.js直接运行js文件&#xff08;也就不用通过网页html了&#xff09;绝对路径调用:相对路径调用&#xff1a;直接运行js命令&#xff1a; Vscode控制台使用node运行js文件 安装node 这个就不用讲了吧&#xff0c;网上搜…

文件操作的详序

1.为什么使用文件&#xff1f; 如果没有文件&#xff0c;我们写的程序的数据是存储在电脑的内存中&#xff0c;如果程序退出&#xff0c;内存回收&#xff0c;数据就丢失了&#xff0c;等再次运行程序&#xff0c;是看不到程序的数据的&#xff0c;如果将数据进行持久化的保存…

【LeetCode】热题100 刷题笔记

文章目录 T1 两数之和T49 字母异位词分组常用小技巧 T1 两数之和 链接&#xff1a;1. 两数之和 题目&#xff1a; 【刷题感悟】这道题用两层for循环也能做出来&#xff0c;但我们还是要挑战一下时间复杂度小于 O ( n 2 ) O(n^2) O(n2)的解法&#xff0c;不能因为它是第一道 …

3.写一个char类型的字符数组,对该数组访问越界时抛出异常,并做处理。

#include <iostream>using namespace std;char arr[10]"12345678"; void show(int i) {if(i>10){throw int(2);}cout << arr[i] <<endl; } int main() {int i;cin >> i;try {show(i);} catch (int) {cout << "越界" <…

C++之优化Linux内核结构体用智能指针std::unique_ptr与std::make_unique分配内存总结(二百六十五)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

搜索二叉树详细介绍C++

文章目录 前言一、搜索二叉树介绍二、二叉搜索树实现1.查找2.插入3.删除 三、二叉搜索树递归实现1.查找2.插入3.删除 四、二叉搜索树性能分析五、二叉搜索树应用1.K模型2.KV模型 总结 前言 在本篇文章中&#xff0c;我们将会学到数据结构中有关二叉树中一种特殊的结构-----搜索…

PCB三大走线,如何高效率检查?

在PCB设计中&#xff0c;走线的布局与检查是至关重要的环节。按照走线类型&#xff0c;可分为直角走线、差分走线及蛇形线&#xff0c;如何针对这三种走线方式进行高效率检查&#xff0c;去也报电路的稳定性和可靠性&#xff1f; 1、直角走线 容性负载&#xff1a;观察直角拐角…

2024.4.2每日一题

LeetCode 所有可能的真二叉树 题目链接&#xff1a;894. 所有可能的真二叉树 - 力扣&#xff08;LeetCode&#xff09; 题目描述 给你一个整数 n &#xff0c;请你找出所有可能含 n 个节点的 真二叉树 &#xff0c;并以列表形式返回。答案中每棵树的每个节点都必须符合 Nod…