算法——递归与搜索算法

1. 递归

①什么是递归?

官方一点来说

递归指的是一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法。

通俗一点来说,递归就是一个函数自己调用自己的过程

②什么情况下会用到递归?

我们在遇见一个问题的时候,我怎么知道这里可能会用到递归呢?这就需要我们了解递归的本质,即:在解决一个主问题时如果会衍生出一个相同的子问题,那么这里大概率就可以使用递归来解决。在这里举几个例子

1. 归并排序算法

归并排序大致流程如图所示,我们想要对一个数组进行排序,就将它分为两半,对左边的数据排序好后,再对右边数据排序,左右数据排好后再将他们进行归并,为了解决排序的问题,我们将这个数组分为两半之后,我们又需要再分别对左右半边再次进行上述的操作

2. 快速排序算法

快速排序大致流程如图所示,我们想要对一个数组进行排序,就先挑选一个基准值key,将其移动到正确位置之后,对key左边的数组进行排序,再对key右边的数组排序,为了解决排序问题,我们将两边的数组排序之后,我们需要再次分别对左右部分挑选基准值并排序,即重复之前的过程

3. 二叉树的遍历

以上面这棵树为例,我们要对其进行后序遍历时,先要遍历左子树(左边黑色部分),然后遍历右子树(右边黑色部分),再访问自己,那么对于根节点的左子树来说,我们先遍历其左子树(左边红色部分),然后遍历其右子树(右边红色部分),本质上也是一种重复的过程

③如何理解递归?

在举例了上面几个例子之后,我们对递归应该有什么样的认识呢?我认为应该至少要有三点

1. 不要过于陷入递归展开图

2. 我们要将进行递归的函数视作一个黑盒

3. 在书写递归函数的过程中,我们要认为黑盒一定可以做到我们想做的事

在这里我们书写几个伪代码来实现上述部分例子

1. 归并

void merge(int nums[], int left, int right)
{// 出口if (left >= right) return;// 相信我们能够排序成功int mid = (left + right) / 2;merge(nums, left, mid);merge(nums, mid+1, right);// 排序好之后归并//...
}

2. 遍历

void dfs(Node* root)
{// 出口if (root == NULL) return;// 相信我们能够遍历成功dfs(root->left);dfs(root->right);// 遍历细节printf("%d ", root->val);
}

④如何写好一个递归?

在看了上面两个伪代码之后,我们可以知道要想写好一个递归,关键在于两点:

1. 要找到一个相同的子问题,即要设计好一个函数头

2. 具体解决好一个子问题即可,即书写递归函数主体

此外,为了避免函数进入死循环,需要给函数设计一个出口

2. 搜索

①深度优先遍历与深度优先搜索

在这里我们用一棵树来举例

dfs即Depth-First Search,深度优先搜索,字面意思就是不断往深处搜索,搜索到头时返回上一个分支,然后继续往深处搜索,深度优先遍历与深度优先搜索,遍历是形式,搜索是目的

②广度优先遍历与广度优先搜索

bfs即Breadth-First Search,广度优先搜索,字面意思就是每次都遍历一层,然后不断地向外搜索

③拓展搜索问题

在这里我们以全排列问题为例,即

对于1,2,3三个数字来说,要找到它们的全排列,可以采取如上图所示的方式来做到,这之后我们只需要使用dfs或bfs就可以得到最后的全部结果,上面这种方式类似于树,我们也将其称为决策树

3. 回溯与剪枝

对于回溯我们可以将其视为与dfs相似,举个例子

对于上面这个迷宫,我们从起点出发到第一个分叉点时,即

我们向左试探,发现这条路不能到达终点,因此需要我们进行回溯,即回到上一个分叉点

而在下面这种情况时,由于回到上一个分叉节点时,其有两种前进方式,而我们已经排除了其中的一种方式,此时我们需要对已经探索过的路径进行剪枝,即表示该路径无法到达终点

将其称为剪枝也是因为在树中访问过一个子树后,其就不用再次访问,即将树的枝条剪掉,是很形象的一个说法

4. 递归与循环(迭代)

在了解递归后,我们可以知道,递归解决的其实就是重复的问题,而循环也可以解决重复的问题,因此它们之间是可以互相转换的,那么在遇到一个既可以使用循环又可以使用递归的问题时,我们应该如何选择呢?

首先我们要知道,递归在展开后,我们可以发现它的路径和对一颗树进行dfs的路径是一样的,举个例子

对于上面这棵树, 我们在递归解决它时,在深度递归完一条支路后需要返回到到上一个节点,即保存之前的信息,因此这里如果使用循环的话就需要使用栈来保存信息,因此此处使用递归更好,而在不需要保存信息的情况下,如遍历数组时,使用循环更佳

5. 例题

①递归专题

1. 汉诺塔问题

题目链接:面试题 08.06. 汉诺塔问题 - 力扣(LeetCode)

解析:题目说起来可能比较抽象,这里我们画图表示

大致要求是要让小的始终在上面,让后将A的所有圆盘移动到C上,我们在看到这个问题时,不能先入为主的想着用递归去解决它,而是在解决的过程中发现它具有一些递归的特性,然后才会想到使用递归解决它,那么我们如何解决它呢?这里我们可以使用先一般后特殊的方法

即在解决问题的过程中,我们可以发现在解决主问题时,衍生出来一个和他类似的子问题,而在解决子问题时,又出现一个和子问题相似的子问题,因此我们可以将其分解成若干个相同的子问题,即使用递归解决

其解决代码如下

class Solution 
{
public:void Hanota(vector<int>& A, vector<int>& B, vector<int>& C, int n){// 出口if (n == 1) {C.push_back(A.back());A.pop_back();return;}// 操作1:借助C杆,将A上的n-1个圆盘移动到B上Hanota(A, C, B, n-1);// 操作2:将A杆的底层转移到C杆上C.push_back(A.back());A.pop_back();// 操作3:将B上的剩余部分借助A,转移到C杆上Hanota(B, A, C, n-1); }void hanota(vector<int>& A, vector<int>& B, vector<int>& C) {int n = A.size();Hanota(A, B, C, n);}
};

注:在操作2中,不能直接向C直接插入A[0],通过逐步拆分我们可以发现,虽然逻辑上来说,我们是将A除底层的整个部分直接平移的,但在实际上,我们是从最上面开始一个一个平移的,因此这里使用的是back元素。

2. 合并两个有序链表

题目链接:21. 合并两个有序链表 - 力扣(LeetCode)

解析

参考代码

class Solution {
public:ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {return dfs(list1, list2);   }ListNode* dfs(ListNode* list1, ListNode* list2) {// 出口if (list1 == nullptr) return list2;if (list2 == nullptr) return list1;// 比较大小后将当前较小节点作为头节点返回// 然后将它们链接起来if (list1->val < list2->val){list1->next = dfs(list1->next, list2);return list1;   }else{list2->next = dfs(list1, list2->next);return list2; }}
};

3. 反转链表

题目链接:206. 反转链表 - 力扣(LeetCode)

解析:

参考代码:

class Solution 
{
public:ListNode* reverseList(ListNode* head) {// 出口if (head == nullptr || head->next == nullptr) return head;// 将当前节点之后的链表反转,并返回头节点ListNode* newhead = reverseList(head->next);// 反转之后让后面链表的next指向headhead->next->next = head;// 再将head的next指向空head->next = nullptr;return newhead;}
};

4. 两两交换链表中的节点

题目链接:24. 两两交换链表中的节点 - 力扣(LeetCode)

解析:

参考代码:

class Solution {
public:ListNode* dfs(ListNode* head){// 出口if (head == nullptr || head->next == nullptr) return head;// 反转之后的链表并链接起来ListNode* next = head->next;head->next = dfs(next->next);next->next = head;return next;}ListNode* swapPairs(ListNode* head){ListNode* newnode = dfs(head);return newnode;}
};

5. Pow(x, n) —— 快速幂 

题目链接:50. Pow(x, n) - 力扣(LeetCode)

解析:

参考代码:

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

注:在这里因为n可能会取到INT_MIN,将其取负时会导致int类型的变量储存不下,因此需要long long类型来储存 。

②二叉树专题

1. 计算布尔⼆叉树的值

题目链接:2331. 计算布尔二叉树的值 - 力扣(LeetCode)

解析:

参考代码:

class Solution
{
public:bool evaluateTree(TreeNode* root){if (root->left == nullptr && root->right == nullptr) return root->val;bool left = evaluateTree(root->left);bool right = evaluateTree(root->right);return root->val == 2 ? left || right : left && right;}
};

2. 求根节点到叶节点数字之和

题目链接:129. 求根节点到叶节点数字之和 - 力扣(LeetCode)

解析:

参考代码:

class Solution
{
public:int dfs(TreeNode* root, int prev){prev = prev * 10 + root->val;if (root->left == nullptr && root->right == nullptr) return prev;int res = 0;if (root->left) res += dfs(root->left, prev);if (root->right) res += dfs(root->right, prev);return res;}int sumNumbers(TreeNode* root){return dfs(root, 0);}
};

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

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

相关文章

ffmpeg操作实战001:视频+音频文件融合

一、功能需求 把视频文件video.mp4 和音频文件audio.wav融合在一起&#xff0c;输出视频文件output.mp4 二、操作指令 ffmpeg -i video.mp4 -i audio.wav -c:v copy -map 0:v:0 -map 1:a:0 output.mp4 三、参数说明 ffmpeg: 这是用于执行FFmpeg命令行工具的命令。-i video…

深度学习(生成式模型)—— Consistency Models

文章目录 前言预备知识&#xff1a;SDE与ODEMethod实验结果 前言 Diffusion model需要多次推断才能生成最终的图像&#xff0c;这将耗费大量的计算资源。前几篇博客我们已经介绍了加速Diffusion model生成图像速率的DDIM和Stable Diffusion&#xff0c;本节将介绍最近大火的Co…

HarmonyOS 鸿蒙应用开发(九、还是蓝海,如何贡献第三方库)

快来共享第三方库吧&#xff0c;不但可以通过分享自己的成果&#xff0c;可以获得来自全球开发者的技术反馈和建议&#xff0c;提升自身技术能力&#xff0c;还有助于提高个人或团队在开源社区中的知名度和影响力。在流量时代和粉丝经济时代&#xff0c;获得曝光度和流量密码。…

Maven工程的配置及使用

一、Maven章节 Maven 是 Apache 软件基金会组织维护的一款专门为 Java 项目提供构建和依赖管理支持的工具 1.1、maven的作用 1&#xff09;依赖管理&#xff1a; 方便快捷的管理项目依赖的资源包&#xff08;jar包&#xff09;避免版本冲突 2&#xff09;统一项目结构&…

初识C语言·编译与链接

1 翻译环境和运行环境 C语言标准ANSI C 实现C语言代码的时候 一般需要经过两种环境&#xff0c;一是翻译环境&#xff0c;二是运行环境&#xff0c;计算机能识别的是二进制的指令&#xff0c;人写完代码后通过翻译环境&#xff0c;使代码变成计算机能读懂的可执行的机器指令&a…

go-redis hash slot 之旅

搭建redis 集群 创建一个网桥 docker network create -d bridge --subnet192.168.148.0/24 --gateway192.168.148.1 -o parenteno1 redis-net通过docker 文件创建redis 集群&#xff0c; 这里注意要不要使用redis 7以上的版本&#xff0c;不然会出问题 version: "3&quo…

ArcGIS学习(三)数据可视化

ArcGIS学习(三)数据可视化 1.矢量数据可视化 需要提前说明的是,在ArcGIS中,所有的可视化选项设置都是在“图层属性”对话框里面的“符号系统”中实现的。 对于矢量数据的可视化,主要有四种可视化方式: 按“要素”可视化按“类别”可视化按“数量”可视化按“图表”可视…

最近宣布的NIST后量子密码学标准的3个关键要点

当今世界依赖于许多保护措施&#xff0c;即使您没有注意到这一点。从手机和智能技术到网站&#xff0c;从支付交易到城市基础设施&#xff0c;人们经常与之互动的一切&#xff0c;都通过保护和检查技术来保护。量子计算机能够快速轻松地打破这些保护措施&#xff0c;这是政府和…

django+flask网上购物商城系统的设计与实现python-vue

全球经济在快速的发展&#xff0c;中国更是进步飞速&#xff0c;这使得国内的互联网技术进入了发展的高峰时期&#xff0c;这让中外资本不断转向互联网这个大市场[3]。在这个信息高度发达的现在&#xff0c;利用网络进行信息管理改革已经成为了人们追捧的一种趋势。“网上购物系…

01-Datahub是什么?

Datahub是LinkedIn开源的基于现代数据栈的元数据管理平台&#xff0c;原来叫做WhereHows 。经过一段时间的发展datahub于2020年2月在Github开源。 官网地址为&#xff1a;A Metadata Platform for the Modern Data Stack | DataHub 源码地址为&#xff1a;GitHub - datahub-p…

stable-diffusion | v1-5-pruned.ckpt和v1-5-pruned-emaonly.ckpt的区别

https://github.com/runwayml/stable-diffusion?tabreadme-ov-file#reference-sampling-script 对于 1.5 模型&#xff0c;其中可能包括四部分&#xff1a;标准模型、文本编码器、VAE模型、EMA模型。 标准模型&#xff1a;生成图片的核心模块&#xff0c;潜空间中的前向扩散和…

STM32F407移植OpenHarmony笔记9

继上一篇笔记&#xff0c;已经完成liteos内核的基本功能适配。 今天尝试启动OHOS和XTS兼容性测试。 如何启动OHOS&#xff1f; OHOS系统初始化接口是OHOS_SystemInit(void)&#xff0c;在内核初始化完成后&#xff0c;就能调用。 extern void OHOS_SystemInit(void); OHOS_Sys…