代码随想录算法训练营第十四天|二叉树理论基础、递归遍历、迭代遍历、统一迭代

代码随想录算法训练营第十四天|二叉树理论基础、递归遍历、迭代遍历、统一迭代

  • ● 二叉树理论基础
    • ● 1.基础理论
      • (1)概念
      • (2)性质
    • ● 二叉树的分类
      • (1)满二叉树
      • (2)完全二叉树
      • (3)二叉查找树(Binary Search Tree)
      • (4)平衡二叉搜索树
    • ● 二叉树的存储形式
    • ● 二叉树的遍历
      • 深度优先遍历
      • 广度优先遍历
    • ● 二叉树的定义
  • ● 递归遍历
    • ● 解题思路
    • ● 注意
    • ● 代码实现
  • ● 迭代遍历/非递归遍历
    • ● 解题思路
    • ● 代码实现
  • ● 统一迭代
    • ● 解题思路
    • ● 代码实现

● 二叉树理论基础

视频讲解:代码随想录|二叉树理论基础

● 1.基础理论

(1)概念

结点: 构成复杂数据结构的基本组成单位。

树:n(n >= 0)个结点的有限集,当n == 0时成为空树。对于任意非空树都满足:
(1)有且仅有一个特定的称为根(Root)的结点;
(2)当n>1时,其余结点可分为m(m>0)个互不相交的有限集T1、T2、…、Tn,其中每一个集合本身又是一棵树,并且称为根的子树。

结点的度: 结点拥有子树数目成为结点的度,对于二叉树结点的度只有0, 1, 2

结点关系: 像B,C这样作为结点A的左孩子或者右孩子的结点称为孩子结点,相应的A称为父亲结点,而对于B, C(有共同的父亲结点)又互为对方的兄弟结点
结点关系
树的度: 树中结点最大的度成为树的度。
层次: 根节点的层次为1,根的子节点层次为2,依次往下。
树的高度或深度: 树中结点的最大层次。
森林: 多棵互不相交的树的集合称为森林。

(2)性质

树的性质:
(1)树的节点数 = 所有结点度数 + 1;
(2)度为m的树第i曾最多有mi-1(i >= 1)个结点;
(3)高度为h的m茶树最多拥有(mh-1) / (m - 1)个结点;
(4)n个结点的m叉树最小高度为[ logm(n(m - 1) + 1)]。

● 二叉树的分类

在解题过程中二叉树有两种主要形式:满二叉树和完全二叉树。

(1)满二叉树

高度为h,并且由2{h} –1个结点的二叉树,被称为满二叉树。也就是说如果一个二叉树只有度为0的结点(叶子结点)和度为2的结点(分支结点),并且度为0的结点在同一层。
对于满二叉树而言,除了叶子结点,其余结点均达到最大子结点个数。
满二叉树

(2)完全二叉树

一颗二叉树中,只有最下面两层节点的度可以小于2,并且最下层的叶节点集中在靠左的若干位置上。
简单理解就是,在完全二叉树的基础上,除了最底层结点可能没有填满外,其余每层结点数都达到最大值。
对于完全二叉树,是在满二叉树的基础上,在最后一层从右向左依次缺少n(n >= 0)个元素,因此满二叉树是特殊的完全二叉树。
完全二叉树

(3)二叉查找树(Binary Search Tree)

二叉查找树是一个有序树 ,每一个二叉查找树都满足:
(1)若左子树不空,则左子树上所有结点的值均小于它的根结点的值;
(2)若右子树不空,则右子树上所有结点的值均大于它的根结点的值;
(3)二叉查找树的左、右子树也分别为二叉排序树。
二叉搜索树

(4)平衡二叉搜索树

平衡二叉搜索树又称为AVL树,得名于它的发明者 G. M. Adelson-Velsky 和 Evgenii Landis,他们在1962年的论文《An algorithm for the organization of information》中公开了这一数据结构。AVL树中,任一节点对应的两棵子树的最大高度差为1,因此它也被称为高度平衡树 。查找、插入和删除在平均和最坏情况下的时间复杂度都是O(logn) ,增加和删除元素的操作则可能需要借由一次或多次树旋转,以实现树的重新平衡。
![平衡二叉搜索树![](https://img-blog.csdnimg.cn/direct/4f70b79d6d8a44f79036b297221ef168.png)
C++中map、set、multimap,multiset的底层实现都是平衡二叉搜索树,所以map、set的增删操作时间时间复杂度是logn。

● 二叉树的存储形式

二叉树的存储形式包括链式存储和顺序存储。
我们常见的拥有左孩子指针和右孩子指针的形式就是链式存储 ,链式存储通过指针把分布在各个地址的节点串联一起;
链式存储

顺序存储 则依靠父亲结点和孩子结点之间所存在的数学关系,将其使用数组的方式存储在连续内存中

假设父亲结点序号为n,则左孩子结点序号为2n + 1,右孩子结点序号为2n + 2.

顺序存储

● 二叉树的遍历

二叉树主要有两种遍历方式:深度优先遍历和广度优先遍历。

深度优先遍历

从名字看,深度优先遍历就是先往深处走,当某一条路径走到头的时候,再返回上一个结点走另一条路径。
深度优先遍历包括前序遍历、中序遍历和后序遍历 ,均可以使用迭代法和递归法实现;对于前序遍历、中序遍历和后序遍历:
前序遍历:中左右
中序遍历:左中右
后序遍历:左右中
我们只需要知道前中后指的是根(root)的遍历顺序就很好理解。

广度优先遍历

广度优先遍历就是对树一层一层地进行遍历,包括了层次遍历。

● 二叉树的定义

链式存储

struct TreeNode{int val;TreeNode *left, *right;TreeNode(int x) : val(x), left(nullptr), right(nullptr){}
};

● 递归遍历

题目链接:
递归遍历|二叉树的前序遍历
递归遍历|二叉树的中序遍历
递归遍历|二叉树的后序遍历
视频讲解:代码随想录|递归遍历

● 解题思路

我们需要清楚递归算法的三要素,每次写递归的时候严格按照顺序做:
(1)确定递归函数的参数和返回值: 确定递归过程需要对哪些参数进行处理,就在递归函数传入该参数,并且还需要知道每次递归的返回值是什么;
(2)确定终止条件: 递归算法运行进场出现栈溢出,就是没写终止条件或者终止条件不正确。因为操作系统使用栈保存每一层的递归信息,如果递归没有种植,操作系统的内存栈必然溢出;
(3)确定单层递归逻辑: 就是需要清楚每一层递归对信息执行操作的顺序。

● 注意

对于traversal()函数的传参需要注意:
在给函数传递参数时,如果不使用引用,那么参数会被传递给函数的副本。在这种情况下,vector参数vec会被复制到函数traversal的每个递归调用中。

由于vector对象在内部维护了动态数组,而数组的复制可能是一个昂贵的操作,因此在递归函数中多次复制vector对象可能会导致性能下降。

为了避免复制vector对象,使用引用是一个更好的选择。通过将vector&作为参数类型,你将在函数调用中传递vector对象的引用,而不是复制它的内容。这样,所有的递归调用都将使用同一个vector对象,避免了不必要的复制。

如果你不使用引用,而是将vector作为参数类型,你需要修改traversal函数的定义和递归调用,以便正确地传递和使用vector对象。

● 代码实现

二叉树的前序遍历

/*** Definition for a binary tree node.* struct TreeNode {*     int val;*     TreeNode *left;*     TreeNode *right;*     TreeNode() : val(0), left(nullptr), right(nullptr) {}*     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}*     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}* };*/
class Solution {
public:void traversal(TreeNode* cur, vector<int>& vec){if(cur == nullptr) return;vec.push_back(cur->val);traversal(cur->left, vec);traversal(cur->right, vec);}vector<int> preorderTraversal(TreeNode* root) {vector<int> res;traversal(root, res);return res;}
};

二叉树的中序遍历

/*** Definition for a binary tree node.* struct TreeNode {*     int val;*     TreeNode *left;*     TreeNode *right;*     TreeNode() : val(0), left(nullptr), right(nullptr) {}*     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}*     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}* };*/
class Solution {
public:void traversal(TreeNode* cur, vector<int>& vec){if(cur == nullptr) return;traversal(cur->left, vec);vec.push_back(cur->val);traversal(cur->right, vec);}vector<int> inorderTraversal(TreeNode* root) {vector<int> res;traversal(root, res);return res;}
};

二叉树的后序遍历

/*** Definition for a binary tree node.* struct TreeNode {*     int val;*     TreeNode *left;*     TreeNode *right;*     TreeNode() : val(0), left(nullptr), right(nullptr) {}*     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}*     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}* };*/
class Solution {
public:void traversal(TreeNode* cur, vector<int>& vec){if(cur == nullptr) return;traversal(cur->left, vec);traversal(cur->right, vec);vec.push_back(cur->val);}vector<int> postorderTraversal(TreeNode* root) {vector<int> res;traversal(root, res);return res;}
};

● 迭代遍历/非递归遍历

● 解题思路

在迭代过程中一共有两个操作:
处理: 将元素放进result数组中
访问: 遍历节点

二叉树的前序遍历
对于递归,其底层调用栈维护访问信息,因此我们在迭代实现的时候也需要借助栈完成。
二叉树前序遍历为根左右,我们先对根节点进行处理,也就是需要先将根节点的值放入返回容器中;因为栈是先进后出的数据结构,访问的时候需要先放入右孩子,再放入左孩子,才能让左孩子为栈顶元素,对其先进行处理,随后再对右孩子访问。
前序遍历

二叉树的后序遍历
后序遍历为左右根,结合调整前序遍历访问节点的顺序我们可以得到根右左,此时可以得到逆序的后序遍历结果,因此我们仍需要对其进行一步逆序操作得到正确顺序的后序遍历。
后序遍历

二叉树的中序遍历
中序遍历无法直接调整前序遍历/后序遍历访问结点的代码达到正确的输出效果,需要我们使用一个cur遍历结点进行输出。
我们一路向左遍历完树的结点之后,此时curnullptr,中序遍历的顺序为左根右,此时我们可以对子树根节点进行处理,再将cur往回倒一个结点查看其右孩子是否为空,循环往复。
中序遍历

● 代码实现

二叉树的前序遍历

/**
/*** Definition for a binary tree node.* struct TreeNode {*     int val;*     TreeNode *left;*     TreeNode *right;*     TreeNode() : val(0), left(nullptr), right(nullptr) {}*     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}*     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}* };*/
class Solution {
public:vector<int> preorderTraversal(TreeNode* root) {vector<int> res;stack<TreeNode*> st;if(!root) return res;st.push(root);while(!st.empty()){//处理结点TreeNode* node = st.top();st.pop();res.push_back(node->val);//中//遍历结点if(node->right) st.push(node->right);//右;因为stack先进后出,所以需要先将right放入,才能先处理leftif(node->left) st.push(node->left);//左}return res;}
};

二叉树的后序遍历

/*** Definition for a binary tree node.* struct TreeNode {*     int val;*     TreeNode *left;*     TreeNode *right;*     TreeNode() : val(0), left(nullptr), right(nullptr) {}*     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}*     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}* };*/
class Solution {
public:vector<int> postorderTraversal(TreeNode* root) {stack<TreeNode*> st;vector<int> res;if(root == nullptr) return res;st.push(root);while(!st.empty()){TreeNode* node = st.top();st.pop();res.push_back(node->val);if(node->left) st.push(node->left);if(node->right) st.push(node->right);}reverse(res.begin(), res.end());return res;}
};

二叉树的中序遍历

/*** Definition for a binary tree node.* struct TreeNode {*     int val;*     TreeNode *left;*     TreeNode *right;*     TreeNode() : val(0), left(nullptr), right(nullptr) {}*     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}*     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}* };*/
class Solution {
public:vector<int> inorderTraversal(TreeNode* root) {vector<int> res;stack<TreeNode*> st;TreeNode* cur = root;while(cur || !st.empty() ){if(cur){st.push(cur);cur = cur->left;}else{cur = st.top();st.pop();res.push_back(cur->val); cur = cur->right;}}return res;}
};

● 统一迭代

● 解题思路

因为使用栈进行迭代遍历的时候无法同时解决访问结点和处理节点不一致的情况,因此我们需要将访问结点放入栈中,处理结点也放入栈中但需要标记,我们如何对处理结点进行标记呢?再处理节点放入栈之后,放入一个空指针作为标记即可。

● 代码实现

二叉树的前序遍历

class Solution {
public:vector<int> preorderTraversal(TreeNode* root) {vector<int> result;stack<TreeNode*> st;if (root != NULL) st.push(root);while (!st.empty()) {TreeNode* node = st.top();if (node != NULL) {st.pop();if (node->right) st.push(node->right);  // 右if (node->left) st.push(node->left);    // 左st.push(node);                          // 中st.push(NULL);} else {st.pop();node = st.top();st.pop();result.push_back(node->val);}}return result;}
};

二叉树的中序遍历

class Solution {
public:vector<int> inorderTraversal(TreeNode* root) {vector<int> result;stack<TreeNode*> st;if (root != NULL) st.push(root);while (!st.empty()) {TreeNode* node = st.top();if (node != NULL) {st.pop(); // 将该节点弹出,避免重复操作,下面再将右中左节点添加到栈中if (node->right) st.push(node->right);  // 添加右节点(空节点不入栈)st.push(node);                          // 添加中节点st.push(NULL); // 中节点访问过,但是还没有处理,加入空节点做为标记。if (node->left) st.push(node->left);    // 添加左节点(空节点不入栈)} else { // 只有遇到空节点的时候,才将下一个节点放进结果集st.pop();           // 将空节点弹出node = st.top();    // 重新取出栈中元素st.pop();result.push_back(node->val); // 加入到结果集}}return result;}
};

二叉树的后序遍历

class Solution {
public:vector<int> postorderTraversal(TreeNode* root) {vector<int> result;stack<TreeNode*> st;if (root != NULL) st.push(root);while (!st.empty()) {TreeNode* node = st.top();if (node != NULL) {st.pop();st.push(node);                          // 中st.push(NULL);if (node->right) st.push(node->right);  // 右if (node->left) st.push(node->left);    // 左} else {st.pop();node = st.top();st.pop();result.push_back(node->val);}}return result;}
};

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

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

相关文章

麦肯锡问题解决流程-为希望提升水平的产品经理量身定制

您是否想知道世界上最成功的产品经理如何始终如一地提供不仅满足而且超出预期的解决方案&#xff1f;秘密可能就在于世界上最负盛名的咨询公司之一麦肯锡公司所磨练的方法论。本文深入探讨了麦肯锡的问题解决流程&#xff0c;该流程专为希望提升水平的产品经理量身定制。 01. 麦…

AI - 碰撞避免算法分析(VO/RVO)

VO/RVO VO和RVO的原理本身理解起来比较简单的&#xff0c;就是根据两个圆形的相对半径&#xff0c;相对速度&#xff0c;相对位置&#xff0c;求出碰撞区域&#xff0c;然后将速度移出碰撞区域。VO是双方都是当作对方速度不变的情况下&#xff0c;各自都将速度完整的移出了会碰…

01.数据结构篇-链表

1.找出两个链表的交点 160. Intersection of Two Linked Lists (Easy) Leetcode / 力扣 例如以下示例中 A 和 B 两个链表相交于 c1&#xff1a; A: a1 → a2↘c1 → c2 → c3↗ B: b1 → b2 → b3 但是不会出现以下相交的情况&#xff0c;因为每个节点只有一个…

pycharm—配置python解释器【2023最新版】

目录 1.前言1.打开设置2. 点击到project >> Python interpreter3. 选择环境4. 确定之后等待加载就行了 1.前言 嗨嗨&#xff0c;大家好啊&#xff0c;我是小曼~ 刚入门python的伙伴们&#xff0c;一开始也会很多的问题。今天来给大家分享一下python新手必须学会的技巧 &…

关闭Windows 10自动更新方法

1. 关闭WindowsUpdate服务 如果你想要完全关闭Win10的自动更新功能&#xff0c;你可以在Windows服务中的WindowsUpdate选项里进行禁用设置。按照以下步骤&#xff0c;你就能完成操作。 按下“WinR”键&#xff0c;来启动“运行”&#xff0c;在运行中输入“services.msc”&…

监督学习:从数据中挖掘模式的引导

目录 前言1 定义2 举例说明3 回归问题4 分类问题结论 前言 监督学习是机器学习领域中的一种重要方法&#xff0c;通过给模型提供带有标签的训练数据&#xff0c;使其能够学习输入与输出之间的关系。这种学习方式在各个领域都有广泛的应用&#xff0c;从垃圾邮件过滤到医学诊断…

MATLAB 1:基础知识

MATLAB中的数据类型主要包括数值类型、逻辑类型、字符串、函数句柄、结构体和单元数组类型。这六种基本的数据类型都是按照数组形式存储和操作的。 MATLAB中还有两种用于高级交叉编程的数据类型&#xff0c;分别是用户自定义的面向对象的用户类类型和Java类类型。 1.1.1数值类…

Java+SpringBoot+Vue:高校科研管理的技术革新

✍✍计算机编程指导师 ⭐⭐个人介绍&#xff1a;自己非常喜欢研究技术问题&#xff01;专业做Java、Python、微信小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目&#xff1a;有源码或者技术上的问题欢迎在评论区一起讨论交流&#xff01; ⚡⚡ Java实战 |…

深入探索Pandas读写XML文件的完整指南与实战read_xml、to_xml【第79篇—读写XML文件】

深入探索Pandas读写XML文件的完整指南与实战read_xml、to_xml XML&#xff08;eXtensible Markup Language&#xff09;是一种常见的数据交换格式&#xff0c;广泛应用于各种应用程序和领域。在数据处理中&#xff0c;Pandas是一个强大的工具&#xff0c;它提供了read_xml和to…

算法沉淀——字符串(leetcode真题剖析)

算法沉淀——字符串 01.最长公共前缀02.最长回文子串03.二进制求和04.字符串相乘 01.最长公共前缀 题目链接&#xff1a;https://leetcode.cn/problems/longest-common-prefix/ 编写一个函数来查找字符串数组中的最长公共前缀。 如果不存在公共前缀&#xff0c;返回空字符串…

UnityShader玉石效果

效果&#xff1a; 代码&#xff1a; Shader "MyShader/Jade" {Properties{_DiffuseColor("漫反射颜色",color)(1,1,1,1)_ThicknessMap("厚度图",2d)"white"{}_AddColor("叠加颜色",color)(1,1,1,1)_CubeMap("环境贴图…

steam搬砖项目,“一个月赚8K+”真的假的?

在游戏中&#xff0c;搬砖党是永远都不能忽视的存在&#xff0c;随着游戏产业的不断发展&#xff0c;普通人也可以在steam搬砖项目中找到自己的生财之道。由于是低技术的重复工作&#xff0c;和现实的搬砖类似&#xff0c;所以才叫steam搬砖项目。 steam搬砖项目其实就和pdd无…