二叉树概念结构,以及画图加代码分析二叉树遍历,创建,销毁,层序遍历,判断是否完全二叉树等等

news2024/11/26 22:37:00

二叉树

  • 树的概念及结构
    • 树的概念
    • 树的相关概念
    • 树的表示
  • 二叉树的概念及结构
    • 概念
    • 特殊的二叉树
    • 二叉树性质
    • 二叉树的存储结构
  • 二叉树的实现
    • 二叉树顺序结构的实现
    • 二叉树链式结构的实现
    • 二叉树的遍历
      • 前序遍历
      • 中序遍历
      • 后序遍历
    • 二叉树结点数量
    • 叶子结点数量
    • 求树高
    • 求k层结点数量
  • 二叉树创建
  • 二叉树销毁
  • 层序遍历
  • 判断是否是完全二叉树

树的概念及结构

树的概念

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

有一个特殊的结点,称为根结点,根节点没有前驱结点

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

树的相关概念

在这里插入图片描述

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

树的表示

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

typedef int TDatatype;
typedef struct TreeNode
{
	struct TreeNode* firstchild;//第一个孩子结点
	struct TreeNode* pNextBrother;//指向下一个兄弟结点
	TDatatype data;
};

在这里插入图片描述
这样我们就把各结点关系通过孩子兄弟表示法表示了出来。如何把结点孩子打印出来呢?我们简单看一看方法,等以后在深究

void TreePrint(TreeNode* root)
{
	if (root == NULL)
		return;

	TreeNode* cur = root->firstchild;
	while (cur)
	{
		pirntf("%d ", root->data);
		//递归
		TreePrint(cur);

		cur = cur->pNextBrother;
	}
}

双亲表示法方便找双亲在哪里
在这里插入图片描述

二叉树的概念及结构

概念

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

  1. 或者为空
  2. 由一个根节点加上两棵别称为左子树和右子树的二叉树组成
    在这里插入图片描述

从上图可以看出:

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

二叉树有五种形态
在这里插入图片描述

特殊的二叉树

  1. 满二叉树:一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是
    说,如果一个二叉树的层数为K,且结点总数是 ,则它就是满二叉树。
  2. 完全二叉树:完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为K
    的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对
    应时称之为完全二叉树。 要注意的是满二叉树是一种特殊的完全二叉树。
    在这里插入图片描述

二叉树性质

  1. 若规定根节点的层数为1,则一棵非空二叉树的第i层上最多有 2^(i-1)个结点.
  2. 若规定根节点的层数为1,则深度为h的二叉树的最大结点数是2^h-1个结点
  3. 对任何一棵二叉树, 如果度为0其叶结点个数为n0 , 度为2的分支结点个数为n2 ,则有 n0=n2 +1
  4. 若规定根节点的层数为1,具有n个结点的满二叉树的深度,h=log2(n+1) . (ps: 是log以2
    为底,n+1为对数)
  5. 对于具有n个结点的完全二叉树,如果按照从上至下从左至右的数组顺序对所有节点从0开始编号,则对
    于序号为i的结点有:
  1. 若i>0,i位置节点的双亲序号:(i-1)/2;i=0,i为根节点编号,无双亲节点
  2. 若2i+1<n,左孩子序号:2i+1,2i+1>=n否则无左孩子
  3. 若2i+2<n,右孩子序号:2i+2,2i+2>=n否则无右孩子

二叉树的存储结构

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

  1. 顺序存储
    顺序结构存储就是使用数组来存储,一般使用数组只适合表示完全二叉树(和满二叉树),因为不是完全二叉树会有空间的浪费。而现实中使用中只有才会使用数组来存储,关于堆的实现,在下面有介绍。,有兴趣可以看一看。二叉树顺序存储在物理上是一个数组,在逻辑上是一颗二叉树。
    在这里插入图片描述
  2. 链式存储
    二叉树的链式存储结构是指,用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。 通常的方法是
    链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别用来给出该结点左孩子和右孩子所
    在的链结点的存储地址 。链式结构又分为二叉链和三叉链,当前我们学习中一般都是二叉链。
    在这里插入图片描述

二叉树的实现

二叉树顺序结构的实现

顺序结构存储一般堆会使用,下面详解讲解了堆。
堆的实现,画图和代码分析建堆,堆排序,时间复杂度以及TOP-K问题。

二叉树链式结构的实现

在学习二叉树的基本操作前,需先要创建一棵二叉树,然后才能学习其相关的基本操作。由于现在大家对二
叉树结构掌握还不够深入,为了降低大家学习成本,此处手动快速创建一棵简单的二叉树,快速进入二叉树
操作学习,等二叉树结构了解的差不多时,我们反过头再来研究二叉树真正的创建方式。

typedef int BTDataType;
typedef struct BTNode
{
	int data;
	struct BTNode* left;
	struct BTNode* right;
}BTNode;

BTNode* CreateBTree()
{
	BTNode* n1 = (BTNode*)malloc(sizeof(BTNode));
	assert(n1);
	BTNode* n2 = (BTNode*)malloc(sizeof(BTNode));
	assert(n2);
	BTNode* n3 = (BTNode*)malloc(sizeof(BTNode));
	assert(n3);
	BTNode* n4 = (BTNode*)malloc(sizeof(BTNode));
	assert(n4);
	BTNode* n5 = (BTNode*)malloc(sizeof(BTNode));
	assert(n5);
	BTNode* n6 = (BTNode*)malloc(sizeof(BTNode));
	assert(n6);
	BTNode* n7 = (BTNode*)malloc(sizeof(BTNode));
	assert(n7);

	n1->data = 1;
	n2->data = 2;
	n3->data = 3;
	n4->data = 4;
	n5->data = 5;
	n6->data = 6;
	n7->data = 7;

	n1->left = n2;
	n1->right = n4;
	n2->left = n3;
	n2->right = NULL;
	n3->left = NULL;
	n3->right = n7;
	n4->left = n5;
	n4->right = n6;
	n5->left = NULL;
	n5->right = NULL;
	n6->left = NULL;
	n6->right = NULL;
	n7->left = NULL;
	n7->right = NULL;

	return n1;
}

二叉树的遍历

  1. 前序遍历(Preorder Traversal 亦称先序遍历)——访问根结点的操作发生在遍历其左右子树之前。
  2. 中序遍历(Inorder Traversal)——访问根结点的操作发生在遍历其左右子树之中(间)。
  3. 后序遍历(Postorder Traversal)——访问根结点的操作发生在遍历其左右子树之后。
    在这里插入图片描述
    二叉树遍历代码其实很简单,但是里面递归逻辑就不是这样了,因此需要真正遍历的顺序才行。要把空也带上,才能更好理解代码。

前序遍历

void PostOrder(BTNode* root)
{
	if (root == NULL)
		return;

	PostOrder(root->left);
	PostOrder(root->right);
	printf("%d ", root->data);

}

我们来画一画递归展开图
在这里插入图片描述

记住一个函数栈帧调用结束会返回到上一层。

中序遍历

void InOrder(BTNode* root)
{
	if (root == NULL)
		return;

	InOrder(root->left);
	printf("%d ", root->data);
	InOrder(root->right);
}

在这里插入图片描述

后序遍历

void PostOrder(BTNode* root)
{
	if (root == NULL)
		return;

	PostOrder(root->left);
	PostOrder(root->right);
	printf("%d ", root->data);

}

这里递归展开图就不画了,有兴趣的可以自己画一下加深理解

二叉树结点数量

我们写二叉树代码的时候,一定要有分治的思想或者资本家的思想,分治思想是,处理好根这棵树,在处理左子树和右子树,而左子树和右子树又可以分成根左子树右子树,层层递归再返回。而资本家这个思想,假设董事长要经理统计员工多少人,经理让总监统计,总监让在下一级人统计。总之就是都是干一样的事,然后结果层层返回来。一定要有这个思想!!!

这个代码就是先统计左子树和右子树结点数量,然后再加上根自己。就是总结点数量。

int TreeSize(BTNode* root)
{
	if (root == NULL)
		return 0;

	return TreeSize(root->left) + TreeSize(root->right)+1;
}

这里也可以用递归展开图看一看,但是呢,递归展开图是写出代码之后才能画的,再没有代码的时候,我们可以根据二叉树图把我们思想走一步。
在这里插入图片描述

叶子结点数量

int TreeLeafSize(BTNode* root)
{
	if (root == NULL)
		return 0;

	if (root->left == NULL && root->right == NULL)
		return 1;

	return TreeLeafSize(root->left) + TreeLeafSize(root->right);
}

求树高

思想:先求左子树高度,再求右子树高度,谁高,谁高度再加1就是树高

int TreeHigh(BTNode* root)
{
	if (root == NULL)
		return 0;

	int lefthigh = TreeHigh(root->left);
	int righthigh = TreeHigh(root->right);

	return lefthigh > righthigh ? lefthigh + 1 : righthigh + 1;
}

求k层结点数量

思想:分治的思想转化为子问题,对于根来说是K层,对于左右子数来说是K-1层,然后层层递归。符合条件就返回。

int TreeKLevel(BTNode* root,int k)
{
	if (root == NULL)
		return 0;

	if (k == 1)
		return 1;

	return TreeKLevel(root->left,k-1) + TreeKLevel(root->right,k-1);

}

为了方便就画了左边的递归展开图
在这里插入图片描述

看了这一些题,应该对二叉树有更多了解了,下面我们学一学二叉树创建

二叉树创建

给这样**abc##de#g##f###**一个先序遍历数组(#代表为NULL),动态创建一颗二叉树
在这里插入图片描述
其实思想很简单,遇到结点就申请空间,然后把该结点左孩子和右孩子链接起来,再返回该结点。

BTNode* BinaryTreeCreate(char* a,int* pi)
{
	if (a[*pi] == '#')
	{
		(*pi)++;
		return NULL;
	}

	BTNode* root = (BTNode*)malloc(sizeof(BTNode));
	if (root == NULL)
	{
		perror("mallo fail");
		return NULL;
	}
	root->data = a[*pi];
	(*pi)++;

	root->left = BinaryTreeCreate(a, pi);
	root->right = BinaryTreeCreate(a, pi);

	return root;

}

有兴趣画个递归展开图加深理解。

二叉树销毁

思想:不能先销毁根结点,不然就找不到左子树和右子树了,所以先释放左子树和右子树再释放根结点;

void BinaryTreeDestroy(BTNode* root)
{
	if (root == NULL)
		return;

	BinaryTreeDestroy(root->left);
	BinaryTreeDestroy(root->right);
	free(root);

}

这里用的是一级指针,root是形参,改变不了外面实参,如果外面的人调用这个参数,在外面把root置为NULL;如果想里面就把root置为空,就得传一级指针得地址,那么就得用二级指针来接受,里面可以在加个代码,root==NULL;具体原因看单链表的实现这一章,详细解释了原因。

层序遍历

思想:申请一个队列,先让根入队列,队列不为空,就出队列,然后把该结点的左孩子和右孩子入队列。
在这里插入图片描述
有一件事情要注意,我们入队列的是结点的指针。

void BinartLevelOrder(BTNode* root)
{
	QE q;
	QueueInit(&q);

	//根入队列
	QueuePush(&q, root);

	while (!QueueEmpty(&q))
	{
		//出队列
		BTNode* Front = QueueFront(&q);
		QueuePop(&q);

		printf("%d ", Front->data);


		//左孩子不为空,入队列
		if (Front->left)
			QueuePush(&q, Front->left);
		//右孩子不为空,入队列
		if (Front->right)
			QueuePush(&q, Front->right);
	}
	printf("\n");

	QueueDestroy(&q);
}

在这里插入图片描述

判断是否是完全二叉树

什么是完全二叉树前面概念我们说过,下面有一些是完全二叉树,有些则不是
在这里插入图片描述

有人想用结点数判断,因为完全二叉树,h-1层是全满的,h层不满,但是上面第二张图每层结点都是满的,显示不能把结点数作为判断依据。
还有人这样想的,完全二叉树有且仅有一个度为1的结点,该结点只有左孩子没有右孩子,第四张图显然满足这个条件,但是它也不是颗完全二叉树。
下面介绍一种方法,还是用队列来帮助完成。
思想:一层一层走,遇到空以后,后续层序不能有非空,有非空就不是完全二叉树。

在这里插入图片描述
在这里插入图片描述
这里注意一点,队列结点和二叉树结点一定不能弄混了,我们入队列的是二叉树结点指针。

// 判断二叉树是否是完全二叉树
bool BinaryTreeComplete(BTNode* root)
{
	QE q;
	QueueInit(&q);

	QueuePush(&q, root);

	while (!QueueEmpty(&q))
	{
		BTNode* Front = QueueFront(&q);
		QueuePop(&q);

		//遇见空跳出循环
		if (Front == NULL)
			break;

		//左右子树入队列,空也入
		QueuePush(&q, Front->left);
		QueuePush(&q, Front->right);
	}
	while (!QueueEmpty(&q))
	{
		BTNode* Front = QueueFront(&q);
		QueuePop(&q);

		//遇到空之后,再次遇到不为空,就不是完全二叉树
		if (Front)
		{
			QueueDestroy(&q);
			return false;
		}
	}

	//一直为空,就是完全二叉树
	QueueDestroy(&q);
	return true;
}

至此关于二叉树概念,结构,实现,遍历等等写完了,后续还会更新红黑树和平衡二叉树,喜欢的可以点赞,收藏,加关注,以后比较着学。

关注小王不迷路,关键时候顶得住!
在这里插入图片描述

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

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

相关文章

SpringMVC第一阶段:SpringMVC的介绍和使用

1、SpringMVC的概述 Spring MVC框架是一个开源的Java平台&#xff0c;为开发强大的基于JavaWeb应用程序提供全面的基础架构支持&#xff0c;并且使用起来非常简单容易。 Spring web MVC框架提供了MVC(模型 - 视图 - 控制器)架构&#xff0c;用于开发灵活和松散耦合的Web应用程…

【原创】创建Vue3 ts项目

文章目录 创建Vue3 ts项目工作空间创建vue 项目进入项目目录启动项目 & 停止项目访问成功 创建Vue3 ts项目 工作空间 进入工作空间目录&#xff1a;D:\workspace\vue 创建vue 项目 vue create vt #创建vt项目vue test 选择手动进行配置&#xff1a; 选中下面几个核心…

利用 Mybatis-Plus 的动态数据源实现多数据源配置

目录 一、导入依赖 二、Application.yaml配置文件 三、切换数据源 四、其他方法 4.1 配置多个数据源 4.2 定义Datasource和EntityManager 4.3 在需要使用数据源的地方注入不同的EntityManager 官网&#xff1a;https://baomidou.com/pages/a61e1b/#dynamic-datasource …

vue2实现路由跳转后隐藏底部固定导航栏Tabber的一种方式

在使用vue路由的时候&#xff0c;跳转到某些页面上是不需要展示底部固定的导航栏的&#xff0c;所以在某些特定的页面跳转时候&#xff0c;就需要隐藏底部的导航栏 这里用了一种方式去解决这个问题 1、前提 这里我把底部导航栏做了一个组件的封装&#xff0c;然后在App.vue里…

玩转 LLMs 之基础设施「利刃出鞘」

LLMs 时代已经到来&#xff0c;这个由 ChatGPT 开始的全球化技术浪潮&#xff0c;所经之处&#xff0c;风起云涌。LLMs 之于当下&#xff0c;更像是 iOS 、Android 之于移动互联网时代。开发者沸腾&#xff0c;投资机构争抢&#xff0c;与赛道相关的基础设施建设自然也成为整个…

IOC容器中的核心容器ApplicationContext

文章目录 1 环境准备2 容器2.1 容器的创建方式2.2 Bean的三种获取方式2.3 容器类层次结构2.4 BeanFactory的使用 前面已经完成bean与依赖注入的相关知识学习&#xff0c;接下来我们主要学习的是IOC容器中的核心容器。 这里所说的核心容器&#xff0c;大家可以把它简单的理解为…

聊聊我在三家IT公司的工作经历,C++程序员都在做什么?

我想分享一些关于C程序员工作的经验。我先自我介绍一下&#xff1a;我是一名毕业于普通985大学的程序员&#xff0c;已经工作了5年&#xff0c;但是我曾在三家互联网公司工作过&#xff0c;这些公司都还算是比较有名的互联网公司。 好了&#xff0c;不多说了&#xff0c;进入正…

真题详解(自顶向下)-软件设计(七十六)

真题详解&#xff08;传引用&#xff09;-软件设计&#xff08;七十五)https://blog.csdn.net/ke1ying/article/details/130695214 计算机软件著作权&#xff0c;正确的是_____。 非法进行拷贝、发布或更改软件的人被称为软件盗版者 循环冗余CRC校验&#xff0c;设数据位为k位…

KVM网络管理-创建桥接网络

KVM网络管理-创建桥接网络 分类 网络&#xff1a; nat isolated 接口&#xff1a; bridge 虚拟交换机&#xff1a; linux-bridge(linux) ovs(open-Vswitch) NAT网络拓扑图 仅主机模式网络拓扑图 桥接模式网络拓扑图 在Linux主机上开启vm1&#xff0c;从交换机上把…

第五章 面向对象-6.内部类

内部类 内部类分为: 成员内部类、静态嵌套类、匿名内部类&#xff08;直接new 抽象类&#xff0c;直接new 接口&#xff09;$1 $2。 如果是函数式接口&#xff0c;可以使用lambda表达&#xff0c;这样可以避免new 接口产生内部匿名类 内部类仍然是一个独立的类&#xff0c;在…

接口测试要测试什么?怎么测?我来告诉你

目录 本文主要分为两个部分&#xff1a; 第一部分&#xff1a; 第二部分&#xff1a; 备注&#xff1a; 本文主要分为两个部分&#xff1a; 第一部分&#xff1a;主要从问题出发&#xff0c;引入接口测试的相关内容并与前端测试进行简单对比&#xff0c;总结两者之前的区别与…

SGI STL(四)——_S_chunk_alloc函数解析

情况一&#xff1a;内存池剩余空间的大小不满足需要分配的内存空间 假设调用为 _S_chunk_alloc(8, 20), 即希望分配8个20B的内存小块构成的chunk块 代码如下 template <bool __threads, int __inst> char* __default_alloc_template<__threads, __inst>::_S_chu…

Ansible 自动化运维工具(二)——Ansible 的脚本(playbook 剧本)

文章目录 一、playbooks 概述以及实例操作1、playbooks 的组成2、操作示例一&#xff1a;3、操作实例二&#xff1a;定义、引用变量4、操作示例三&#xff1a;指定远程主机sudo切换用户5、操作示例四&#xff1a;when条件判断6、操作示例:五&#xff1a;迭代 二、playbook的模块…

怎么把视频压缩变小一点,必须收藏的方法

怎么把视频压缩变小一点&#xff1f;我们发现现在视频在工作中的占比也很大的。当我们拍摄了很多视频后&#xff0c;当然是需要进行后续的编辑和传输啦。但是我们发现视频的进行传输的时候最大的问题就是&#xff0c;视频太大导致无法发送或是发送的时间很慢。现今许多平台都对…

5springboot

SpringBoot 1.SpringBoot是什么 我们知道&#xff0c;从 2002 年开始&#xff0c;Spring 一直在飞速的发展&#xff0c;如今已经成为了在Java EE&#xff08;Java Enterprise Edition&#xff09;开发中真正意义上的标准&#xff0c;但是随着技术的发展&#xff0c;Java EE使…

C++实现图—邻接矩阵,邻接表,深度遍历,广度遍历

目录 1.图的基本概念 2.图的存储结构 2.1邻接矩阵 2.2 邻接表 3.图的遍历 3.1广度优先遍历 3.2图的深度遍历 总结&#xff1a; 1.图的基本概念 图是由顶点集合以及顶点之间的关系组成的一种数据结构&#xff1a;G (V,E),其中顶点集合V{x|x属于某个对象集}是有穷非空集合…

“西湖论剑”四大观察:十年筑梦向未来,数字安全开新局

既有人工智能与安全何去何从的激烈讨论&#xff0c;又有数据安全与数据治理的深度解读&#xff0c;还有数字中国建设背景下安全产业升级的蓝图规划&#xff0c;更有数字安全人才培养的期许与行动……这就是2023 西湖论剑数字安全大会所呈现出的一片热闹景象。 自2012年&#x…

2023全网最全真人总结的常见接口测试面试题,一定不能错过

1、按你的理解&#xff0c;软件接口是什么&#xff1f; 答&#xff1a; 就是指程序中具体负责在不同模块之间传输或接受数据的并做处理的类或者函数。 2、HTTP和HTTPS协议区别&#xff1f; 答&#xff1a; https协议需要到CA&#xff08;Certificate Authority&#xff0c;证书…

理论力学专题:理论力学(物理类)框架

理论力学专题&#xff1a;理论力学&#xff08;物理类&#xff09;框架 拉格朗日方程 虚位移&#xff1a;任意方向的微变化 约束分类&#xff1a; 稳定/不稳定&#xff08;显含时间与否&#xff09;可解/不可解&#xff1a;完整/微分约束&#xff1a; 几何约束&#xff08;完…

F5—创建DDR3内存条DIMM读写测试程序2023-05-16

本文区别于DDR颗粒的配置&#xff0c;记录几个与颗粒配置不同的地方&#xff0c;具体DDR的原理请查看DDR3的应用总结&#xff08;一&#xff09;DDR3的应用总结&#xff08;二&#xff09; 1.确认板卡FPGA型号为xc7k325tffg900 -2&#xff0c;据此创建FPGA工程。 2.添加MIG I…