0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

新数据结构“树”的详细介绍

算法与数据结构 来源:袁厨的算法小屋 作者:厨子 2021-05-25 15:28 次阅读
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

下面我们将镜头切到袁记菜馆。

小二:掌柜的,最近大家都在忙着种树,说是要保护环境。

老板娘:树 ? 咱们店有呀,前几年种的那棵葡萄树,不是都结果子了吗?就数你吃的最多。

小儿:这...

大家应该猜到,咱们今天要唠啥了。

之前给大家介绍了链表,栈,哈希表 等数据结构

今天咱们来看一种新的数据结构,树。

PS:本篇文章内容较基础,对于没有学过数据结构的同学会有一些帮助,如果你已经学过的话,也可以复习一下,查缺补漏,后面会继续更新这个系列。

我们先来看下百度百科对树的定义

树是 n (n 》= 0) 个节点的有限集。n = 0 时 我们称之为空树, 空树是树的特例。

在任意一棵非空树中:

有且仅有一个特定的节点称为根(Root)的节点

当 n 》 1 时,其余节点可分为 m (m 》 0)个互不相交的有限集 T1、T2、....Tm,其中每一个集合本身又是一棵树,并且称为根的子树。

我们一起来拆解一下上面的两句话,到底什么是子树呢?见下图

c0ab973a-bc8b-11eb-9e57-12bb97331649.png

例如在上面的图中

有且仅有一个特定的节点称为根节点,也就是上图中的橙色节点。

当节点数目大于 1 时,除根节点以外的节点,可分为 m 个互不相交的有限集 T1,T2.。..。...Tm。

例如上图中,我们将根节点以外的节点,分为了 T1 (2,3,4,5,6,7),T2(8,9)两个有限集。

那么 T1 (绿色节点)和 T2(蓝色节点)就是根节点(橙色节点)的子树。

我们拆解之后发现,我们上图中的例子符合树的要求,另外不知道大家有没有注意到这个地方。

除根节点以外的节点,可分为 m 个互不相交的有限集。

那么这个互不相交又是什么含义呢?见下图。

c0b46c16-bc8b-11eb-9e57-12bb97331649.png

我们将 (A) , (B) , (C) , (D) 代入上方定义中发现,(A) , (B) 符合树的定义,(C), (D) 不符合,这是因为 (C) , (D) 它们都有相交的子树。

好啦,到这里我们知道如何辨别树啦,下面我们通过下面两张图再来深入了解一下树。

主要从节点类型,节点间的关系下手。

c0f8e22e-bc8b-11eb-9e57-12bb97331649.png

c1060198-bc8b-11eb-9e57-12bb97331649.png

这里节点的高度和深度可能容易记混,我们代入到现实即可。

我们求物体深度时,从上往下测量,求高度时,从下往上测量,节点的高度和深度也是如此。

二叉树

我们刷题时遇到的就是二叉树啦,下面我们一起来了解一下二叉树

二叉树前提是一棵树,也就是需要满足我们树的定义的同时,还需要满足以下要求

每个节点最多有两个子节点,分别是左子节点和右子节点。

注意我们这里提到的是最多,所以二叉树并不是必须要求每个节点都有两个子节点,也可以有的仅有一个左子节点,有的节点仅有一个右子节点。

下面我们来总结一下二叉树的特点

每个节点最多有两棵子树,也就是说二叉树中不存在度大于 2 的节点,节点的度可以为 0,1,2。

左子树和右子树是有顺序的,有左右之分。

假如只有一棵子树 ,也要区分它是左子树还是右子树

好啦,我们已经了解了二叉树的特点,那我们分析一下,下图中的树是否满足二叉树定义,共有几种二叉树。

c12424b6-bc8b-11eb-9e57-12bb97331649.png

上图共为 5 种不同的二叉树,在二叉树的定义中提到,二叉树的左子树和右子树是有顺序的,所以 B,C 是两个不同的二叉树,故上图为 5 种不同的二叉树。

特殊的二叉树

下面我们来说几种比较特殊的二叉树,可以帮助我们刷题时,考虑到特殊情况。

满二叉树

满二叉树:在一棵二叉树中,所有分支节点都存在左子树和右子树,并且所有的叶子都在同一层,这种树我们称之为满二叉树。见下图

c17206f4-bc8b-11eb-9e57-12bb97331649.png

我们发现只有 (B) 符合满二叉树的定义,我们发现其实满二叉树也为完全二叉树的一种。

完全二叉树

完全二叉树:叶子结点只能出现在最下层和次下层,且最下层的叶子结点集中在树的左部。

哦!我们可以这样理解,除了最后一层,其他层的节点个数都是满的,而且最后一层的叶子节点必须靠左。

下面我们来看一下这几个例子

c18792a8-bc8b-11eb-9e57-12bb97331649.png

上面的几个例子中,(A)(B)为完全二叉树,(C)(D)不是完全二叉树

斜二叉树

这个就很好理解啦,斜二叉树也就是斜的二叉树,所有的节点只有左子树的称为左斜树,所有节点只有右子树的二叉树称为右斜树。

诺,下面这俩就是。

c1e59484-bc8b-11eb-9e57-12bb97331649.png

另外还有 一些二叉树的性质, 比如第 i 层至多有多少节点,通过叶子节点求度为 2 的节点, 通过节点树求二叉树的深度等, 这些是考研常考的知识, 就不在这里进行赘述,需要的同学可以看一下王道或者天勤的数据结构, 上面描述的很具体, 并附有证明过程。

好啦,我们已经了解了二叉树,那么二叉树如何存储呢?

如何存储二叉树

二叉树多采用两种方法进行存储,基于数组的顺序存储法和基于指针的二叉链式存储法

我们在之前说过的堆排序中,其中对堆的存储采用的则是顺序存储法,具体细节可以看这篇文章

一个破堆排我搞了 4 个动画?

这里我们再来回顾一下如何用数组存储完全二叉树。

c1f8ed18-bc8b-11eb-9e57-12bb97331649.png

我们首先看根节点,也就是值为 1 的节点,它在数组中的下标为 1 ,它的左子节点,也就是值为 4 的节点,此时索引为 2,右子节点也就是值为 2 的节点,它的索引为 3。

我们发现其中的关系了吗?

数组中,某节点(非叶子节点)的下标为 i , 那么其左子节点下标为 2*i(这里可以直接通过相乘得到左孩子, 也就是为什么空出第一个位置, 如果从 0 开始存,则需要 2i+1 才行), 右子节点为 2i+1,其父节点为 i/2 。既然我们完全可以根据索引找到某节点的 左子节点 和右子节点,那么我们用数组存储是完全没有问题的。

但是,我们再考虑一下这种情景,如果我们用数组存储斜树时会出现什么情况?

c2088d40-bc8b-11eb-9e57-12bb97331649.png

通过 2*i 进行存储左子节点的话,如果遇到斜树时,则会浪费很多的存储空间,这样显然是不合适的,

所以说当存储完全二叉树时,我们用数组存储,无疑是最省内存的,但是存储斜树时,则就不太合适。

所以我们下面介绍一下另一种存储结构,链式存储结构。

因为二叉树的每个节点, 最多有两个孩子, 所以我们只需为每个节点定义一个数据域,两个指针域即可

val 为节点的值, left 指向左子节点, right 指向右子节点。

c214aa8a-bc8b-11eb-9e57-12bb97331649.png

下面我们对树 1, 2, 3, 4, 5, 6, 7 使用链式存储结构进行存储,即为下面这种情况。

c238ab24-bc8b-11eb-9e57-12bb97331649.png

二叉链表的节点结构定义代码

public class BinaryTree {

int val;

BinaryTree left;

BinaryTree right;

BinaryTree() {}

BinaryTree(int val) { this.val = val; }

BinaryTree(int val, BinaryTree left, BinaryTree right) {

this.val = val;

this.left = left;

this.right = right;

}

}

另外我们在刷题的时候, 可以自己实现一下数据结构, 加深我们的理解, 提升基本功, 而且面试考的也越来越多。

好啦,下面我们说一下树的遍历,

下面我会用动图的形式进行描述,很容易理解, 我也会为大家总结对应的题目,欢迎各位阅读。

遍历二叉树

二叉树的遍历指从根节点出发,按照某种次序依次访问二叉树的所有节点,使得每个节点都被访问且访问一次。

我们下面介绍二叉树的几种遍历方法及其对应的题目, 前序遍历, 中序遍历 , 后序遍历 , 层序遍历 。

前序遍历

前序遍历的顺序是, 对于树中的某节点,先遍历该节点,然后再遍历其左子树,最后遍历其右子树。

只看文字有点生硬, 下面我们直接看动画吧

前序遍历

测试题目: leetcode 144. 二叉树的前序遍历

代码实现(递归版)

class Solution {

public List《Integer》 preorderTraversal(TreeNode root) {

List《Integer》 arr = new ArrayList《》();

preorder(root,arr);

return arr;

}

public void preorder(TreeNode root,List《Integer》 arr) {

if (root == null) {

return;

}

arr.add(root.val);

preorder(root.left,arr);

preorder(root.right,arr);

}

}

时间复杂度 : O(n) 空间复杂度 : O(n) 为递归过程中栈的开销,平均为 O(logn),但是当二叉树为斜树时则为 O(n)

为了控制文章篇幅, 二叉树的迭代遍历形式, 会在下篇文章进行介绍。

中序遍历

中序遍历的顺序是, 对于树中的某节点,先遍历该节点的左子树, 然后再遍历该节点, 最后遍历其右子树

继续看动画吧, 如果有些遗忘或者刚开始学数据结构的同学可以自己模拟一下执行步骤。

中序遍历

测试题目: leetcode 94 题 二叉树的中序遍历

代码实现(递归版)

class Solution {

public List《Integer》 inorderTraversal(TreeNode root) {

List《Integer》 res = new ArrayList《》();

inorder(root, res);

return res;

}

public void inorder (TreeNode root, List《Integer》 res) {

if (root == null) {

return;

}

inorder(root.left, res);

res.add(root.val);

inorder(root.right, res);

}

}

时间复杂度 : O(n) 空间复杂度 : O(n)

后序遍历

后序遍历的顺序是,对于树中的某节点, 先遍历该节点的左子树, 再遍历其右子树, 最后遍历该节点。

后序遍历

测试题目: leetcode 145 题 二叉树的后序遍历

代码实现(递归版)

class Solution {

public List《Integer》 postorderTraversal(TreeNode root) {

List《Integer》 res = new ArrayList《》();

postorder(root,res);

return res;

}

public void postorder(TreeNode root, List《Integer》 res) {

if (root == null) {

return;

}

postorder(root.left, res);

postorder(root.right, res);

res.add(root.val);

}

}

时间复杂度 : O(n) 空间复杂度 : O(n)

层序遍历

顾名思义,一层一层的遍历。

c498f356-bc8b-11eb-9e57-12bb97331649.png

比如刚才那棵二叉树的层序遍历序列即为 1 ~ 9.

二叉树的层序, 这里我们需要借助其他数据结构来实现, 我们思考一下, 我们需要对二叉树进行层次遍历, 从上往下进行遍历, 我们可以借助什么数据结构来帮我们呢 ?

我们可以利用队列先进先出的特性,使用队列来帮助我们完成层序遍历, 具体操作如下

让二叉树的每一层入队, 然后再依次执行出队操作,

对该层节点执行出队操作时, 需要将该节点的左孩子节点和右孩子节点进行入队操作,

这样当该层的所有节点出队结束后, 下一层也就入队完毕,

不过我们需要考虑的就是, 我们需要通过一个变量来保存每一层节点的数量。

这样做是为了防止, 一直执行出队操作, 使输出不能分层

测试题目: leetcode 102 二叉树的层序遍历

题目代码

class Solution {

public List《List《Integer》》 levelOrder(TreeNode root) {

List《List《Integer》》 res = new ArrayList《》();

if (root == null) {

return res;

}

//入队 root 节点,也就是第一层

Queue《TreeNode》 queue = new LinkedList《》();

queue.offer(root);

while (!queue.isEmpty()) {

List《Integer》 list = new ArrayList《》();

int size = queue.size();

for (int i = 0; i 《 size; ++i) {

TreeNode temp = queue.poll();

//孩子节点不为空,则入队

if (temp.left != null) queue.offer(temp.left);

if (temp.right != null) queue.offer(temp.right);

list.add(temp.val);

}

res.add(list);

}

return res;

}

}

时间复杂度:O(n) 空间复杂度:O(n)

大家如果吃透了二叉树的层序遍历的话,可以顺手把下面几道题目解决掉,思路一致,甚至都不用拐弯

leetcode 107. 二叉树的层序遍历 II

leetcode 103. 二叉树的锯齿形层序遍历上面两道题仅仅是多了翻转

leetcode 199. 二叉树的右视图

leetcode 515. 在每个树行中找最大值

leetcode 637. 二叉树的层平均值

这三道题,仅仅是加了一层的一些操作

leetcode 116 填充每个节点的下一个右侧

leetcode 117 填充每个节点的下一个右侧2

这两个题对每一层的节点进行链接即可,两道题目代码一致

大家可以去顺手解决这些题目,但是也要注意一下其他解法,把题目吃透。不要为了数目而刷题,好啦,今天的节目就到这里啦,我们下期见!

原文标题:把二叉树揉碎(一)

文章出处:【微信公众号:算法与数据结构】欢迎添加关注!文章转载请注明出处。

责任编辑:haq

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 数据
    +关注

    关注

    8

    文章

    7314

    浏览量

    93968

原文标题:把二叉树揉碎(一)

文章出处:【微信号:TheAlgorithm,微信公众号:算法与数据结构】欢迎添加关注!文章转载请注明出处。

收藏 人收藏
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

    通过优化代码来提高MCU运行效率

    选择时间复杂度低的算法。 根据访问模式选择数据结构。频繁查找用哈希表,有序数据用二叉等。 查表法:对于复杂的数学计算(如sin, log),或者协议解析,预先计算好结果存于数组中,用空间换时间
    发表于 11-12 08:21

    Verilog实现使用Booth编码和Wallace的定点补码乘法器原理

    是Wallace结构和CSA结构的对比。其结构的关键特性在于利用不规则的树形结构对所有的准备好输入
    发表于 10-23 08:01

    分享一个嵌入式学习阶段规划

    (如指针实现链表增删查改),排查内存泄漏、野指针数据结构与算法:掌握线性表 / 栈 / 结构,学查找 / 排序算法,手写代码(如二分查找、快速排序)电子电路基础:学电路原理、电子元件特性,看懂简单
    发表于 09-12 15:11

    【HZ-T536开发板免费体验】6、使用protoc-gen-gorm生成标准化的数据结构

    在设计espnow协议的时候,考虑到我需要在esp32,Linux设备,web上使用相同的数据结构,那就需要考虑一下,是否使用一个通用的跨平台序列化数据结构。这时候我想起了protobuf,这个就是
    发表于 08-26 00:32

    ePTFE防水透气膜结构的设计与应用场景介绍

    ePTFE(膨体聚四氟乙烯)防水透气膜的结构设计与其应用场景密切相关,其层数并非固定,而是根据功能需求通过复合其他材料形成多层结构。今天我们就详细介绍ePTFE膜的
    的头像 发表于 05-15 11:26 1422次阅读
    ePTFE防水透气膜<b class='flag-5'>结构</b>的设计与应用场景<b class='flag-5'>介绍</b>

    程序设计与数据结构

    《程序设计与数据结构》重点阐述了三大方向内容: 1. C语言学习中的痛点:针对当前工程师在C语言学习中的痛点,如指针函数与函数指针,如何灵活应用结构体等。从变量的三要素(变量的类型,变量的值和变量
    发表于 05-13 16:45

    开关电源拓扑结构介绍

    的选择可能会从一开始就给电源设计带来厄运。 正确选择并合理应用各种拓扑对于整个电路设计来说至关重要。本文将对常见的开关电源基本拓扑进行详细介绍,让读者能够更快更好地了解和使用这些拓扑。 开关电源的10
    发表于 05-12 16:04

    白话理解RCC时钟(可下载)

    时钟就像是单片机的“心脏”,单片机正常工作离不开时钟的支持,下图是我们单片机的时钟 ,它反映了单片机的时钟关系。我们来详细描述一下时钟的工作原理。寄存器上电后有一个复位值,大家看我画红线的这个
    发表于 03-27 13:50 0次下载

    硅光通信技术的原理和基本结构

    本文介绍了硅光芯片的发展历史,详细介绍了硅光通信技术的原理和几个基本结构单元。
    的头像 发表于 02-26 17:31 1774次阅读
    硅光通信技术的原理和基本<b class='flag-5'>结构</b>

    科技在物联网方面

    科技在物联网领域有多方面的涉及和发展,以下是一些具体信息: 传感器技术合作 与传感器公司合作:宇科技与一些传感器技术公司有合作,例如奥比中光为宇机器狗提供激光雷达及结构光传感
    发表于 02-04 06:48

    谁能详细介绍一下track-and-hold

    在运放和ADC芯片的数据手册中经常看到track-and-hold,谁能详细介绍一下track-and-hold?
    发表于 01-20 09:10

    嵌入式学习-飞凌嵌入式ElfBoard ELF 1板卡-初识设备之Makefile修改

    不同而新增加了dts,则需要在这个Makefile的这个位置添加上对应的.dtb文件名参与编译。ELF 1使用的设备命名为imx6ull-elf1-emmc.dts,是基于NXP官方evk板子的设备imx6ull-14x14-evk.dts修改而来,修改的内容及方法将
    发表于 01-13 09:09

    飞凌嵌入式ElfBoard ELF 1板卡-初识设备之Makefile修改

    不同而新增加了dts,则需要在这个Makefile的这个位置添加上对应的.dtb文件名参与编译。ELF 1使用的设备命名为imx6ull-elf1-emmc.dts,是基于NXP官方evk板子的设备imx6ull-14x14-evk.dts修改而来,修改的内容及方法将
    发表于 01-10 09:23

    嵌入式学习-飞凌嵌入式ElfBoard ELF 1板卡-初识设备之设备组成和结构

    的一项技能。设备的起源设备(Device Tree)是一种描述硬件资源的数据结构,它由uboot传递给Linux内核,被内核解析,内核根据设备中的硬件描述信息加载利用相应驱动资源
    发表于 01-08 08:32

    飞凌嵌入式ElfBoard ELF 1板卡-初识设备之设备组成和结构

    的一项技能。设备的起源设备(Device Tree)是一种描述硬件资源的数据结构,它由uboot传递给Linux内核,被内核解析,内核根据设备中的硬件描述信息加载利用相应驱动资源
    发表于 01-07 09:16