数据结构入门指南:二叉树

目录

文章目录

前言

 1. 树的概念及结构

   1.1 树的概念

 1.2 树的基础概念

1.3 树的表示

1.4 树的应用

 2. 二叉树

2.1 二叉树的概念

 2.2 二叉树的遍历


前言

        在计算机科学中,数据结构是解决问题的关键。而二叉树作为最基本、最常用的数据结构之一,不仅在算法和数据处理中发挥着重要作用,也在日常生活中有着丰富的应用。无论是搜索引擎的索引算法、文件系统的组织方式,还是编译器的语法分析,二叉树都扮演着不可或缺的角色。为了便于大家更好的入门二叉树,本期先向大家简单介绍一下二叉树的基本性质,以及代码理解实现,给大家预预热。


 1. 树的概念及结构

   1.1 树的概念

         树是一种非线性的数据结构,它是由n(n>=0)个有限结点组成一个具有层次关系的集合。把它叫做树是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。

  • 有一个特殊的结点,称为根结点,根节点没有前驱结点
  • 除根节点外,其余结点被分成M(M>0)个互不相交的集合T1、T2、……、Tm,其中每一个集合Ti(1<= i<= m)又是一棵结构与树类似的子树。每棵子树的根结点有且只有一个前驱,可以有0个或多个后继
  • 因此,树是递归定义的。

 那这样是树还是非树?

 

 答案是非树,树形结构中,子树之间不能有交集,否则就不是树形结构。

 1.2 树的基础概念

  • 节点的度:一个节点含有的子树的个数称为该节点的度; 如上图:A的为6
  • 叶节点或终端节点:度为0的节点称为叶节点; 如上图:B、C、H、I...等节点为叶节点
  • *非终端节点或分支节点:度不为0的节点; 如上图:D、E、F、G...等节点为分支节点
  • 双亲节点或父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点; 如上图:A是B的父节点
  • 孩子节点或子节点:一个节点含有的子树的根节点称为该节点的子节点; 如上图:B是A的孩子节点
  • 兄弟节点:具有相同父节点的节点互称为兄弟节点; 如上图:B、C是兄弟节点
  • 树的度:一棵树中,最大的节点的度称为树的度; 如上图:树的度为6
  • *节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推;
  • *树的高度或深度:树中节点的最大层次; 如上图:树的高度为4
  • *堂兄弟节点:双亲在同一层的节点互为堂兄弟;如上图:H、I互为兄弟节点
  • *节点的祖先:从根到该节点所经分支上的所有节点;如上图:A是所有节点的祖先
  • *子孙:以某节点为根的子树中任一节点都称为该节点的子孙。如上图:所有节点都是A的子孙
  • *森林:由m(m>0)棵互不相交的树的集合称为森林;

带星号的了解即可。

        这里我们重点说一下树的高度和节点层次,不同的数据结构书中一般有两种方式表示树的高度,一种是从0开始例如上述的树,根节点A高度就是0,到P、Q高度就是3。另外一种是从1开始,根节点1的高度为1,那P、Q的高度就是4。个人更推荐使用从1开始的,如果使用从0开始的,那如果是空树,在使用0表示就有点说不过去了,那空树的高度就只能是-1了。如果使用从1开始的,那空树就可以使用0来表示。

1.3 树的表示

         树结构相对线性表就比较复杂了,要存储表示起来就比较麻烦了,既然保存值域,也要保存结点和结点之间的关系,实际中树有很多种表示方式如:双亲表示法,孩子表示法、孩子双亲表示法以及孩子兄弟表示法等。

         首先在定义树的节点时就很为难,一个节点到底要定义多少个指向子节点的指针:

struct TreeNode
{Datatype x;struct TreeNode* child1;struct TreeNode* child2;……};

 要想定义一个节点就要先知道一个树的度,例如上述的树:

         这棵树的度为6,那我们定义时就要定义6个指针变量吗?那大部分的节点的子节点并没有达到6个,这样就会很浪费,定义也很费劲。

在C++中,有这样一种定义方法:

struct TreeNode
{Datatype x;vector<struct TreeNode*> childs;
};

         可以不规定个数,它使用数组的方式来存储,如果不够还可以进行增容,这种方式是使用顺序表来存储孩子节点的信息。

         还有一种非常巧的方式,叫孩子兄弟表示法,即左孩子右兄弟。

         拿这棵树为例,这种方法在定义节点时就只定义两个指针,一个指针叫左孩子指针,一个指针叫右兄弟指针。怎么指向呢?就是说无论一个节点有多少个孩子,它的孩子指针就只指向第一个孩子(最左边的孩子节点),剩下的孩子用第一个孩子的兄弟指针指向第二个,第二个孩子的兄弟指针指向第三个。

表示出来就是这样的结构:

 除此之外还有其他的表示法,这里就不再一一列举。

1.4 树的应用

 在现实生活中我们也经常使用到树状结构,例如:文件存储

 2. 二叉树

         了解完树的基本概念后,我们接下来进入二叉树的学习。

2.1 二叉树的概念

 一棵二叉树是结点的一个有限集合,该集合:

  1. 或者为空
  2. 由一个根节点加上两棵别称为左子树和右子树的二叉树组成

 二叉树的特点:

  1. 二叉树不存在度大于2的结点
  2. 二叉树的子树有左右之分,次序不能颠倒,因此二叉树是有序树

 对于任何一颗二叉树都是由一下几种情况复合而成。

         当然关于二叉树的基础概念还有很多,今天就先简单介绍,接下来给大家来点干货,先让大家切身体验一下二叉树。                                                

 2.2 二叉树的遍历

 当我们看到任何一颗二叉树都应该把它分为三个部分:

  • 根节点
  • 左子树
  • 右子树

 我们以这棵树为例进行分析:

         A为这棵树的根节点,B及其以下的节点(D、E)被称为左子树,C及其以下节点被称为右子树。然后B子树仍然可以分为D及其以下节点是左子树,E及其以下节点是右子树,然后再分,直到子节点为NULL,停止。

我们这里用的是分治算法。分治算法:

        把大问题分成类似的子问题,子问题再分成子问题,……,直到子问题无法再分割为止。

遍历可分为三种:

前序:也叫先根遍历,遍历顺序为:根、左子树、右子树

中序:也叫中根遍历,遍历顺序为:左子树、根、右子树

后序:也叫后根遍历,遍历顺序为:左子树、右子树、根

         我们先来尝试以下前序遍历,理解了前序遍历后两个就简单了。还是以这棵树为例:

         前序遍历,我们是先访问根,然后是左子树、右子树。那应该先遍历A,然后遍历A的左子树B(及其以下节点),然后就以B为根继续遍历它的左子树D(及其以下节点),然后再次以D为根开始,遍历左子树(NULL),然后开始返回到D,D再遍历右子树(NULL),然后D(这个子树)就遍历完毕,返回到B,B开始遍历右子树E(及其以下节点),E左子树为NULL返回到E,然后遍历右子树,右子树也为空(E子树遍历完毕),然后返回E(B的右子树遍历完毕),接着返回B(B的所有子树遍历完毕),接着返回到A,A开始遍历右子树……这个规律很符合递归。

        遍历完的顺序为:A、B、D、NULL、NULL、E、NULL、NULL、C、NULL、NULL。大部分学校讲的都是A、B、D、E、C。大部分同学应该都知道这个规律,但不知道为什么这样遍历。

        根据这个思路我们再来写一下中序遍历,中序遍历先访问左子树,然后是跟,最后是右子树。还是从A开始找,A的左子树B,然后以B为根,找B的左子树,接着以D为根,找D的左子树,D的左子树为NULL(D左子树遍历结束),返回到D(根),开始遍历D的右子树……

         最后遍历的顺序为:【NULL、D、NULL(B的左子树)、B(根)、NULL、E、NULL              (B的右子树)、】(A的左子树)、A(根)、【NULL、C、NULL】(A的右子树)。整理一下:

NULL、D、NULL、B、NULL、E、NULL、A、NULL、C、NULL(D、B、E、C、A)。

         我们接着写一下后序遍历:NULL、NULL、D、NULL、NULL、E、B、NULL、NULL、C、A(D、E、B、C、A)。

我们再来练一个:

         前序遍历:A(根)||这部分为整体二叉树的根、B(左子树)、NULL(B的左子树)、D(B的右子树)、F(D的左子树)、NULL(F的左子树)、NULL(F的右子树)、NULL(D的右子树)||这部分属于A的左子树、C、E、NULL(E的左子树)、NULL(E的右子树)、NULL(C的右子树)||这部分为A的右子树。

 后续的中序遍历和后续遍历大家可以自己私下练一下。

        好了我们已经了解了遍历,接下来我们来说实现以下前序遍历。给大家来点干货,便于大家更好理解。

 我们依然以这个简单的二叉树为例进行实现。

 我们先定义一个二叉树的节点:

typedef char Datatype;
typedef struct BinaryTreeNode
{Datatype data;struct BinaryTreeNode* left;struct BinaryTreeNode* right;
}BTNode;

 然后就是它的前序遍历的实现:

void PrevOrder(BTNode* root)
{}

        我们知道前序遍历的顺序是根,然后是左子树,最后是右子树。因此在开始前我们先判断以下root是否为NULL,如果不为NULL我们就打印根节点的数据。 

void PrevOrder(BTNode* root)
{if (root == NULL){printf("NULL ");return;}printf("%c ", root->data);}

         那如何遍历到左子树、右子树呢?其实很简单,我们之前介绍的时候说:二叉树的遍历复合递归结构,这里我们就可以使用递归来完成遍历,代码如下:

void PrevOrder(BTNode* root)
{if (root == NULL){printf("NULL ");return;}printf("%c ", root->data);PrevOrder(root->left);PrevOrder(root->right);
}

         先遍历左子树,再遍历右子树,那调用的顺序就先调用自己传左孩子指针过去,以左孩子节点为根,再按照这个程序进行执行,再次传左孩子指针过去……,知道左孩子为NULL,返回上一层,开始遍历右子树。

 我们可以简单粗暴的测试以下,测试代码如下:

#include<stdio.h>
#include<stdlib.h>typedef char Datatype;
typedef struct BinaryTreeNode
{Datatype data;struct BinaryTreeNode* left;struct BinaryTreeNode* right;
}BTNode;void PrevOrder(BTNode* root)
{if (root == NULL){printf("NULL ");return;}printf("%c ", root->data);PrevOrder(root->left);PrevOrder(root->right);
}
int main()
{BTNode* A = (BTNode*)malloc(sizeof(BTNode));A->data = 'A';A->left = NULL;A->right = NULL;BTNode* B = (BTNode*)malloc(sizeof(BTNode));B->data = 'B';B->left = NULL;B->right = NULL;BTNode* C = (BTNode*)malloc(sizeof(BTNode));C->data = 'C';C->left = NULL;C->right = NULL;BTNode* D = (BTNode*)malloc(sizeof(BTNode));D->data = 'D';D->left = NULL;D->right = NULL;BTNode* E = (BTNode*)malloc(sizeof(BTNode));E->data = 'E';E->left = NULL;E->right = NULL;A->left = B;A->right = C;B->left = D;B->right = E;PrevOrder(A);printf("\n");return 0;
}

执行结果:

 和上述分析的结果一致。

那剩下的中序遍历和后序遍历也很简单,只需要改变一下递归调用函数的次序即可:

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

 我们也可以测试以下看看执行结果,测试代码如下:

typedef struct BinaryTreeNode
{Datatype data;struct BinaryTreeNode* left;struct BinaryTreeNode* right;
}BTNode;void PrevOrder(BTNode* root)
{if (root == NULL){printf("NULL ");return;}printf("%c ", root->data);PrevOrder(root->left);PrevOrder(root->right);
}
void InOrder(BTNode* root)
{if (root == NULL){printf("NULL ");return;}InOrder(root->left);printf("%c ", root->data);InOrder(root->right);
}
void PostOrder(BTNode* root)
{if (root == NULL){printf("NULL ");return;}PostOrder(root->left);PostOrder(root->right);printf("%c ", root->data);}
int main()
{BTNode* A = (BTNode*)malloc(sizeof(BTNode));A->data = 'A';A->left = NULL;A->right = NULL;BTNode* B = (BTNode*)malloc(sizeof(BTNode));B->data = 'B';B->left = NULL;B->right = NULL;BTNode* C = (BTNode*)malloc(sizeof(BTNode));C->data = 'C';C->left = NULL;C->right = NULL;BTNode* D = (BTNode*)malloc(sizeof(BTNode));D->data = 'D';D->left = NULL;D->right = NULL;BTNode* E = (BTNode*)malloc(sizeof(BTNode));E->data = 'E';E->left = NULL;E->right = NULL;A->left = B;A->right = C;B->left = D;B->right = E;printf("前序遍历:");PrevOrder(A);printf("\n");printf("中序遍历:");InOrder(A);printf("\n");printf("后序遍历:");PostOrder(A);printf("\n");return 0;
}

 执行结果如下:

 可以和上边的分析对比一下,没有问题,


总结

        二叉树遍历是学习和理解二叉树的重要部分。通过遍历,我们可以按照特定的顺序访问二叉树的节点,从而深入了解它们的结构和关系。在这篇博客中,我们介绍了三种常见的二叉树遍历方式:前序遍历、中序遍历和后序遍历,并对它们的原理、特点和应用进行了详细讨论。本期内容为预热阶段,先让大家熟悉一下二叉树,以便于后续二叉树的学习,好的本期内容到此结束,感谢阅读!

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

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

相关文章

java对大文件分片上传

这里记录一下&#xff0c;Java对大文件的切分&#xff0c;和后端接口分片上传的实现逻辑 正常&#xff0c;前后端分离的项目其实是前端去切分文件&#xff0c;后端接口接收到切分后的分片文件去合并&#xff0c;这里都用java来记录一下。特别说明&#xff1a;我这里用的是zip包…

ReactDOM模块react-dom/client没有默认导出报错解决办法

import ReactDOM 模块“"E:/Dpandata/Shbank/rt-pro/node_modules/.pnpm/registry.npmmirror.comtypesreact-dom18.2.7/node_modules/types/react-dom/client"”没有默认导出。 解决办法 只需要在tsconfig.json里面添加配置 "esModuleInterop": true 即…

【C++】queue容器

1.queue容器基本概念 2.queue常用接口 #include <iostream> using namespace std;//队列queue #include<queue>//创建Person类 class Person { public:Person(string name, int age){this->m_Name name;this->m_Age age;}string m_Name; //姓名int m_Age; …

优维低代码实践:自定义模板

优维低代码技术专栏&#xff0c;是一个全新的、技术为主的专栏&#xff0c;由优维技术委员会成员执笔&#xff0c;基于优维7年低代码技术研发及运维成果&#xff0c;主要介绍低代码相关的技术原理及架构逻辑&#xff0c;目的是给广大运维人提供一个技术交流与学习的平台。 优维…

禾赛科技Q2营收交付双新高,国产激光雷达从量变到质变

随着2022年激光雷达元年、2023年城市智能辅助驾驶&#xff08;NOA&#xff09;元年相继到来&#xff0c;激光雷达产业迎来爆发期。 今年以来&#xff0c;自动驾驶公司、汽车制造商以及移动出行公司等各路人马积极推动城市级别的智能辅助驾驶全面落地&#xff0c;北京、上海、深…

通过css设置filter 属性,使整个页面呈现灰度效果,让整个网页变灰

通过css设置filter 属性设置页面整体置灰 效果图: 通过设置 filter 属性为 grayscale(100%)&#xff0c;页面中的所有元素都会被应用灰色滤镜效果&#xff0c;使整个页面呈现灰度效果。 <style type"text/css"> html { filter: grayscale(100%); -webkit-f…

TB/TM-商品详情原数据(APP)

一、接口参数说明&#xff1a; item_get_app-获得TB/TMapp商品详情原数据&#xff0c;点击更多API调试&#xff0c;请移步注册API账号点击获取测试key和secret 公共参数 请求地址: https://api-gw.onebound.cn/taobao/item_get_app 名称类型必须描述keyString是调用key&…

考研 408 | 【计算机网络】 应用层

导图 网络应用模型 客户/服务器&#xff08;c/s&#xff09;模型 P2P模型 DNS 域名 域名服务器 域名解析过程 文件传输协议FTP FTP服务器和用户端 FTP工作原理 电子邮件 电子邮件的信息格式 组成结构 邮件服务器的功能&#xff1a; 1.发送&接收邮件 2.给发件人报告邮…

《游戏编程模式》学习笔记(四) 观察者模式 Observer Pattern

定义 观察者模式定义了对象间的一种一对多的依赖关系&#xff0c;当一个对象的状态发生改变时&#xff0c;所有依赖于它的对象都得到通知并被自动更新。 这是定义&#xff0c;看不懂就看不懂吧&#xff0c;我接下来举个例子慢慢说 为什么我们需要观察者模式 我们看一个很简…

如何在iPhone手机上修改手机定位和模拟导航?

如何在iPhone手机上修改手机定位和模拟导航&#xff1f; English Location Simulator&#xff08;定位模拟工具&#xff09; 是一款功能强大的 macOS 应用&#xff0c;专为 iPhone 用户设计&#xff0c;旨在修改手机定位并提供逼真的模拟导航体验。无论是为了保护隐私、测试位…

Python中的字符串与字符编码

Hello&#xff0c;这里是Token_w的博客&#xff0c;欢迎您的到来 今天文章讲解的是Python中的字符串与字符编码&#xff0c;其中有基础的理论知识讲解&#xff0c;也有实战中的应用讲解&#xff0c;希望对你有所帮助 整理不易&#xff0c;如对你有所帮助&#xff0c;希望能得到…

PDM/PLM系统建设

仅供学习使用&#xff0c;会随时更新 工程机械跨生命周期数据管理系统 来源&#xff1a;清华大学 浅论企业PDM/PLM系统建设成功经验 来源&#xff1a;e-works 作者&#xff1a;陈凡 https://articles.e-works.net.cn/pdm/article149572.htm 随着“中国制造2025”强基工程战略的…

张俊林:由ChatGPT反思大语言模型(LLM)的技术精要

转自&#xff1a;https://mp.weixin.qq.com/s/eMrv15yOO0oYQ-o-wiuSyw 导读&#xff1a;ChatGPT出现后惊喜或惊醒了很多人。惊喜是因为没想到大型语言模型&#xff08;LLM,Large Language Model&#xff09;效果能好成这样&#xff1b;惊醒是顿悟到我们对LLM的认知及发展理念&a…

Elisp之获取PC电池状态(二十八)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 人生格言&#xff1a; 人生…

ArcGIS Pro 基础安装与配置介绍

ArcGIS Pro ArcGIS Pro作为ESRI面向新时代的GIS产品&#xff0c;它在原有的ArcGIS平台上继承了传统桌面软件&#xff08;ArcMap&#xff09;的强大的数据管理、制图、空间分析等能力&#xff0c;还具有其独有的特色功能&#xff0c;例如二三维融合、大数据、矢量切片制作及发布…

Unity 鼠标控制 UI 放大、缩小、拖拽

文章目录 1. 代码2. 测试场景 1. 代码 using UnityEngine; using UnityEngine.UI; using UnityEngine.EventSystems;public class UIDragZoom : MonoBehaviour, IDragHandler, IScrollHandler {private Vector2 originalSize;private Vector2 originalPosition;private RectTr…

css3 瀑布流布局遇见截断下一列展示后半截现象

css3 瀑布流布局遇见截断下一列展示后半截现象 注&#xff1a;css3实现瀑布流布局简直不要太香&#xff5e;&#xff5e;&#xff5e;&#xff5e;&#xff5e; 场景-在uniapp项目中 当瀑布流布局column-grap:10px 相邻两列之间的间隙为10px&#xff0c;column-count:2,2列展…

在阿里云服务器上安装Microsoft SharePoint 2016流程

本教程阿里云百科分享如何在阿里云ECS上搭建Microsoft SharePoint 2016。Microsoft SharePoint是Microsoft SharePoint Portal Server的简称。SharePoint Portal Server是一个门户站点&#xff0c;使得企业能够开发出智能的门户站点。 目录 背景信息 步骤一&#xff1a;添加…

无涯教程-Perl - setgrent函数

描述 此功能将枚举设置(或重置)到组条目集的开头。该函数应在第一次调用getgrent之前调用。 语法 以下是此函数的简单语法- setgrent返回值 此函数不返回任何值。 例 以下是显示其基本用法的示例代码- #!/usr/bin/perl -wwhile( ($name,$passwd,$gid,$members)getgrent…

ide internal errors【bug】

ide internal errors【bug】 前言版权ide internal errors错误产生相关资源解决1解决2 设置虚拟内存最后 前言 2023-8-15 12:36:59 以下内容源自《【bug】》 仅供学习交流使用 版权 禁止其他平台发布时删除以下此话 本文首次发布于CSDN平台 作者是CSDN日星月云 博客主页是h…