【数据结构】二叉树(Binary Tree)

文章目录

  • 一、树的概念及结构
  • 二、二叉树的概念及结构
    • 1.二叉树的概念
    • 2.特殊的二叉树
    • 3.二叉树的性质
  • 三、二叉树的存储
    • 顺序存储
    • 链式存储
  • 四、二叉树的实现
    • 1.创建二叉树
    • 2.二叉树的遍历
      • 前序遍历
      • 中序遍历
      • 后序遍历
      • 层序遍历
      • 根据遍历顺序创建二叉树
    • 3.二叉树的基本操作
      • 1.总结点个数
      • 2.二叉树高度
      • 3.第K层结点个数
      • 4.查找
      • 5.判断二叉树是否是完全二叉树
    • 4.销毁二叉树

一、树的概念及结构

是一种非线性的数据结构,它是由n(n>=0)个有限结点组成一个具有层次关系的集合。它看起来像一棵倒挂的树,根朝上,而叶子朝下,所以我们把它叫做为树。
在这里插入图片描述

根结点:没有前驱结点,是当前树中所有结点的祖先。

除根节点外,其余结点被分成一棵结构与树类似的子树。每棵子树的根结点有且只有一个前驱,可以有0个或多个后继。因此,树是递归定义的。

g)

节点的度:一个节点含有的子树的个数称为该节点的度; 如上图:A的度为3。

叶子节点:度为0的节点称为叶子节点; 如上图:E、F、G为叶子节点。

双亲节点或父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点; 如上图:A是B的父节点,B是E的父节点。

孩子节点或子节点:一个节点含有的子树的根节点称为该节点的子节点; 如上图:B是A的子节点,F是D的子节点。

兄弟节点:具有相同父节点的节点互称为兄弟节点; 如上图:B、C、D是兄弟节点。

树的度:一棵树中,最大的节点的度称为树的度; 如上图:树的度为3.

节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推;

树的高度或深度:树中节点的最大层次/深度; 如上图:树的高度为3

堂兄弟节点:双亲在同一层的节点互为堂兄弟;如上图:E、F互为堂兄弟节点

节点的祖先:从根到该节点所经分支上的所有节点;如上图:A是所有节点的祖先

子孙:以某节点为根的子树中任一节点都称为该节点的子孙。如上图:所有节点都是A的子孙

森林:由m(m>0)棵互不相交的树的集合称为森林;

二、二叉树的概念及结构

1.二叉树的概念

二叉树是一种特殊的树,每个结点最多只能有两个子结点,即二叉树不存在度大于2的结点。
二叉树可以分为三个部分:根、左子树、右子树,每颗子树又可以划分为这三个部分,一直递归到叶子结点,叶子结点是其本身的根结点。
二叉树的子树有左右之分,次序不能颠倒,因此二叉树是有序树。
在这里插入图片描述

二叉树有五种基本形态:空树(B的右子树)、只有根节点(D、E、F)、只有左子树(B)、只有右子树、左右子树(A、C)都存在。

2.特殊的二叉树

满二叉树:除了叶子结点之外的所有结点都有两个子结点。如果一个满二叉树的层数为K,那么结点总数是20 + 21 + … + 2k-1 = 2k - 1个。

在这里插入图片描述

完全二叉树:除了最后一层,完全二叉树的每一层从左到右都是满的。也就是说,如果完全二叉树的层数为 h,那么前 h-1 层一定是一个满二叉树。
满二叉树是一种特殊的完全二叉树。

在这里插入图片描述

斜二叉树:除了叶子结点外,所有结点都只有一个结点,且是一个方向的结点,要么都只有左结点,要么都只有右结点。

在这里插入图片描述

3.二叉树的性质

1.任意二叉树第 i 层最多有2i-1个结点。 ( i ≥ \geq 1)
2.深度为 h 的任意二叉树的最大结点总数为2h-1。( h ≥ \geq 1)
3.任意二叉树中,度为0的叶子结点个数永远比度为2的结点个数多一个。
4.对于一个有n个结点的满二叉树,其深度 h= l o g 2 log_2 log2(n+1)(向上取整)

三、二叉树的存储

二叉树一般可以使用两种结构存储,一种顺序结构,一种链式结构。

顺序存储

二叉树的顺序结构存储就是使用数组来存储。按照层序遍历的顺序(从上到下,从左到右)依次将结点存储在数组中,如果某个结点的左结点或者右结点不存在,则对应的数组的位置就为空,不存储元素。
在这里插入图片描述

  • 二叉树顺序存储在物理上是一个数组,在逻辑上是一颗二叉树。
  • 二叉树以数组方式存储,父子结点下标的关系:左结点下标 = 2*父结点下标+1,右结点下标 = 2*父结点下标+2,父结点下标 = (任意孩子结点下标-1)/ 2。
  • 由上图可以看出,如果是非完全二叉树用数组来表示,则会存在空间浪费,所以使用数组一般只适合表示完全二叉树。而对于一般的二叉树,常用链式存储结构。
  • 链式存储

    二叉树的链式存储结构是指,用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。 通常的方法是链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别用来存储该结点左孩子和右孩子所在结点的存储地址 。

    在这里插入图片描述

    四、二叉树的实现

    接下来我们用链表来实现二叉树的创建与遍历。

    二叉树定义

    typedef char BTDataType;
    typedef struct BinaryTreeNode
    {BTDataType data;//数据struct BinaryTreeNode* left;//左孩子结点struct BinaryTreeNode* right;//右孩子结点
    }BTNode;
    

    1.创建二叉树

    在这里插入图片描述

    以上图为例建立一棵二叉树。
    以下代码并不是真正创建二叉树的方式,只是为了方便大家理解,所以手动快速创建一棵简单的二叉树。等后面学完二叉树遍历之后,我们再讲解二叉树的真正创建方式。

    //动态申请结点
    BTNode* BuyNode(BTDataType x)
    {BTNode* node = (BTNode*)malloc(sizeof(BTNode));if (NULL == node){perror("malloc");return NULL;}node->data = x;node->left = node->right = NULL;return node;
    }
    //创建二叉树
    BTNode* CreatTree()
    {BTNode* node1 = BuyNode('A');BTNode* node2 = BuyNode('B');BTNode* node3 = BuyNode('C');BTNode* node4 = BuyNode('D');BTNode* node5 = BuyNode('E');BTNode* node6 = BuyNode('F');node1->left = node2;node1->right = node3;node2->right = node4;node3->left = node5;node4->left = node6;return node1;
    }
    

    2.二叉树的遍历

  • 前面我们讲过,二叉树分为:根、左子树、右子树。每个结点的左右子树又可以划分为这三个部分,直到最后一层的叶子结点,叶子结点是其本身的根结点,左右结点为空。根据这一特点,可以看出,二叉树定义是递归式的。
  • 二叉树遍历是按照某种特定的规则,依次访问每个节点,并且每个节点只访问一次。根据根节点的访问顺序分为三种遍历方式:前序遍历、中序遍历和后序遍历。
  • 在这里插入图片描述

    前序遍历

    前序遍历的访问顺序:根结点、左子树、右子树。
    其中,对于每棵子树,访问顺序也是根结点、左子树、右子树,一直递归到空结点。

    在这里插入图片描述

    在这里插入图片描述相同颜色框的是相同根节点的左右子树

    比如上图的前序遍历访问顺序为:A B NULL D F NULL NULL NULL C E NULL NULL NULL
    而我们一般不打印空结点,所以前序遍历结果为:A B D F C E

    //前序遍历
    void PreOrder(BTNode* root)
    {if (NULL == root){//printf("NULL ");return;}printf("%c ", root->data);//打印根节点的值PreOrder(root->left);//遍历子树PreOrder(root->right);//遍历右子树
    }
    

    中序遍历

    中序遍历的访问顺序:左子树、根结点、右子树。
    还是拿上图示例,中序遍历访问顺序为:NULL B NULL F NULL D NULL A NULL E NULL C NULL
    去掉空结点后,中序遍历结果为:B F D A E C
    在这里插入图片描述

    在这里插入图片描述

    //中序遍历
    void InOrder(BTNode* root)
    {if (NULL == root){//printf("NULL ");return;}InOrder(root->left);printf("%c ", root->data);InOrder(root->right);
    }
    

    后序遍历

    中序遍历的访问顺序:左子树、右子树、根结点。

    在这里插入图片描述

    在这里插入图片描述上图二叉树的后序遍历访问顺序为:NULL NULL NULL F NULL D B NULL NULL E NULL C A
    去掉空结点后,后序遍历结果为:F D B E C A

    //后序遍历
    void PostOrder(BTNode* root)
    {if (NULL == root){//printf("NULL ");return;}PostOrder(root->left);PostOrder(root->right);printf("%c ", root->data);
    }
    

    层序遍历

    二叉树的层次遍历最好理解,从二叉树的根结点开始,从上到下每一层按照从左到右的顺序遍历。比如上图中二叉树的层序遍历为:A B C D E F

    前面三种遍历都是以递归的形式,而层序遍历可以用队列来实现非递归遍历。前面我们已经用C语言写过队列(点击跳转文章)结构,可以在vs中直接将队列的相关代码copy过来使用,在源文件和头文件中直接添加现有项即可。

    并将队列中的类型改为二叉树类型

    //typedef int QDataType;
    typedef struct BinaryTreeNode* QDataType;
    

    具体过程是:先将当前节点入队列访问当前结点,再将当前结点出队列。然后将当前结点的左结点和右结点按顺序入队,如果为空结点则不用入队,直到队列为空。

    //层序遍历
    void LevelOrder(BTNode* root)
    {assert(root);Queue q;QueueInit(&q);QueuePush(&q, root);while (!QueueEmpty(&q)){BTNode* front = QueueFront(&q);QueuePop(&q);printf("%c ", front->data);if(front->left != NULL)QueuePush(&q, front->left);if (front->right != NULL)QueuePush(&q, front->right);}QueueDestroy(&q);printf("\n");
    }
    

    根据遍历顺序创建二叉树

    数组a存储前序遍历的顺序,比如通过前面我们知道二叉树的前序遍历结果为A B NULL D F NULL NULL NULL C E NULL NULL NULL ;我们将NULL替换成字符#表示空。数组下标用指针接收,否则递归函数中不会改变实参。

    //根据前序遍历的顺序二叉树创建
    BTNode* BTreeCreate_PreOrder(BTDataType* a, int* pi)
    {// 通过前序遍历的数组"AB#DF###CE###"构建二叉树if (a[*pi] == '#'){(*pi)++;//数组下标+1return NULL;}BTNode* node = BuyNode(a[*pi]);//根据数组元素构建二叉树结点(*pi)++;node->left = BTreeCreate_PreOrder(a, pi);node->right = BTreeCreate_PreOrder(a, pi);return node;
    }
    

    根据中序/后序顺序来构建二叉树同理。

    int main()
    {char a[] = "AB#DF###CE###";int i = 0;BTNode* root = BTreeCreate_PreOrder(a, &i);//BTNode* root = BTreeCreate();PreOrder(root);printf("\n");InOrder(root);printf("\n");PostOrder(root);printf("\n");return 0;
    }
    

    3.二叉树的基本操作

    在前面二叉树递归遍历的基础上,我们可以实现一些二叉树的基本操作。

    1.总结点个数

    转换成子问题:二叉树总结点个数=1(根节点个数)+ 左子树的结点个数 + 右子树的节点个数。很容易想到递归。

    //二叉树的总结点个数
    int BTreeSize(BTNode* root)
    {if (NULL == root){return 0;}return 1 + BTreeSize(root->left) + BTreeSize(root->right);
    }
    

    2.二叉树高度

    当前树的高度 = 左右子树中比较高的高度+1

    //二叉树的高度
    int BTreeLevel(BTNode* root)
    {if (NULL == root)//空结点则返回0{return 0;}int leftLevel = BTreeLevel(root->left);//左子树的高度int rightLevel = BTreeLevel(root->right);//右子树的高度return leftLevel > rightLevel ? leftLevel + 1 : rightLevel + 1;
    }
    

    最好不要写成下面这样,会造成重复递归,判断时遍历左右子树递归两次,返回结果时又递归一次,栈帧开销会很大。

    return BTreeLevel(root->left) > BTreeLevel(root->right) ? BTreeLevel(root->left) + 1 : BTreeLevel(root->right) + 1;
    

    3.第K层结点个数

    求第K层结点个数,转换成子问题:求左子树第K-1层结点个数 + 右子树第K-1层结点个数

    //第k层结点的个数
    int BTreeLevelKSize(BTNode* root, int k)
    {assert(k > 0);//默认根节点为第一层,所以不存在第0层if (NULL == root)//空结点{return 0;}if (1 == k)//遍历到第k层{return 1;}return BTreeLevelKSize(root->left, k-1)+BTreeLevelKSize(root->right, k-1);
    }
    

    4.查找

    //查找值为x的结点
    BTNode* BTreeFind(BTNode* root, BTDataType x)
    {if (NULL == root)return NULL;if (root->data == x)return root;BTNode* left = BTreeFind(root->left, x);if (left)//左子树找到则返回return left;//左子树未找到,只可能存在于右子树return BTreeFind(root->right, x);
    }
    

    5.判断二叉树是否是完全二叉树

    完全二叉树按层序走,非空结点一定是连续的。每次将当前结点的左右结点入队列,不用判空,空结点也入队列。遇到第一个空结点时,停止入队,开始判断队列中的元素是否都为空,若出现非空结点,说明不是完全二叉树,反之则是完全二叉树。

    //判断二叉树是否是完全二叉树
    bool BTreeComplete(BTNode* root)
    {assert(root);Queue q;QueueInit(&q);QueuePush(&q, root);while (!QueueEmpty(&q)){BTNode* front = QueueFront(&q);QueuePop(&q);//完全二叉树按层序走,非空结点一定是连续的if (NULL == front)//遍历到第一个空结点则退出循环,进入下个while循环判断{break;}else{//不为空则将左右孩子结点入队QueuePush(&q, front->left);QueuePush(&q, front->right);}}//如果是完全二叉树,则队列中元素全是NULLwhile (!QueueEmpty(&q)){BTNode* front = QueueFront(&q);QueuePop(&q);//有非空结点则说明非空结点不是完全连续,说明不是完全二叉树if (front != NULL){QueueDestroy(&q);//返回之前及时释放空间,防止内存泄露return false;}}QueueDestroy(&q);return true;
    }
    

    4.销毁二叉树

    根节点最后释放,否则无法访问左右子树。

    //二叉树销毁(后序遍历)
    void BTreeDestroy(BTNode* root)
    {if (NULL == root)return;BTreeDestroy(root->left);BTreeDestroy(root->right);free(root);root = NULL;
    }
    

    完整代码:链式二叉树

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

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

相关文章

ctfshow之_萌新web9至web10

一、访问在线靶场ctfshow 1、web9 如下图所示,进入_萌新赛的web9问题,题目提醒flag在config.php中: 如上图所示,可以get传参,且传入的参数需要正则匹配system、exec、highlight,且不区分大小写&#xff0…

C++设计模式|创建型 5.原型模式

1.什么是原型模式? 原型模式⼀种创建型设计模式,该模式的核⼼思想是基于现有的对象创建新的对象,⽽不是从头开始创建。 在原型模式中,通常有⼀个原型对象,它被⽤作创建新对象的模板。新对象通过复制原型对象的属性和状…

Mac IDEA 自动补全mybatis sql语句

导航 Mac IDEA 自动补全mybatis sql语句一、点击IDEA 右侧Database选项二、选择添加对应数据库三、输入数据库信息和方案四、输入数据库信息和方案五、成功 Mac IDEA 自动补全mybatis sql语句 背景: 想在Mapper中,能够实现自动检索数据库表和对应的字段…

web前端之sass中的颜色函数、active按钮激活、hover鼠标悬浮、disabled禁用、scss循环、css

MENU 效果图htmlsassscss编译后的css页面css 效果图 注意查看蓝色按钮。 html <div class"box"><button class"btn type_1">按钮</button><button class"btn type_2">按钮</button><button class"btn ty…

一文读懂通用漏洞评分系统CVSS4.0:顺带理清CVE、CWE及其与CVSS之间的关系

事件响应和安全团队论坛 (FIRST&#xff0c;Forum of Incident Response and Security Teams) 于 2023 年 11 月 1 日正式推出第四版通用漏洞评分系统 (CVSS 4.0&#xff0c;Common Vulnerability Scoring System version 4.0)。CVSS 4.0 是评估计算机系统安全漏洞严重性的行业…

C++ 多态性

一 多态性的分类 编译时的多态 函数重载 运算符重载 运行时的多态 虚函数 1 运算符重载的引入 使用C编写程序时&#xff0c;我们不仅要使用基本数据类型&#xff0c;还要设计新的数据类型-------类类型。 一般情况下&#xff0c;基本数据类型的运算都是运算符来表达&#x…

【C++】详解C++的模板

目录 概念 ​编辑 语法 函数模板 类模板 非类型模板参数 模板的特化 函数模板特化 类模板特化 全特化 偏特化 分离编译 概念 模板是C中非常厉害的设计&#xff0c;模板把通用的逻辑剥离出来&#xff0c;让不同的数据类型可以复用同一种模板的逻辑&#xff0c;甚至可以…

PHP黑魔法之md5绕过

php本身是一种弱语言,这个特性决定了它的两个特点: 输入的参数都是当作字符串处理变量类型不需要声明,大部分时候都是通过函数进行类型转化php中的判断有两种: 松散比较:只需要值相同即可,类型不必相同,不通类型比较会先转化为同类型,比如全数字字符串和数字比较,会比…

凸优化理论学习三|凸优化问题(一)

系列文章目录 凸优化理论学习一|最优化及凸集的基本概念 凸优化理论学习二|凸函数及其相关概念 文章目录 系列文章目录一、优化问题&#xff08;一&#xff09;标准形式的优化问题&#xff08;二&#xff09;可行点和最优点&#xff08;三&#xff09;局部最优点&#xff08;四…

《Python编程从入门到实践》day28

# 昨日知识点回顾 安装Matplotlib 绘制简单的折线图 # 今日知识点学习 15.2.1 修改标签文字和线条粗细 # module backend_interagg has no attribute FigureCanvas. Did you mean: FigureCanvasAgg? # 解决办法&#xff1a;matplotlib切换图形界面显示终端TkAgg。 #…

使用Three.js绘制快速而逼真的水

本文将利用GPUComputationRenderer来实现水波纹的绘制&#xff0c;相似的案例可以看threejs官方的GPGPU Water示例。更多精彩内容尽在数字孪生平台。 什么是 GPGPU GPGPU代表通用图形处理单元&#xff08;General-Purpose Graphic Processing Unit&#xff09;&#xff0c;意思…

1146 -Table ‘performance schema.session variables‘ doesn‘t exist的错误解决

一、问题出现 今天在本地连数据库的时候&#xff0c;发现这个问题&#xff0c;哎呦我擦&#xff0c;差点吓死了 二、解决办法 1&#xff09;找文件 用everything搜一下MySQL Server 5.7 然后去Windows服务找一下MySQL配置文件的具体路径 如果知道那最好&#xff0c;不知道那…

宝塔8.1.0去除绑定用户

非要绑定手机号&#xff0c;确实很烦 1&#xff0c;/www/server/panel/BTPanel __init__.py if not public.is_bind():return redirect(/bind, 302) 将is_bind的路由全部注释 2&#xff0c;/www/server/panel/class下 panelPlugin.py 注释异常&#xff0c; 新增 softLis…

SSL协议

SSL 安全传输协议&#xff08;安全套接层&#xff09; 也叫TLS ---- 传输层安全协议 SSL的工作原理&#xff1a;SSL协议因为是基于TCP协议工作的&#xff0c;通信双方需要先建立TCP会话。因为SSL协议需要进行安全保证&#xff0c;需要协商安全参数&#xff0c;所以也需要建立…

springboot房屋租赁系统

摘要 房屋租赁系统&#xff1b;为用户提供了一个房屋租赁系统平台&#xff0c;方便管理员查看及维护&#xff0c;并且可以通过需求进行设备信息内容的编辑及维护等&#xff1b;对于用户而言&#xff0c;可以随时进行查看房屋信息和合同信息&#xff0c;并且可以进行报修、评价…

清理缓存简单功能实现

在程序开发中&#xff0c;经常会用到缓存&#xff0c;最常用的后端缓存技术有Redis、MongoDB、Memcache等。 而有时候我们希望能够手动清理缓存&#xff0c;点一下按钮就把当前Redis的缓存和前端缓存都清空。 功能非常简单&#xff0c;创建一个控制器类CacheController&#xf…

【计算机毕业设计】基于SSM+Vue的线上旅行信息管理系统【源码+lw+部署文档+讲解】

目录 1 绪论 1.1 研究背景 1.2 设计原则 1.3 论文组织结构 2 系统关键技术 2.1JSP技术 2.2 JAVA技术 2.3 B/S结构 2.4 MYSQL数据库 3 系统分析 3.1 可行性分析 3.1.1 技术可行性 3.1.2 操作可行性 3.1.3 经济可行性 3.1.4 法律可行性 3.2系统功能分析 3.2.1管理员功能分析 3.2.…

JavaScript精粹(一)

JavaScript&#xff08;简称为JS&#xff09;是一种广泛应用于网页开发的脚本语言&#xff0c;具有以下几个主要作用&#xff1a; 网页交互&#xff1a;JavaScript 可以用于创建动态的网页效果&#xff0c;例如响应用户的操作&#xff0c;实现页面内容的动态更新&#xff0c;以…

Spring整合其他技术

文章目录 Spring整合mybatis思路分析Mybatis程序核心对象分析整合Mybatis 代码实现 Spring整合Junit修改成警告 Spring整合mybatis 思路分析 Mybatis程序核心对象分析 上面图片是mybatis的代码&#xff0c;上述有三个对象&#xff0c;分别是sqlSessionFactory&#xff0c;sqlS…

数据库SQL编写规范-SQL书写规范整理(SQL语句书写规范全解-Word原件)

编写本文档的目的是保证在开发过程中产出高效、格式统一、易阅读、易维护的SQL代码。 1 编写目 2 SQL书写规范 3 SQL编写原则 软件全套精华资料包清单部分文件列表&#xff1a; 工作安排任务书&#xff0c;可行性分析报告&#xff0c;立项申请审批表&#xff0c;产品需求规格说…