C C++最新《数据结构C语言版》——二叉树详解(图文并茂)_c语言二叉树,你掌握了多少

img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取

4和9以及3和9都是堂兄弟关系;
结点5的祖先为4、2、1结点;
结点2的子孙为3、4、5、6、7;
从根开始定义,第一层,第二层…则图中树的高度为4;
结点2的高度为2。

树的存储结构

双亲表示法

有的结点可能没有孩子,但是除了根结点外,它们一定都有双亲,而且有且仅有一个双亲结点。

所谓双亲表示法就是在每个结点中,附设一个指示器指示其双亲结点在数组中的位置。

即一维数组的每个元素分别包含data 和 parent 关系。

相当于一个静态链表。同时,这里规定根节点的parent 指向-1。

例如这样一棵树:

用双亲表示法表示即为:

双亲表示法具有以优点:

  • 结构简单;
  • 查找双亲或祖先结点方便。

其代码结构如下:

/* 树的双亲表法结点结构定义*/
#define MAX_TREE_SIZE 100
typedef int  ElemeType;

typedef struct PTNode{ // 结点结构
    ElemeType data; //结点数据
    int parent;    // 双亲位置
}PTNode;

typedef struct { // 树结构
    PTNode nodes[MAX_TREE_SIZE];   // 结点数组
    int r; // 根的位置
    int n; // 结点数
}PTree;
孩子表示法

将树中的每个结点的孩⼦结点排列成⼀个线性表,⽤链表存储起来。对于含有 n 个结点的树来说,就会有 n 个单链表,将 n 个单链表的头指针存储在⼀个线性表中,这样的表⽰⽅法就是孩子表示法。如果结点没有孩⼦(例如叶⼦结点),那么它的单链表为空表。

例如这样一棵树用双亲表示法表示为:

其代码结构如下:

/* 树的孩子表示法结构定义*/
#define MAX_TREE_SIZE 100
typedef int  ElemeType;

typedef struct CTNode{  // 孩子结点
    int child; // 孩子结点的下标
    struct CTNode * next; // 指向下一结点的指针
}*ChildPtr;

typedef struct {  // 表头结构
    ElemeType data; // 存放在数中的结点数据
    ChildPtr firstchild; // 指向第一个孩子的指针
}CTBox;

typedef struct {  // 树结构
    CTBox nodes[MAX_TREE_SIZE]; // 结点数组
    int r;  // 根的位置
    int n;  // 结点树
}CTree;
孩子兄弟表示法

任意一棵树,它的结点的第一个孩子如果存在就是唯一的,它的右兄弟如果存在也是唯一的。

因此我们设置两个指针,分别指向该结点的第一个孩子和此节点的右兄弟

孩子兄弟表示法的结点结构

data(数据域)firstchild(指针域)rightsib(指针域)
存储结点的数据信息存储该结点的第一个孩子的存储地址存储该结点的右兄弟结点的存储地址

我们也常称其为 左孩子右兄弟,这一“秘籍”在做手画图的题时很有效

例如这样一棵树:

用孩子兄弟表示法表示的结果如下:

其代码结构如下:

/* 树的孩子兄弟表示法结构定义*/
#define MAX_TREE_SIZE 100
typedef int  ElemeType;

typedef struct CSNode{
    ElemeType data;
    struct CSNode * firstchild;
    struct CSNode * rightsib;
    
}CSNode, *CSTree;

自此,树的相关性质和表示方法及其结构我们都学习了,接下来才是重点内容,二叉树!


二叉树

一图带你明确什么是二叉树!!!

没错 这个就是二叉树~~ 仔细看,每一个小树枝都仅有两个“分支”

二叉树的定义

光有图还不够严谨,二叉树的定义是这样的

二叉树(Binary Tree):是n(n≥0)个结点的有限集合,该集合或者为空集(称为空二叉树),或者由一个根结点和两颗互不相交的、分别称为根结点的左子树和右子树的二叉树组成。

定义就是这样,好像并不清楚,简而言之 满足以下两个条件的树就是二叉树:

  1. 本身是有序树;
  2. 树中包含的各个结点的度不能超过 2,即只能是 0、1 或者 2;

如图所示,a的度最多是2,所以是二叉树,而b有度为3的结点,所以不是二叉树!

二叉树的特点

二叉树肯定有不同于普通树的特点来彰显它的好处。

二叉树的特点有:

  • 每个结点最多有两颗子树,所以二叉树中不存在度大于2的结点。注意不是只有两颗子树,而是最多有。没有子树或者有一颗子树都是可以的。
  • 左子树和右子树是有顺序的,次序不能颠倒。就像人有双手、双脚。
  • 即使树中某结点只有一颗树,也要区分它是左子树还是右子树。

如图左右是不一样的存在

由于“左右有别”这一性质,所以二叉树有了五种基本形态:

  1. 空二叉树
  2. 只有一个根结点
  3. 根结点只有左子树
  4. 根结点只有右子树
  5. 根结点既有左子树又有右子树

二叉树五种基本形态如下图所示:

那么如果是有三个结点的二叉树,又有几种形态呢?

没错,也是五种,你画对了吗~~~

它们都代表不同的二叉树

二叉树的性质

二叉树有一些需要理解并且记住的性质,以便于我们更好地使用它。

二叉树性质1:在二叉树的第i层上最多有2^(i-1)个结点(i≥1)。
第一层是根结点,只有一个,所以2(1-1)=20=1。 第二层有两个,2(2-1)=21=2。 第三层有四个,2(3-1)=22=4。 第四层有八个,2(4-1)=2^3=8。

二叉树性质2:深度为k的二叉树至多有2^k-1个结点(k≥1)。
注意这里一定要看清楚,是2k后再减去1,而不是2(k-1)。以前很多同学不能完全理解,这样去记忆,就容易把性质2与性质1给弄混淆了。 深度为k意思就是有k层的二叉树,我们先来看看简单的。 如果有一层,至多1=21-1个结点。 如果有二层,至多1+2=3=22-1个结点。 如果有三层,至多1+2+4=7=23-1个结点。 如果有四层,至多1+2+4+8=15=2^4-1个结点。

二叉树性质3:对任何一棵二叉树,如果其终端结点数为n0,度为2的结点数为n2,则n0=n2+1。
终端结点数其实就是叶子结点数,而一棵二叉树,除了叶子结点外,剩下的就是度为1或2的结点数了,我们设n1为度是1的结点数。则树T结点总数n=n0+n1+n2
终端结点数其实就是叶子结点数,而一棵二叉树,除了叶子结点外,剩下的就是度为1或2的结点数了,我们设n1为度是1的结点数。则树T结点总数n=n0+n1+n2 。

二叉树性质4:具有n个结点的完全二叉树的深度为|log(2^n)+1|  (向下取整)。
由满二叉树的定义我们可以知道,深度为k的满二叉树的结点数n一定是2k-1。因为这是最多的结点个数。那么对于n=2k-1倒推得到满二叉树的深度为k=log2(n+1),比如结点数为15的满二叉树,深度为4。

二叉树性质5:如果对一棵有n个结点的完全二叉树(其深度为|log(2^n)+1|)的结点按层序编号(从第一层到第层,每层从左到右),对任一结点i(1<=i<=n),有
1.如果i=1,则结点i是二叉树的根,无双亲;如果i>1,则其双亲是结点。
2.如果2i>n,则结点i无左孩子(结点i为叶子结点);否则其左孩子是结点2i。
3.如果2i+1>n,则结点i无右孩子;否则其右孩子是结点2i+1

特殊的二叉树

二叉树再进行细分也能划分好几种二叉树,暂时只需要了解并且能够辨别,至于有什么用处,以后再讨论~~~

斜树

顾名思义,斜树一定得是斜着的树。

所有结点都只有左子树的二叉树叫左斜树

所有结点都只有右子树的二叉树叫右斜树

两者统称为斜树。

如图这就是左斜树和右斜树

满二叉树

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

根据定义,满二叉树就是这样的:

但是这样的情况:

只有B是满二叉树

相信你已经有所判断能力了~~

满二叉树有属于自己的特点

  • 叶子只能出现在最下一层
  • 非叶子结点的度一定是2
  • 在同样深度的二叉树中,满二叉树的结点个数最多,叶子数最多
完全二叉树

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

好像不是很明白。换而言之

若一棵二叉树至多只有最下面两层的结点的度数可以小于2,并且最下层的结点都集中在该层最左边的若干位置上,则此二叉树为完全二叉树。

完全二叉树就是这样的:

对比一下:

同样的,完全二叉树也有自己的特点

  • 叶子结点只用出现在最下两层
  • 最下层的叶子一定集中在左部连续位置
  • 倒数两层若有叶子结点,一定都在右部连续位置
  • 如果结点度为1,则该结点只有左孩子
  • 同样结点数的二叉树,完全二叉树的深度最小

二叉树的存储结构

顺序存储

顺序存储只适用于完全二叉树、满二叉树

由于它们的特殊性,使得用顺序存储结构也可以轻松实现。

顺序存储结构就是用一维数组存储二叉树中的结点,并且结点的存储位置,也就是数组的下标,要能体现结点之间的逻辑关系,比如双亲与孩子的关系,左右兄弟的关系等。

如图这样一个二叉树:

我们就可以将其存储在数组中,其对应关系就是这样的:

根据性质,将树中结点按照层次并从左到右依次标号(从下标0开始),若结点 i 有左右孩子,则其左孩子结点为 2i+1,右孩子结点为 2i+2,此性质可用于还原数组中存储的完全二叉树

若下标从1开始也是一样的道理,左孩子结点为2i,右孩子结点为2i+1

二叉链表

二叉树每个结点最多有两个孩子,所以为它设计一个数据域和两个指针域就很方便存储这样的结构了。我们称其为二叉链表

结点结构如图所示:

因此我们存储二叉树只需要将左右孩子的指针指向其对应的孩子即可,就像这样:

根据这个图,我们不难得到结点的存储结构代码:

//结点结构体定义
typedef struct BTNode{
	int data;
	struct BTNode *LChild,*RChild;
}BTNode,*BTree;

以这样的一颗简单的二叉树,我们了解如何手动创建一下二叉链表:

int main(){
	BTree root = (BTNode *)malloc(sizeof(BTNode));
	root->data = 1;
	BTNode *p = (BTNode *)malloc(sizeof(BTNode));
	p->data = 2;
	root->LChild = p;
	p = (BTNode *)malloc(sizeof(BTNode));
	p->data = 3;
	root->RChild = p;
	printf("根:%d 左孩子:%d 右孩子:%d\n",root->data,root->LChild->data,root->RChild->data);
	return 0;
}

得到结果显然就是这样的:

二叉树的遍历

二叉树的遍历是指 从根结点出发按照某种次序一次访问二叉树中的所有结点,使得每个结点被访问一次且仅被访问一次

二叉树的遍历方式有很多,如果我们限制了从左到右的方式,那么主要分为以下四种:

  1. 前序遍历
  2. 中序遍历
  3. 后序遍历
  4. 层次遍历

接下来我们对这四种遍历进行系统的学习~~~

前序遍历

规则是:

若二叉树为空,则空操作返回,否则先访问根结点,然后前序遍历左子树,再前序遍历右子树。

注意:这里用词和贴切~~明明在说前序遍历的规则,结果规则里又是前序遍历,没错 这就是递归!

使用递归来实现遍历操作是非常的简洁。

递归实现遍历及其简洁明了。

前序遍历的代码如下:

//前序遍历
void PreOrderTraverse(BTree T){
	if(T == NULL)
		return;
	printf("%d",T->data);
	PreOrderTraverse(T->LChild);
	PreOrderTraverse(T->RChild);
}
中序遍历

第一次经过时不访问,等遍历完左子树之后再访问,然后遍历右子树

中序遍历和前序遍历原理是一样的,差别只是访问的次序不同,体现在代码上就上顺序的差异。

中序遍历的代码如下:

//中序遍历
void InOrderTraverse(BTree T){
	if(T == NULL)
		return;
	InOrderTraverse(T->LChild);
	printf("%d",T->data);
	InOrderTraverse(T->RChild);
}
后序遍历

第一次和第二次经过时都不访问,等遍历完该节点的左右子树之后,最后访问该节点

同样的,后序遍历和前序遍历原理是一样的,差别只是访问的次序不同,体现在代码上就上顺序的差异。

后序遍历的代码如下:

//后序遍历
void PostOrderTraverse(BTree T){
	if(T == NULL)
		return;
	PostOrderTraverse(T->LChild);
	PostOrderTraverse(T->RChild);
	printf("%d",T->data);
}
层次遍历

通过对树中各层的节点从左到右依次遍历,即可实现对正棵二叉树的遍历,此种方式称为层次遍历。

推导遍历结果

除了掌握四种遍历方式的思想和代码的实现,能够根据已知的遍历结果来反推二叉树的形态也是考研、期末的常考重点题型

前序+中序遍历序列

通过遍历结果来反推二叉树的形态,最重要的是理解每种遍历之间和二叉树的联系。

例如:我们可以从前序遍历得到根,其第一个就是根结点,根结点在其中序遍历的序列中,把该位置左右分为了根结点的左右子树,然后我们就可以将整个中序序列划分为三大部分了。然后我们又对其每一个小部分进行再次的判断,又能得到左、右子树的根和对应的左右子树…一直细化三部分后就能得到最终结果了。

前序遍历序列:根结点      左子树的前序遍历序列      右子树的前序遍历序列

中序遍历序列:左子树的中序遍历序列          根结点          右子树的中序遍历序列

例题:

由前序遍历第一个结点可知,该结点的根结点就是A,于是在中序遍历序列中找到A的左右,则在A的左边就是A左子树包含的结点,在A的右边就是A右子树包含的结点

然后整个序列划分为 {B,C,D} A {E}

对于BCD这颗“小树”,在前序序列中得到D是根结点,然后在中序序列中找到D,D的左边是B,右边是C,所以对于{B,C,D}这个集合,D为根结点,B为D的左结点,C为D的右结点。

A的右子树由于只有E,所以E就是A的右孩子结点。

这样就完成了!我们可以得到如下的结果:

后序+中序遍历序列

如果是后序+中序遍历序列的话,思路也是一样的,但是在后序遍历序列中,靠右的才是根结点,这里和前序序列靠左刚好相反。

后序遍历序列:左子树的中序遍历序列          右子树的中序遍历序列           根结点

中序遍历序列:左子树的中序遍历序列          根结点          右子树的中序遍历序列

看一道例题:

img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取

集合,D为根结点,B为D的左结点,C为D的右结点。

A的右子树由于只有E,所以E就是A的右孩子结点。

这样就完成了!我们可以得到如下的结果:

后序+中序遍历序列

如果是后序+中序遍历序列的话,思路也是一样的,但是在后序遍历序列中,靠右的才是根结点,这里和前序序列靠左刚好相反。

后序遍历序列:左子树的中序遍历序列          右子树的中序遍历序列           根结点

中序遍历序列:左子树的中序遍历序列          根结点          右子树的中序遍历序列

看一道例题:

[外链图片转存中…(img-6WUYYad9-1715722340814)]
[外链图片转存中…(img-MFqahZyg-1715722340815)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取

  • 19
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值