自己调用自己的算法——递归算法

自己调用自己的算法——递归算法

目录

1. 什么是递归

2.具体例题讲解

2.1 LeetCode面试题 08.06. 汉诺塔问题

2.2 LeetCode21. 合并两个有序链表

2.3 LeetCode206. 反转链表

2.4 LeetCode50. Pow(x, n)

3. 总结


今天我们来聊一聊递归算法。

1. 什么是递归

相信各位在学习计算机的过程中都是听说过递归,它的本质就是通过自己调用自己的方式来实现一些问题。

它的代码一般来说是比较简单,那么我们该怎么设计递归呢,或者说什么样的问题是可以使用递归的呢?

在递归的设计中我们都需要以下这几步:

1. 确定问题是可以被分成多个重复子问题的。并且这些⼦问题具有与原问题相同的解决方法。

2. 把这些子问题的共同点确定出来,并从宏观的角度去看待这些问题。

3. 构建函数头,这个函数头表示的是什么,解决的是什么问题。

4. 接着去思考递归的出口。

PS:递归的出口就是递归的结束条件。一个递归是一定要有出口的,同时这个出口是要求一定会在递归的执行过程中触发的。

其实在我看来递归其实就是我们在算法的学习过程中发现一些问题可以被分为一个个小问题,接着这些小问题又可以被分为一些小问题。然后我们根据这个逻辑设计了一种代码或者说一种算法,这个算法和那些问题一样可以不断地被分化。我们管这些算法叫做递归。

说实话,我一开始学习的时候感觉这个算法总是有一种左脚踩右脚上天的感觉。

2.具体例题讲解

2.1 LeetCode面试题 08.06. 汉诺塔问题

我们来看下面这个问题,就是要求我们借助B这个柱子把A上面的盘子放到C上。

我们看下面这个图片,里面是1个盘子,2个盘子和3个盘子的情况。当只有一个盘子的时候我们只需要把A上面的这个盘子给放到C上。当是2个盘子的时候,我们先借助C把盘子给到B上,接着把A上面最大的那个盘子给C,接着用同样的方法把B盘子中最大的那个给C,循环往复。当是3个盘子时也是同样的方法。然后我们就发现了这个重复子问题,就是不断的通过借助中间柱子把盘子给放到C上面。

那我们的出口是什么呢?出口就是当A或者B上面只有一个盘子的时候我们就该结束了,直接把最后一个盘子给放到C上面,接着直接return。

我们看下面这个代码,dfs这个函数头表示X借助Y的帮助把盘子放到Z上。按照前面所说的,我们先借助Z把X上面的n-1个盘子给转移到B上,接着把X上面的那个盘子给放到Z上。接着借助X把Y上面的盘子给转移到Z上面。

PS:我们这边要注意,就是递归的出口基本上是写在最前面的,这样才可以防止死循环。

class Solution { public: void dfs(vector<int>& x, vector<int>& y, vector<int>& z,int n) { if(n==1) { z.push_back(x.back()); x.pop_back(); return; } dfs(x,z,y,n-1); z.push_back(x.back()); x.pop_back(); dfs(y,x,z,n-1); } void hanota(vector<int>& a, vector<int>& b, vector<int>& c) { dfs(a,b,c,a.size()); } };

2.2 LeetCode21. 合并两个有序链表

我们接下来看下面这道题,这道题的题目很简单,就是要求我们把下面两个链表给合并成一个链表。注意在这道题中我们不可以再生成一个链表。

这道题我们直接看代码,我们直接在题目给的函数里面进行递归,首先这个函数的函数头就是合并链表,出口就是但我们一个链表为空的时候直接返回另一个链表。

接着我们就开始判断两个链表里面的值,小的那个放到前面,接着再进行递归。因为我们前面说过了,这个函数头表示的是合并链表,我们我们直接当前节点的next=它就好了,代码会自己递归下去的。

class Solution { public: ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) { if(!l1) return l2; if(!l2) return l1; if(l1->val<=l2->val) { l1->next=mergeTwoLists(l1->next,l2); return l1; } else { l2->next=mergeTwoLists(l1,l2->next); return l2; } } };

2.3 LeetCode206. 反转链表

我们看下面这道题,题目就是要求我们翻转链表。

前面忘记说了,就是一道题的话并不是唯一解法的,像这道题我们既可以用递归,也可以使用一个三指针。

我们看下面这个代码dfs这个函数头表示的是当前需要进行反转的节点,接着出口就是当我们遇到最后节点或者空节点的时候进行return。

其中因为是单链表,所以我们在这里需要提前保存最后一个节点的位置,也就是这里面的newnode。接着的话就是叫当前节点的后一个节点指向当前节点,然后当前节点指向空(在这里直接指向空不会干扰到前面的节点,因为递归的过程中间已经记录下了上一层节点了)。

/** * Definition for singly-linked list. * struct ListNode { * int val; * ListNode *next; * ListNode() : val(0), next(nullptr) {} * ListNode(int x) : val(x), next(nullptr) {} * ListNode(int x, ListNode *next) : val(x), next(next) {} * }; */ class Solution { public: ListNode* dfs(ListNode* l1) { if(!l1||!l1->next) { return l1; } ListNode* newnode=dfs(l1->next); l1->next->next=l1; l1->next=nullptr; return newnode; } ListNode* reverseList(ListNode* head) { return dfs(head); } };

2.4 LeetCode50. Pow(x, n)

我们看下面这道题,就是叫我们手动实现一个pow函数。接着我们发现在这道题里面,n即可以能是一个负数,也可能是一个很大的数,所以我们在设计代码的时候要注意。

我们看下面这个图,下面这个就是我们发现重复子问题的过程,2的16次方可以通过两个2的8次方相乘来实现,2的8次方可以通过两个2的4次方相乘来实现。这样我们就发现了重复子问题。我们的递归也就按照它来进行设计。

我们看下面这个代码,首先名为要给m赋值给一个long long类型的值,因为m会很大。同时我们要注意这个m是可以为负数的,所以在这里如果为负数的话我们就要把x=1/x;接着把转换成正数(这一步我们是把负数的那个负号提前给x)。接着我们就按照上面所说的方式来实现递归,代码从下往上一点点走,下面那个ifelse判断就是判断有没有走到最后,也就是上面图片里面的1X2这一步。

class Solution { public: double myPow(double x, int m) { long long n=m; if(n<0) { x=1/x; n=-n; } if(n==0) return 1.0; double tmp=myPow(x,n/2); if(n%2==0) { return tmp*tmp; } else return tmp*tmp*x; } };

3. 总结

在递归算法的学习中我们一定要理解自己调用自己这个过程。同时也要理解递归是先走到最后面或者最下面再向上面返回的,它是有一个从小到大,从后到前的过程的。

如果还是觉得有点不理解,那么就画递归展开图。

Read more

人工智能:自然语言处理在客户服务领域的应用与实战

人工智能:自然语言处理在客户服务领域的应用与实战

人工智能:自然语言处理在客户服务领域的应用与实战 学习目标 💡 理解自然语言处理(NLP)在客户服务领域的应用场景和重要性 💡 掌握客户服务领域NLP应用的核心技术(如聊天机器人、意图识别、情感分析) 💡 学会使用前沿模型(如BERT、GPT-3)进行客户服务文本分析 💡 理解客户服务领域的特殊挑战(如实时性要求、多语言处理、用户体验) 💡 通过实战项目,开发一个客户服务聊天机器人应用 重点内容 * 客户服务领域NLP应用的主要场景 * 核心技术(聊天机器人、意图识别、情感分析) * 前沿模型(BERT、GPT-3)在客户服务领域的使用 * 客户服务领域的特殊挑战 * 实战项目:客户服务聊天机器人应用开发 一、客户服务领域NLP应用的主要场景 1.1 聊天机器人 1.1.1 聊天机器人的基本概念 聊天机器人是通过自然语言与用户进行交互的程序。在客户服务领域,聊天机器人的主要应用场景包括: * 客户服务:回答客户的问题(如“如何退货”、“商品价格”

By Ne0inhk
Spring AI 框架下接入 agent skill 手把手教程

Spring AI 框架下接入 agent skill 手把手教程

参考文档:Spring AI Agentic Patterns (Part 1): Agent Skills - Modular, Reusable Capabilities 引言 点进来的读者应该都了解了 agent skills 是什么,为什么会出现这种工程手段等等,此处不在多说,本篇博客聚焦于在 Spring-AI 下如何快速接入 Skills,并且探究背后实现的原理。 项目示例代码可以在 https://github.com/MimicHunterZ/PocketMind/tree/master/backend/src/main/java/com/doublez/pocketmindserver/demo 下查看,如果觉得项目不错,欢迎给我star~ 环境准备 maven依赖 根据官方手册,skill 需要 Spring-AI

By Ne0inhk
旧电脑秒变 AI 员工:OpenClaw 本地部署教程(含环境配置 + 插件开发 + 常见坑)

旧电脑秒变 AI 员工:OpenClaw 本地部署教程(含环境配置 + 插件开发 + 常见坑)

前言 本文基于最新OpenClaw版本编写,适配电脑低配置场景(最低2vCPU+2GiB内存+40GiB SSD),兼容Windows 10/11(优先WSL2)、Ubuntu 20.04+系统,全程纯操作指令,覆盖环境配置、本地部署、插件开发、高频坑排查。核心解决部署卡顿、国内网络适配、插件开发无思路、报错无法排查四大痛点,全程适配国内网络(国内镜像源)、国内大模型(通义千问、阿里云百炼等),无需海外代理,可稳定运行实现自动化办公(文件处理、IM对接、任务调度等)。 一、前置准备(适配优化) 1.1 硬件要求(最低适配) * CPU:Intel i3 4代+/AMD Ryzen 3 2000+(支持虚拟化,

By Ne0inhk
医疗AI场景下算法编程的深度解析(2026新生培训讲稿)(四)

医疗AI场景下算法编程的深度解析(2026新生培训讲稿)(四)

第7章 k-均值算法:患者分群与精准医疗 在医疗领域,我们常常面临这样的问题:患者是否可以划分为不同的亚型?不同亚型是否有不同的疾病进展模式或治疗反应?这些问题属于无监督学习的范畴。k-均值(k-means)聚类算法是最经典、最常用的无监督学习算法之一,它能够将数据划分为 k 个簇,使得同一簇内的样本高度相似,不同簇间的样本差异显著。本章将从算法原理出发,深入解析 k-均值在医疗场景中的应用,并通过实战案例展示如何利用 k-均值发现慢性病患者的潜在亚型,为精准医疗提供依据。 7.1 算法原理 7.1.1 聚类问题概述 聚类是一种无监督学习任务,目标是将数据集中的样本划分为若干个组(簇),使得同一组内的样本尽可能相似,不同组间的样本尽可能不同。与分类不同,聚类不依赖于预先标记的类别,而是从数据本身发现结构。 7.1.2 k-均值算法的核心思想 k-均值算法试图将 n 个样本划分到 k 个簇中,使得每个样本到其所属簇中心的距离平方和最小。簇中心是簇内所有样本的均值(因此得名“

By Ne0inhk