树与二叉树的学习笔记

news2024/12/28 5:57:54

树与二叉树

在之前的学习中,我们一直学习的是一个线性表,数组和链表这两种都是一对一的线性表,而在生活中的更多情况我们要考虑一对多的情况,这时候就引申出了我的新的数据结构那就是树,而树经过一些规矩的指定也就成为了我们的一个二叉树。

树的定义

树是一种非线性的一种数据结构,是一种一对多的数据结构,特点如下:

  • 结点之间存在分支关系
  • 结点之间存在层次关系

树的基本定义如下:

  1. 树(Tree)是n(n≥0)个结点的有限集

  2. 有且仅有一个特定的称为根(Root)的结点

  3. n = 0是一个空树

  4. 当n>1时,其余结点可分为m(m>0)个互不相交的有限集T 1 、T 2 、……、T m ,其中每一个集合本身又是一棵树,并且称为根的子树。

树如下图所示:

{018077C2-195C-4b6b-9992-11E2491C924C}

注:

  1. 根节点一定是唯一的,不可能存在多个根节点。

  2. 子树的个数没有限制,但它们一定是互不相交的

不可能出现下面的一个树

{A6CC5FFB-9411-42e5-B944-2C1845022B52}

结点的分类

在认识结点之前,我们先要认识一下这个概念。:结点拥有的子树数。就像之前图中的根结点的度是一个2。然后我们通过度来定义不同结点。

叶子结点为0的结点。

内部结点:除根结点之外所有的结点都是内部结点。

树的度 : 树的度是树内各结点的度的最大值

结点之间的关系

结点的子树的根称为该结点的孩子。下面这张图很好的展示了结点之间的一个关系。

{582A2FCE-E1A4-4b40-8F25-C7907BE4CC2E}

深度与高度

结点的层次(Level)从根开始定义起,根为第一层,根的孩子为第二层。树中结点的最大层次称为树的深度(Depth)或高度,当前树的深度为4

高度 :从距离该结点最远的叶节点到该结点所经过的边的数量

{C2FF787A-C453-436f-879B-B07B5ACE97D9}

二叉树

二叉树对于我们的意义

简单性:二叉树的结构相对简单,每个结点最多只有两个子结点,易于理解和实现。任何树都可与二叉树相互转换

高效性:二叉树的结构简单,许多基于二叉树的算法和数据结构具有高效性。例如,二叉搜索树的查找、插入和删除操作的时间复杂度都是O(log n),非常高效。

定义

二叉树是 n (n≥0) 个结点的有限集,它或者是空集 (n=0) ,或者由一个根结点及两棵互不相交的分别称作这个根的左子树和右子树的二叉树组成。

特点

  1. 每个结点最多有两个孩子
  2. 子树有左右之分
  3. 子树可以为空

基本形态

  1. 空二叉树。

  2. 只有一个根结点。

  3. 根结点只有左子树。

  4. 根结点只有右子树。

  5. 根结点既有左子树又有右子树

特殊二叉树

  1. 斜树

就是每一层都只有一个结点,结点的个数与二叉树的深度相同

{AB58348C-F46B-4d06-B37D-F4367A571F13}

  1. 满二叉树

因此,满二叉树的特点有:

(1)叶子只能出现在最下一层。出现在其他层就不可能达成平衡。

(2)非叶子结点的度一定是2。否则就是“缺胳膊少腿”了

(3)在同样深度的二叉树中,满二叉树的结点个数最多,叶子数最多

{CD7313C9-52D1-4d73-A928-77E337A135F1}

  1. 完全二叉树

对一棵具有n个结点的二叉树按层序编号,如果编号为i(1≤i≤n)的结点与同样深度的满二叉树中编号为i的结点在二叉树中位置完全相同,则这棵二叉树称为完全二叉树.

(1)叶子结点只能出现在最下两层。

(2)最下层的叶子一定集中在左部连续位置。

(3)倒数二层,若有叶子结点,一定都在右部连续位置。

(4)如果结点度为1,则该结点只有左孩子,即不存在只有右子树的情况。

(5)同样结点数的二叉树,完全二叉树的深度最小

{78A323FF-F2FB-43bb-8093-D9A9065E2159}

二叉链表

因为二叉树不适合用顺序表存储,所以我们应该采用一个二叉链表来存储我们的一个二叉树。

二叉树的链式存储结构是使用链表来表示二叉树的一种方法。在这种存储结构中,每个结点由数据域和两个指针域组成,分别用来指向该结点的左孩子结点和右孩子结点。这种结构的结点通常被称为二叉树结点。

通常我们的二叉树表示成下面这种结构

typedef struct BinaryTreeNode {
    int data;
    struct BinaryTreeNode *left,*right;
} BiNode;

{85465290-D4F9-4b8e-AA0D-D03955E0085B}

三种遍历方式

遍历原理
  1. 深度优先,沿着树的深度尽可能远的搜索数的一个分支。当到达树的最深处的时候,再回溯树的上一级及结点继续搜索完成遍历,知道遍历一整棵树。
  2. 广度优先,从根结点开始沿着树的宽度遍历树的结点,首先访问根节点,然后依次访问与根结点相邻的结点,直到遍历完整层的节点再向下一层移动。

这两种不同的原理会让我们的二叉树的遍历结果推向不一样。

深度优先(前序,中序,后序遍历)
前序遍历

前序遍历的规则:规则是若二叉树为空,则空操作返回,否则先访问根结点,然后前序遍历左子树,再前序遍历右子树。这里我们引入一道题目来帮助我们理解这个遍历二叉树的一个规则。

{AE65F8B7-8B14-4a46-A25F-020B100A9F0E}

二叉树的题目我们一般采用一个递归遍历的方式来解决这类问题。

我们现在用递归三部曲来思考一下这个问题:

  • 递归函数的参数和返回值

这里我们不难看出这个函数是一个空类型,每一次我们要传入的是他所在的一个结点。

void Traversal(TreeNode* root) {
   ;
}
  • 递归的终止条件

递归的终止条件从我们上面的规则不难看出我们只要走到头就结束,所以也就是我们传入的节点是一个空就退出

if (root == NULL) {
     return;
}
  • 单层递归的逻辑

这里的逻辑就是我们前序遍历的一个顺序,也就是中左右的原则。我们每一次都先把中添加到我们的数组中间,然后先向左节点进行一个递的操作,在向右节点进行一个递的操作。

void Traversal (TreeNode* root, vector<int> & vec) {
        if (root == NULL) {
            return;
        }
        vec.push_back(root->val);
        Traversal(root->left, vec);
        Traversal(root->right, vec)}

AC代码

class Solution {
public:
    void Traversal (TreeNode* root, vector<int> & vec) {
        if (root == NULL) {
            return;
        }
        vec.push_back(root->val); //中
        Traversal(root->left, vec); //左
        Traversal(root->right, vec); //右
    }
    vector<int> preorderTraversal(TreeNode* root) {
        vector<int> arrary;
        Traversal(root, arrary);
        return arrary;
    }
};
中序遍历

规则是若树为空,则空操作返回,否则从根结点开始(注意并不是先访问根结点),中序遍历根结点的左子树,然后是访问根结点,最后中序遍历右子树。

其实通过递归三部曲的分析我们发现这三种遍历方式在递归写法上的唯一区别就是我们最后单层递归的一个逻辑不同。

AC代码

class Solution {
public:
    void Traversal (TreeNode* root, vector<int> & vec) {
        if (root == NULL) {
            return;
        }
        Traversal(root->left, vec); //左
        vec.push_back(root->val); //中
        Traversal(root->right, vec); //右
    }
    vector<int> preorderTraversal(TreeNode* root) {
        vector<int> arrary;
        Traversal(root, arrary);
        return arrary;
    }
};
后序遍历

规则是若树为空,则空操作返回,否则从左到右先叶子后结点的方式遍历访问左右子树,最后是访问根结点。

AC代码

class Solution {
public:
    void Traversal (TreeNode* root, vector<int> & vec) {
        if (root == NULL) {
            return;
        }
        Traversal(root->left, vec); //左
        Traversal(root->right, vec); //右
        vec.push_back(root->val); //中
    }
    vector<int> preorderTraversal(TreeNode* root) {
        vector<int> arrary;
        Traversal(root, arrary);
        return arrary;
    }
};

注:二叉树可以采用一个迭代遍历的方式 ,我们采用一个栈的先进后出的结构来实现这个二叉树的一个遍历(因为递归的特性和栈一样是一个先进后出)下面给出代码。

我们现在对这个部分进行一个分析,入栈出栈的一系列的一些条件。
前序遍历很简单,我们发现我们按照我们的顺序进行一个遍历,也就是中左右的顺序进行遍历即可。

前序遍历

class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {
        stack<TreeNode*> stk;
        vector<int> vec;
        if (root == NULL) {
            return vec;
        }
        stk.push(root); //入栈一个根节点
        while (!stk.empty()) { //栈不为空
            TreeNode* tmp = stk.top(); //出栈根结点,然后分别将他的左右子树的根节点入栈
            stk.pop();
            vec.push_back(tmp->val);
            if (tmp->right) {
                stk.push(tmp->right);
            }
            if (tmp->left) {
                stk.push(tmp->left);
            }
        }
        return vec;
    }
};

中序遍历
而中序遍历就比较复杂了,我们发现我们不能像递归一样稍微修改一下就可以得出一个答案了,因为我们发现采用上面那种方法会出问题。所以这一次中序遍历我们则需要一个指针来进行一个迭代操作,这个指针用来记录我们的位置然后用栈进行一个数据的记录

class Solution {
public:
    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> result;
        stack<TreeNode*> st;
        TreeNode* cur = root;
        while (cur != NULL || !st.empty()) {
            if (cur != NULL) { // 指针来访问节点,访问到最底层
                st.push(cur); // 将访问的节点放进栈
                cur = cur->left;                // 左
            } else {
                cur = st.top(); // 从栈里弹出的数据,就是要处理的数据(放进result数组里的数据)
                st.pop();
                result.push_back(cur->val);     // 中
                cur = cur->right;               // 右
            }
        }
        return result;
    }
};

后序遍历
后序遍历的迭代可以采用和前序遍历一样的思路,同样是一次遍历便可以实现一个结果,最后我们进行一个反转数组的操作便可以实现了。

class Solution {
public:
    vector<int> postorderTraversal(TreeNode* root) {
        stack<TreeNode*> st;
        vector<int> result;
        if (root == NULL) return result;
        st.push(root);
        while (!st.empty()) {
            TreeNode* node = st.top();
            st.pop();
            result.push_back(node->val);
            if (node->left) {
             	st.push(node->left); // 相对于前序遍历,这更改一下入栈顺序 (空节点不入栈)   
            }
            if (node->right) {
             	st.push(node->right); // 空节点不入栈   
            }
        }
        reverse(result.begin(), result.end()); // 将结果反转之后就是左右中的顺序了
        return result;
    }
};
广度优先(层序遍历)

广度优先我们采用一个队列的数据结构实现遍历,记录每一层的节点然后依次入队出队。
请添加图片描述

class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
        queue<TreeNode*> que;
        if (root != NULL) {
            que.push(root);
        }
        vector<vector<int>> result;
        while (!que.empty()) {
            int size = que.size(); // 记录长度
            vector<int> vec;
            for (int i = 0; i < size; i++) { //这里的size一定要使用上面记录的,不能用que.size()因为这个会时刻变化
                TreeNode* node = que.front(); 
                que.pop();
                vec.push_back(node->val);
                if (node->left) {
                	que.push(node->left);
                }
                if (node->right) {
                	que.push(node->right);
                }
            }
            result.push_back(vec);
        }
        return result;
    }
};

小结

这里主要是认识二叉树以及遍历二叉树的三种方法进行了一个讲解,仅仅只是二叉树的一个入门知识,后面还有B+树和线索二叉树以及平衡二叉树需要我去学习,后续会继续进行补充。

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

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

相关文章

秒懂图神经网络(GNN)

​ 图神经网络&#xff08;GNN&#xff09;是一种深度学习模型&#xff0c;专门为处理图结构数据而设计。在现实世界中&#xff0c;许多数据都可以通过图来表示&#xff0c;比如社交网络中人与人之间的联系、分子结构中的原子连接等。图由顶点&#xff08;或称为节点&#xff0…

LLM使用方法介绍,持续更新

LLM使用方法介绍&#xff0c;持续更新 1. LLM本地搭建与运行 1. Ollama的安装 网址&#xff1a;https://ollama.com/点击Download选择对应的操作系统下载安装软件&#xff0c;软件默认安装在C盘无法选择路径&#xff1b; 安装完成后&#xff0c;WinR进入终端执行&#xff1a…

推荐一个在线stable-diffusion-webui,通过文字生成动画视频的网站-Ai白日梦

推荐一个可以通过文字生成动画视频的网站&#xff0c;目前网站处于公测中&#xff0c;应该是免费的。 点击新建作品 使用kimi或者gpt生成一个故事脚本 输入故事正文 新建作品&#xff0c;选择风格 我这里显示了六个风格&#xff0c;可以根据自己需要选一个 选择配音&…

54、图论-实现Trie前缀树

思路&#xff1a; 主要是构建一个trie前缀树结构。如果构建呢&#xff1f;看题意&#xff0c;应该当前节点对象下有几个属性&#xff1a; 1、next节点数组 2、是否为结尾 3、当前值 代码如下&#xff1a; class Trie {class Node {boolean end;Node[] nexts;public Node(…

Java——三层架构

在我们进行程序设计以及程序开发时&#xff0c;尽可能让每一个接口、类、方法的职责更单一些&#xff08;单一职责原则&#xff09;。 单一职责原则&#xff1a;一个类或一个方法&#xff0c;就只做一件事情&#xff0c;只管一块功能。 这样就可以让类、接口、方法的复杂度更低…

centos7上搭建mongodb数据库

1.添加MongoDB的YUM仓库&#xff1a; 打开终端&#xff0c;执行以下命令来添加MongoDB的YUM仓库&#xff1a; sudo vi /etc/yum.repos.d/mongodb-org-4.4.repo 在打开的文件中&#xff0c;输入以下内容&#xff1a; [mongodb-org-4.4] nameMongoDB Repository baseurlh…

黑马程序员Docker快速入门到项目部署笔记

视频来源&#xff1a; 01.Docker课程介绍_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV1HP4118797?p1 Docker官网&#xff1a; docker build | Docker Docshttps://docs.docker.com/reference/cli/docker/image/build/ 一、Docker的安装和配置 1.卸载旧版Docker…

利用STM32的定时器和中断实现精准时间控制

⬇帮大家整理了单片机的资料 包括stm32的项目合集【源码开发文档】 点击下方蓝字即可领取&#xff0c;感谢支持&#xff01;⬇ 点击领取更多嵌入式详细资料 问题讨论&#xff0c;stm32的资料领取可以私信&#xff01; 在嵌入式系统开发中&#xff0c;精确的时间控制是许多应用的…

软考 系统架构设计师系列知识点之大数据设计理论与实践(13)

接前一篇文章&#xff1a;软考 系统架构设计师系列知识点之大数据设计理论与实践&#xff08;12&#xff09; 所属章节&#xff1a; 第19章. 大数据架构设计理论与实践 第4节 Kappa架构 19.4.2 Kappa架构介绍 Kappa架构由Jay Kreps提出&#xff08;Lambda由Storm之父Nayhan M…

Qt 集成OSG

Qt 你好 | 专注于Qt的技术分享平台 一&#xff0c;新建一个 QOsgWidget 类&#xff0c;继承自osgQOpenGLWidget #ifndef QOSGWIDGET_H #define QOSGWIDGET_H#include <QObject> #include <osgViewer/Viewer> #include <osgQOpenGL/osgQOpenGLWidget> class…

ubuntu16安装docker及docker-compose

ubuntu16安装docker及docker-compose 一、环境前期准备 检查系统版本 系统版本最好在16及以上&#xff0c;可以确保系统的兼容性 lsb_release -a查看内核版本及系统架构 建议用 x86_64的系统架构&#xff0c;安装是比较顺利的 uname -a32的系统不支持docker&#xff0c;安…

【面试八股总结】Linux系统下的I/O多路复用

参考资料 &#xff1a;小林Coding、阿秀、代码随想录 I/O多路复用是⼀种在单个线程或进程中处理多个输入和输出操作的机制。它允许单个进程同时监视多个文件描述符(通常是套接字)&#xff0c;一旦某个描述符就绪&#xff08;一般是读就绪或者写就绪&#xff09;&#xff0c;能够…

【精简改造版】大型多人在线游戏BrowserQuest服务器Golang框架解析(2)——服务端架构

1.架构选型 B/S架构&#xff1a;支持PC、平板、手机等多个平台 2.技术选型 &#xff08;1&#xff09;客户端web技术&#xff1a; HTML5 Canvas&#xff1a;支持基于2D平铺的图形引擎 Web workers&#xff1a;允许在不减慢主页UI的情况下初始化大型世界地图。 localStorag…

C++——类和对象练习(日期类)

日期类 1. 构造函数和析构函数2. 拷贝构造和赋值运算符重载3. 运算符重载3.1 日期的比较3.2 日期加减天数3.3 日期减日期3.4 流插入和流提取 4. 取地址和const取地址重载5. 完整代码Date.hDate.c 对日期类进行一个完善&#xff0c;可以帮助我们理解六个默认成员函数&#xff0c…

30V-STM32设计项目

30V-STM32设计 一、项目描述 (已验证) 基于STM32c8t6芯片设计的开发板&#xff0c;支持4-30V宽电压输入&#xff0c;串口模式自动下载功能&#xff0c;支持串口和STlink&#xff0c;方式下载程序 二、原理图介绍 电源电路采用了DCDCLDO电路&#xff0c;如果是外接DC头供电的话&…

机器学习之sklearn基础教程

目录 前言 一、安装 Sklearn 二、导入 Sklearn 三、加载数据集 四、划分数据集 五、数据预处理 六、选择模型并训练 七、模型评估 八、实验不同的模型 九、调整模型参数 十、实例&#xff1a;使用Sklearn库来进行鸢尾花&#xff08;Iris&#xff09;数据集的分类 #s…

微信小程序 讯飞录音 点击按钮录音内容转文字

<page-meta page-style"{{ showPolish ? overflow: hidden; : }}" /> <view class"wrap"> <view class"header-tab" style"justify-content: {{typeList.length > 2 ? start : center}}"><view class&quo…

加速大数据分析:Apache Kylin使用心得与最佳实践详解

Apache Kylin 是一个开源的分布式分析引擎&#xff0c;提供了Hadoop之上的SQL接口和多维分析&#xff08;OLAP&#xff09;能力以支持大规模数据。它擅长处理互联网级别的超大规模数据集&#xff0c;并能够进行亚秒级的查询响应时间。Kylin 的主要使用场景包括大数据分析、交互…

【基础算法】双指针

1.移动零 移动零 思路&#xff1a; 利用双指针算法 cur&#xff1a;从左往右扫描数组&#xff0c;遍历数组 dest&#xff1a;处理好的区间包括dest dest初始化为-1&#xff0c;因为刚开始dest前应该没有非零元素。 即将非零元素移到dest之前即可 class Solution { public…

BFS解决FloodFill算法:(Leetcode:733. 图像渲染)

题目链接&#xff1a;733. 图像渲染 - 力扣&#xff08;LeetCode&#xff09; 使用广度优先遍历算法解决该问题&#xff1a; 从初始位置开始搜索&#xff0c;初始位置符合条件就入栈&#xff0c;并修改初始位置值。初始位置出栈。 再从初始位置开始广度优先搜索&#xff08;…