二叉树基础知识总结

2023-05-16

现实生活当中,我们每个家庭都会有一个家谱,来罗列家庭成员的关系。例如父亲下面的分支里有儿子或者女儿,而父亲又属于祖父祖母的下部分支。其实这个家谱在计算机科学中映射的就是树形的表示方法。可见在很久以前,我们的祖先其实就已经有意识地使用树形结构来进行数据记录了。

如图1所示,即为一个通用树的示意图。我们可以看到A点作为一个主节点,这里我们称其为根。而B、C、D、E四个节点属于A节点的子节点,我们称这四个节点为A节点的“孩子”,而A节点为这四个节点的“双亲”。和我们的家谱类似,有相同双亲的结点互为“兄弟”,图中的B、C、D就是E节点的兄弟。另外由于B、C、D、E四个节点同样可以有孩子,所以这四个节点其实也可以称为子树的根。另外,所有子树上的任何结点都是该结点的后裔,例如,F、G、H、I等都是A节点的后裔。从根结点到某个结点的路径上的所有结点都是该结点的祖先,例如A、B均为F节点的祖先。另外,没有子节点的节点我们称之为“叶子”,如图中的F、G、H等均是叶子节点。在这里,我们称结点拥有的子树的数目为“度”,而树的层数我们称之为高度,如图1中的树高度为3.

图1 树的示意图

 

树的遍历

对于所有的数据结构进行操作时,我们首先想到的都是对这个数据结构进行遍历。对于树的遍历一般分为三种遍历方法,分别是先序遍历、中序遍历以及后续遍历。这里的先后顺序均是参照于根节点来定义的。如先序遍历即先对根节点进行遍历,然后按照先左后右的顺序再依次进行先序遍历;中序遍历就是按照“左中右”的顺序来进行遍历操作,此时根节点在中间;后序遍历的操作是按照“左右中”顺序,此时可以发现根节点的遍历优先级已经放在了最后面。

图1中的树按照先序遍历的顺序遍历结果是ABFCGHDIJEK;按照中序遍历的结果是FBGCHAIDJEK;按照后续遍历的顺序,其遍历结果又变成了FBGHCIJDKEA。

由于树中每个节点都可以包含很多子树,所以对于包含很多子树的节点,我们进行表示的时候就会很复杂,所以在计算机科学中我们一般采用的是二叉树来进行数据存储管理。二叉树是最多有两个子树的树结构,通常子树被称作“左子树”(left subtree)和“右子树”(right subtree)。

二叉树一般包含5种形态,其具体表现形式如图2所示。第5种包含左右子树的二叉树,我们通常称之为满二叉树,这是一类高度为h,并且由2h-1个结点组成的二叉树。而第2、3以及第5种形态的二叉树也可以称之为完全二叉树,完全二叉树的是这么定义的:一棵二叉树中,只有最下面两层结点的度可以小于2,并且最下层的叶结点集中在靠左的若干位置上,这样的二叉树称为完全二叉树。

图 2  二叉树的5种形态

 

通过上面的举例说明,我们也可以看出二叉树的一个特点:一棵满二叉树必定是一棵完全二叉树,而完全二叉树未必是满二叉树。

接下来,我们将通过程序操作二叉树,来完成进一步的介绍。在数据结构中,二叉树的结构定义如下:

typedef struct BinaryTreeNode

{

    int Element;

    BinaryTreeNode * LeftChild;

    BinaryTreeNode * RightChild;

}BinaryTreeNode;

创建二叉树

在对二叉树进行操作之前,我们首先需要创造一个二叉树。二叉树的创建,最简单的思路是借用递归的思想来进行节点的重复创建。我们的场景是需要输入一个二叉树的根节点,然后通过键盘来依次对二叉树的所有节点进行赋值定义。由于二叉树中存在层级的概念,所以我们需要定义一个特殊字符来区分当前这个节点是否应该存放数据。本例程中我们采用“#”来表示当前这个节点是空节点,创建二叉树的过程中代码检测到“#”就不对当前的节点进行赋值,该节点也就是叶子节点。

本例中,我们按照先序遍历的顺序来创建二叉树。假设我们输入的字符串为“AB#D##C##”,按照我们创建二叉树的思想对此字符串进行分析可知,第一个字母A为根节点,第二个字母B为左孩子节点。接下来我们需要赋值的是B的左孩子节点,但键盘输入的是“#”,则B的左孩子节点即为空。第四个字母D为B节点的右孩子节点,然后我们需要进一步赋值D的左孩子节点,但此时键盘连续来了两个“#”,即D节点的左右孩子节点均为空节点。此时就返回到了A节点,我们开始对A节点的右子树进行赋值,我们把C给A的右子树。但是C后面连续又跟了两个“#”,这也就是说C节点又是一个叶子节点。该字符串输入后所组成的二叉树如图3所示:

图3 建立二叉树

具体代码实现如下:

void CreateBiTree(BinaryTreeNode* T)

{

    //按先序遍历的次序输入二叉树节点的值

    char ch;

    scanf("%c",&ch);

    if(ch=='#')

    {  

        T=NULL;

    }

    else   

    {  

        T = (BinaryTreeNode*)malloc(sizeof(BinaryTreeNode));

        T->Element=ch;     

        CreateBiTree(T->LeftChild);

        CreateBiTree(T->RightChild);   

    }

}

 

插入二叉树结点

二叉树的节点插入一般是针对二叉搜索树来实现的。二叉搜索树是一种特殊的二叉树,它或者是一棵空树,或者是具有下列性质的二叉树: 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;它的左、右子树也分别为二叉排序树。

假设我们当前存在一个二叉搜索树,如果我们想向该二叉树内插入一个对应的节点,根据二叉搜索树的性质,我们首先需要对待插入的节点进行数据比较,并且还需要在插入新的节点以后,该二叉树还是一颗二叉搜索树。

在具体操作过程中,我们需要遵循这么一个规律:将带插入数据与各节点比较,若待插入节点小于当前节点,则当前节点转移到其左孩子的节点处,这是因为二叉搜索树中左边的节点要比右边以及根节点都要小;当带插入节点比当前节点大时,同理需要将当前节点移到到右孩子节点处。具体的代码实现如下所示:

BinaryTreeNode* InsertNode(BinaryTreeNode *root,int data)

{

    BinaryTreeNode *newnode;

    BinaryTreeNode *current;

    BinaryTreeNode *Pre;

    newnode=(BinaryTreeNode*)malloc(sizeof(BinaryTreeNode *));

    if(newnode==NULL)

    {

        printf("\动态分配内存出错.\n");

    }

    newnode->Element=data;

    newnode->LeftChild=NULL;

    newnode->RightChild=NULL;

    if(root==NULL)

    {

        return newnode;

    }

    else

    {

        current=root;

        while(current!=NULL)

        {

            Pre=current;

            if(current->Element > data)

                current=current->LeftChild;

            else

                current=current->RightChild;

        }

        if(Pre->Element > data)

            Pre->LeftChild=newnode;

        else

            Pre->RightChild=newnode;

    }

    return root;

}

删除二叉树结点

二叉树中删除一个节点最重要的是要找到待删除结点以及它的父结点。因为我们在删除一个节点过程中,和链表的操作类似,都是要将该节点的左右孩子链接到其祖先节点下。如果待删除节点没有孩子节点,则只需要进行节点“销毁”即可;否则,均需要将其孩子节点让其双亲节点“接养”。我们将二叉树的节点删除分三类情况进行分析。

1、删除的结点为叶子结点,此时由于叶子节点并没有子节点,或者说左右节点都是NULL。所以在这种情况下,只需要把删除节点的父节点中对应的指针指向NULL即可。然后释放掉删除节点的空间;

2、删除的结点有左子树或者右子树,并且只有一个。这种情况下,我们只需要将待删除节点的父节点中对应的指针指向删除节点的子节点即可。然后释放掉删除节点的空间; 

3、删除的结点左右子树都有,这是最复杂的情况 。待删除节点包含有两个子节点,这种情况下,必须要找到一个替代删除节点的替代节点,并且保证二叉树的排序性。根据二叉树的排序性,可知替代节点的键值必须最接近待删除节点键值。也就是比待删除节点键值小的所有键值中最大的那个,或者是,比待删除节点键值大的所有键值中最小的那个。这两个键值所在的节点分别在删除节点的左子树中最右边的节点,删除节点右子树中最左边的节点;其代码具体实现如下所示。

BinaryTreeNode* DeleteNode(BinaryTreeNode *root,int data)

{

    BinaryTreeNode *newnode;

    BinaryTreeNode *current;

    BinaryTreeNode *Pre;

    newnode=(BinaryTreeNode*)malloc(sizeof(BinaryTreeNode *));

    if(newnode==NULL)

    {

        printf("\动态分配内存出错.\n");

    }

    newnode->Element=data;

    newnode->LeftChild=NULL;

    newnode->RightChild=NULL;

    if(root==NULL)

    {

        return newnode;

    }

    else

    {

        current=root;

        while(current!=NULL)

        {

            Pre=current;

            if(current->Element > data)

                current=current->LeftChild;

            else

                current=current->RightChild;

        }

        if(Pre->Element > data)

            Pre->LeftChild=newnode;

        else

            Pre->RightChild=newnode;

    }

    return root;

}

 

二叉树遍历

二叉树的遍历属于最基本的操作,即按照一定的顺序,在不重复访问同一节点的情况下,对所有节点都访问一遍。遍历采用的思想主要可以分为递归和非递归遍历两大类。递归的思想就是在运行当前程序的过程中再次调用当前程序,从递归的主要思想也可以看出需要使用递归的方法首先需要子问题和原始问题类似并且需要有一个出口来结束递归。二叉树正好满足这两个要求,每一个节点都是类似的,均包含当前键值以及左右孩子节点;并且每一颗二叉树均有叶子节点,这也正满足遍历过程中跳出递归的要求。

另外,遍历需要按照一定的顺序,而前文也介绍过二叉树的遍历主要有三种顺序:先序遍历、中序遍历以及后序遍历。但不论遍历顺序如何,其遍历的结果都是对所有节点进行访问。所以,在代码实现的过程中,我们可以通过先序遍历来举例说明。

首先我们先介绍非递归的二叉树遍历。非递归的遍历方法对于给定的根节点,按照既定顺序进行节点访问。先序遍历中我们按照根节点、左孩子节点再到右孩子节点的顺序来实现遍历。代码设计过程中,在访问完当前的根节点之后,首先进入左子树节点的判定,如果左子树节点为空,才进入右子树,直至访问到所有叶子节点。以下即为先序遍历的代码。

bool PrevListBinaryTree(BinaryTreeNode * root)

{

    BinaryTreeNode *Current = root;

    while(Current)

    {

        printf("The Element on this node is %d.\n",Current->Element);

        if(Current->LeftChild != NULL)

        {

            Current = Current->LeftChild;

        }

        else

        {

            Current = Current->RightChild;

        }

    }

   

    return OK;

}

         当我们借用递归的思想来实现这个先序遍历时,工作量便大大缩减。递归遍历的思想就是将左右子树分别看成单独的树,再次进行先序遍历,这样依次进行,直至遍历完所有叶子节点。其代码实现如下:

bool PrevListBinaryTree(BinaryTreeNode * root)

{

    if(root!= NULL)

    {

        printf("The Element on this node is %d.\n",root->Element);

        PrevListBinaryTree(root->LeftChild);

        PrevListBinaryTree(root->RightChild);

    }

    return OK;

}

参考文献

https://segmentfault.com/a/1190000008850005

https://blog.csdn.net/zhefutianxia/article/details/78702445

https://blog.csdn.net/xiaoquantouer/article/details/65631708

http://blog.51cto.com/9291927/2083190

https://baike.baidu.com/item/%E4%BA%8C%E5%8F%89%E6%A0%91%E9%81%8D%E5%8E%86/9796049

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

二叉树基础知识总结 的相关文章

  • conda:离线环境安装

    Aanconda的离线环境安装的必要条件是有一台可以联网的电脑 在后文中 xff0c 分别称可以联网的电脑为On line xff0c 不可以联网的电脑为Off line 以下即为对应的操作步骤 1 On line 下载annconda安装
  • Ubuntu:pip install gdal

    方法 sudo apt get update 必须首先安装gdal的lib xff0c python只是针对该lib的调用 sudo apt install gdal bin libgdal dev pip安装的版本必须和gdal一致 pi
  • Git:使用笔记

    git局部配置 git config user name 34 username 34 git config user email 34 email 34 git带用户密码clone git clone https username pas
  • Pytorch:conda安装不同版本的cuda

    我不会是最后一个知道可以用conda安装不同版本的cuda的人吧 通常的pytorch安装流程是 xff1a 首先安装NVIDIA驱动 xff0c 然后安装对应版本的cuda和cudnn最后再安装cuda支持的pytorch版本 然而实际上
  • obsidian使用技巧

    背景 obsidian是一个非常牛逼的本地笔记工具 xff0c 极大的提高了本人的学习能力 xff0c 卷的更加厉害了 此处简要记录一下在使用过程中遇到问题和对应的解决方案 xff0c 至于具体的使用方法网上多的是就不介绍了 三方插件推荐
  • ubuntu:命令行查询文件(夹)大小

    背景 使用命令行查询文件文件 夹 大小 参考 https www cnblogs com zhengyiqun1992 p 11183819 html 使用方法 查看当前文件夹下文件大小 ll h 输出如下 xff0c 其中文件夹大小是错误
  • VSCode:remote-ssh多级跳转

    背景 vscode目前是非常流行的编程工具 xff0c 提供了大量的插件 xff0c 尤其是其中的remote ssh xff0c 能够提供远程ssh连接服务器 xff0c 居家办公两不误 然而比较麻烦的事情是 xff0c 通常服务器为了保
  • Jittor:Jittor1.3.1之离线安装

    背景 Jittor是一个非常牛逼的框架 xff0c 维护了大量的官方demo xff0c 非常容易上手 与其他方法相比 xff0c 采用了即时编译的流程 xff0c 因此在效率上往往更高 但是在使用Jittor的过程中 xff0c 也遇到了
  • 灵活的按键处理程序 FlexibleButton,C程序编写,无缝兼容任意的处理器,支持任意 OS 和 non-OS

    灵活的按键处理程序 FlexibleButton 前言概述获取测试DEMO 程序说明程序入口用户初始化代码事件处理代码 FlexibleButton 代码说明按键事件定义按键注册接口按键事件读取接口按键扫描接口 问题和建议 前言 正好工作中
  • http请求头header、请求体body、请求行介绍

    HttpServletRequest对象代表客户端的请求 当客户端通过http协议请求访问 服务器的时候 http请求头的所有信息都封装在这个对象中 xff0c 通过这个对象 xff0c 可以获取客户端请求的所有信息 http请求包含请求行
  • 理解字节序 大端字节序和小端字节序

    以下内容参考了 http www ruanyifeng com blog 2016 11 byte order html https blog csdn net yishengzhiai005 article details 3967252
  • opencvSharp 学习笔记(二)

    参考文章 xff1a https github com shimat opencvsharp samples tree master SamplesCS Samples 参考opencvsharp的官方sample xff0c 在vs201
  • C++局部对象的析构

    class A span class hljs keyword public span A Func span class hljs attribute span span class hljs attribute span A A spa
  • BIND中基数树的建立

    BIND9新引入了视图的概念 xff0c 简单的来讲就是能根据不同的来源IP来返回不同的数据 其中网段的存储 xff0c 网段的快速匹配都是用基数树来实现的 下面是BIND创建基数树的代码 BIND的IP地址结构 span class hl
  • HTTP协议解析及C/C++代码实现

    超文本传输协议 HTTP 是分布式 协作 超媒体信息系统的应用层协议 这是自 1990 年以来万维网数据通信的基础 HTTP 是一种通用且无状态的协议 xff0c 它可以用于其他目的 xff0c 也可以使用其请求方法 错误代码和标头的扩展
  • C语言发邮件(带账号密码认证),简单的libesmtp实例

    需要安装libesmtp开发环境 xff0c centos下可以用yum安装 yum install libesmtp devel 编译时加上 lesmtp选项 xff0c 账号密码等替换成自己的 gcc o mail mail c les
  • 怎样在Markdown中贴图片

    前言 Markdown真的是一个很优秀的文本标记语言 语法也很简单 熟悉之后基本上可以完全抛弃Word了 用来写文档 一些博客 再好不过了 可是Markdown还是有一个痛点 那就是不大好贴图片 贴图 怎么样在markdown中贴图就不多说
  • 【四】【vlc-android】播放控制交互与demux解复用层、媒体数据流拉取层的具体数据传递和控制流程源码分析

    1 VLC中有很多demux mux encoder decoder模块 xff0c 因此需要先了解这些模块的加载原理 xff0c 模块的加载原理基本一致 xff0c 因此举例分析MP4解复用模块如何加载完成的 xff0c 主要流程如下 x
  • vs2013 设置不显示输出窗口

    工具 选项 项目与解决方案 常规 取消 在生成开始时显示输出窗口 的勾选

随机推荐

  • @Param注解的用法解析

    实例一 64 Param注解单一属性 dao层示例 Public User selectUser 64 param userName String name 64 param userpassword String password xml
  • mybatis choose标签的使用

    有时候我们并不想应用所有的条件 xff0c 而只是想从多个选项中选择一个 而使用if标签时 xff0c 只要test中的表达式为 true xff0c 就会执行 if 标签中的条件 MyBatis 提供了 choose 元素 if标签是与
  • Socket长连接实现思路

    长连接的正确实现方式 1 不关闭流实现长连接 xff1f 流关闭了而不关闭Socket xff0c 还是无法达到长连接的效果的 xff0c 所以 xff0c 要长连接 xff0c 流必须不能关闭 xff01 那么 xff0c 是不是直接不关
  • com.jacob.com.ComFailException: VariantChangeType failed

    调用jacob组件出错 com jacob com ComFailException VariantChangeType failed 在C Windows System32 config systemprofile下创建文件夹Deskto
  • CRC8校验 java实现

    以下为CRC8的实现 span class hljs keyword package span server span class hljs javadoc CRC8相关计算 encode utf 8 span class hljs jav
  • Java list add方法和addAll方法效率

    结论是 在数据量较小时 add方法配合for循环遍历比addAll来得快 但是在大量数据时 addAll的方法的效率更高 list addAll 是浅拷贝 只是将内存中的地址进行了拷贝 指向了原先list的末尾做了拼接
  • STM32——USART1重映射

    前言 为了使不同器件封装的外设 IO 功能数量达到最优 xff0c 可以把一些复用功能重新映射到其他一些引脚上 STM32 中有很多内置外设的输入输出引脚都具有重映射 remap 的功能 我们知道每个内置外设都有若干个输入输出引脚 xff0
  • Pg数据库比较时间大小

    postgresql 比较两个时间差大于 N个小时 摘要 PG 中时间想减后为interval xff0c 比较两个时间大于某个小时或者分钟等可以直接通过interval来实现 example1 xff1a 判断两个时间差大于4个小时 se
  • import java.util.LinkedList; import java.util.Queue; import java.util.Scanner; import java.util.Stac

    span class hljs keyword import span java util LinkedList span class hljs keyword import span java util Queue span class
  • 21-《电子入门趣谈》第四章_自己制作电路板-4.2洞洞板的介绍和经典案例使用教程

    好消息 xff1a 请在手机淘宝或闲鱼上搜索 电子入门趣谈 xff0c 有惊喜哦 我把全本电子入门趣谈的电子版 xff08 包括科技提升和理论升华部分 xff0c 共计50余万字 xff09 放到上面开始兜售啦 xff0c 如果您真的喜欢这
  • vlc-添加自定义的demuxer解复用插件----播放h264裸文件

    使用vlc3 0 6 在ubuntu 64bit上编译 xff0c vlc使用插件的方式组织对多种视频源的支持 xff0c 比如 avi mp4 mkv 等等 xff0c 这里想添加一个自己的demuxer xff0c 从一个h 264文件
  • 进程管理(五)--linux进程内核栈

    在进程创建时 xff0c 内核会为进程创建一系列数据结构 xff0c 其中最重要的就是上章学习的task struct结构 xff0c 它就是进程描述符 xff0c 表明进程在生命周期内的所有特征 同时 xff0c 内核为进程创建两个栈 x
  • [802.11]IEEE 802.11认证方式介绍

    一 802 11认证方式 802 11有开放系统认证 xff08 open system authentication xff09 和共享密钥认证 xff08 shared keyauthentication xff09 两种方式 1 1
  • 对‘std::xxx’未定义的引用

    出现一大串 对 std xxx 未定义的引用 的原因 xff1a 对于gcc后缀文件 xff0c 编译的时候可以用gcc g 43 43 xff0c 但是链接的时候要用g 43 43 xff0c 因为gcc和g 43 43 在编译的时候是相
  • 快速傅里叶变换

    FFT xff0c 即为快速傅氏变换 xff0c 是离散傅氏变换的快速算法 xff0c 它是根据离散傅氏变换的奇 偶 虚 实等特性 xff0c 对离散傅立叶变换的算法进行改进获得的 它对傅氏变换的理论并没有新的发现 xff0c 但是对于在计
  • C++项目开发中的一些问题及解决记录

    1 std vector类使用 xff1a https blog csdn net weixin 41743247 article details 90635931 2 vector求和 xff1a include lt numeric g
  • win32和android 的cocos2dx环境搭建详细教程

    转载 请注明出处 xff1a http blog csdn net aa4790139 article details 8086635 详细搭建步骤如下 xff1a 1 Android 开发环境搭建 Android开发环境搭建不是重点 相信
  • 快速傅里叶变换在信号处理中的应用

    傅里叶变换FT xff08 Fourier Transform xff09 是一种将信号从时域变换到频域的变换形式 它在声学 信号处理等领域有广泛的应用 计算机处理信号的要求是 xff1a 在时域和频域都应该是离散的 xff0c 而且都应该
  • 卷积

    随着机器学习的逐渐升温 xff0c 卷积神经网络这个专业词汇也越来越多地出现在我们眼前 卷积神经网络是一种前馈神经网络 xff0c 包括一维 二维以及三维卷积神经网络 这篇文章我们先来学习了解一下卷积的概念 在泛函分析中 xff0c 卷积是
  • 二叉树基础知识总结

    现实生活当中 xff0c 我们每个家庭都会有一个家谱 xff0c 来罗列家庭成员的关系 例如父亲下面的分支里有儿子或者女儿 xff0c 而父亲又属于祖父祖母的下部分支 其实这个家谱在计算机科学中映射的就是树形的表示方法 可见在很久以前 xf