【数据结构】基础:二叉树

news2024/11/29 12:52:55

【数据结构】基础:二叉树基础

摘要:本文将会介绍二叉树的基础内容,首先引入树的概念,了解树的基本概念与性质,再对二叉树的概念和性质进行分析,最后对其方法进行实现,最重要的是理解对于二叉树方法实现的分治思想。


文章目录

  • 【数据结构】基础:二叉树基础
    • 一、树的概述
      • 1.1 树的概念
      • 1.2 树的相关概念
      • 1.3 树的表示方法
    • 二、二叉树概述
      • 2.1 二叉树的概念
      • 2.2 特殊的二叉树
      • 2.3 二叉树的性质
      • 2.4 二叉树的存储结构
    • 三、二叉树方法实现
      • 3.1 概述
      • 3.2 前置准备
      • 3.3 遍历
      • 3.4 节点个数
      • 3.5 叶子结点个数
      • 3.6 树的高度
      • 3.7 第k层的节点个数
      • 3.8 方法总结

一、树的概述

1.1 树的概念

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

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

注意:

  • 子树是不相交的,树形结构中,子树之间不能有交集,否则就不是树形结构
  • 除根节点外,每个结点有且仅有一个父节点
  • 一棵N个节点的数有N-1条边

1.2 树的相关概念

节点的度:一个节点含有的子树的个数称为该节点的度;

叶节点或终端节点:度为0的节点称为叶节点;

非终端节点或分支节点:度不为0的节点;

双亲节点或父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点;

孩子节点或子节点:一个节点含有的子树的根节点称为该节点的子节点;

兄弟节点:具有相同父节点的节点互称为兄弟节点;

树的度:一棵树中,最大的节点的度称为树的度;

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

树的高度或深度:树中节点的最大层次;

堂兄弟节点:双亲在同一层的节点互为堂兄弟;

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

子孙:以某节点为根的子树中任一节点都称为该节点的子孙;

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

1.3 树的表示方法

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

typedef int DataType;
struct Node{
	struct Node* _firstChild1; // 第一个孩子结点
	struct Node* _pNextBrother; // 指向其下一个兄弟结点
	DataType _data; // 结点中的数据域
};

在这里插入图片描述

二、二叉树概述

2.1 二叉树的概念

一棵二叉树是结点的一个有限集合,该集合要么为空,要么由一个根节点加上两棵别称为左子树和右子树的二叉树组成。

注意:

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

2.2 特殊的二叉树

满二叉树:一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是说,如果一个二叉树的层数为K,且结点总数是2^k - 1 ,则它就是满二叉树。

完全二叉树:完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为K的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时称之为完全二叉树。 要注意的是满二叉树是一种特殊的完全二叉树。

在这里插入图片描述

2.3 二叉树的性质

  • 若规定根节点的层数为1,则一棵非空二叉树的第i层上最多有 2 ^(i - 1)个结点

  • 若规定根节点的层数为1,则深度为h的二叉树的最大结点数是 2^h -1

  • 对任何一棵二叉树, 如果度为0其叶结点个数为 n0, 度为2的分支结点个数为n2 ,则有 n0= n2+1

  • 若规定根节点的层数为1,具有n个结点的满二叉树的深度,h= log(n+1) (ps: 是log以2为底,n+1为对数)

  • 对于具有n个结点的完全二叉树,如果按照从上至下从左至右的数组顺序对所有节点从0开始编号,则对于序号为i的结点有

    若i>0,i位置节点的双亲序号:(i-1)/2;i=0,i为根节点编号,无双亲节点

    若2i+1<n,左孩子序号:2i+1,2i+1>=n否则无左孩子

    若2i+2<n,右孩子序号:2i+2,2i+2>=n否则无右孩子

2.4 二叉树的存储结构

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

  • 顺序存储

    顺序结构存储就是使用数组来存储,一般使用数组只适合表示完全二叉树,因为不是完全二叉树会有空间的浪费。而现实中使用中只有堆才会使用数组来存储,堆的内容可以参考博客http://t.csdn.cn/VsaOe。二叉树顺序存储在物理上是一个数组,在逻辑上是一颗二叉树。

  • 链式存储

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

typedef int BTDataType;
// 二叉链
struct BinaryTreeNode{
    struct BinTreeNode* _pLeft; // 指向当前节点左孩子
    struct BinTreeNode* _pRight; // 指向当前节点右孩子
    BTDataType _data; // 当前节点值域
}
// 三叉链
struct BinaryTreeNode{
    struct BinTreeNode* _pParent; // 指向当前节点的双亲
    struct BinTreeNode* _pLeft; // 指向当前节点左孩子
    struct BinTreeNode* _pRight; // 指向当前节点右孩子
    BTDataType _data; // 当前节点值域
};

三、二叉树方法实现

3.1 概述

二叉树的实现方式主要有两种,分别为通过顺序结构与链式结构实现。

对于顺序结构来说普通的二叉树是不适合用数组来存储的,因为可能会存在大量的空间浪费,而完全二叉树更适合使用顺序结构存储,实际上通常把堆(一种二叉树)使用顺序结构的数组来存储,需要注意的是这里的堆和操作系统虚拟进程地址空间中的堆是两回事,一个是数据结构,一个是操作系统中管理内存的一块区域分段,堆的主要实现可以参考博客:http://t.csdn.cn/VsaOe。

而本文主要介绍链式结构的实现,用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。 通常的方法是链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别用来给出该结点左孩子和右孩子所在的链结点的存储地址 。而二叉树的结构设计,是采用了分治的方法管理的,每一个节点管理着链接自身的子树,因此对于递归的思想,在二叉树实现中非常重要。

3.2 前置准备

在实现二叉树前,先不对直接实现对二叉树的创建难度是比较大的,因此在此先写死一个二叉树的创建,通过对二叉树的部分功能实现后,再对二叉树的创建进行实现。在创建之前,为了方便节点的创建,先封装成一个函数,如下是二叉树节点结构体与节点创建的代码:

typedef int BTDataType;
struct BinaryTreeNode {
	BTDataType val;
	struct BinaryTreeNode* left;
	struct BinaryTreeNode* right;
};
typedef struct BinaryTreeNode BinaryTreeNode;
BinaryTreeNode* BuyBinaryTreeNode(BTDataType val) {
	BinaryTreeNode* ptemp = (BinaryTreeNode*)malloc(sizeof(BinaryTreeNode));
	if (ptemp == NULL) {
		perror("malloc BinaryTreeNode failed");
		exit(1);
	}
	ptemp->val = val;
	ptemp->right = NULL;
	ptemp->left = NULL;

	return ptemp;
}

现在通过代码写死一棵二叉树,图示与代码如下:

BinaryTreeNode* Test_CreateBinaryTree() {
	BinaryTreeNode* node1 = BuyBinaryTreeNode(1);
	BinaryTreeNode* node2 = BuyBinaryTreeNode(2);
	BinaryTreeNode* node3 = BuyBinaryTreeNode(3);
	BinaryTreeNode* node4 = BuyBinaryTreeNode(4);
	BinaryTreeNode* node5 = BuyBinaryTreeNode(5);
	BinaryTreeNode* node6 = BuyBinaryTreeNode(6);
	BinaryTreeNode* node7 = BuyBinaryTreeNode(7);
	BinaryTreeNode* node8 = BuyBinaryTreeNode(8);

	node1->left = node2;
	node1->right = node3;
	node2->left = node4;
	node2->right = node5;
	node3->left = node6;
	node3->right = node7;
	node4->left = node8;

	return node1;
}

在这里插入图片描述

3.3 遍历

所谓二叉树遍历(Traversal)是按照某种特定的规则,依次对二叉树中的节点进行相应的操作,并且每个节点只操作一次。

前、中、后序遍历

前中后序指的是对于根访问的顺序,而对于左右子树的顺序,是左子树优先于右子树,因此对其中的总结如下:

  • 前序遍历(Preorder Traversal 亦称先序遍历):访问根结点的操作发生在遍历其左右子树之前
  • 中序遍历(Inorder Traversal):访问根结点的操作发生在遍历其左右子树之中(间)
  • 后序遍历(Postorder Traversal):访问根结点的操作发生在遍历其左右子树之后

如下图所示,我们对其前中后序排列进行举例:

在这里插入图片描述

而对于通过前中后序遍历的实现,可以利用树的结构来实现,以前序遍历举例,先访问根,再访问左右子树,而对于左右子树而言,也是同样的思路,因此我们可以将其进行分治,使用递归实现,而递归结束的标志是没有节点在继续访问,因此当下一个子树为空时,就不再进行访问,对于代码的实现如下:

void PreOrder(BinaryTreeNode* root) {
	if (root == NULL) {
		printf("NULL ");
		return;
	}
	// do something
	printf("%d ", root->val);
	PreOrder(root->left);
	PreOrder(root->right);
	return;
}

void InOrder(BinaryTreeNode* root) {
	if (root == NULL) {
		printf("NULL ");
		return;
	}
	InOrder(root->left);
	// do something
	printf("%d ", root->val);
	InOrder(root->right);
	return;
}

void PostOrder(BinaryTreeNode* root) {
	if (root == NULL) {
		printf("NULL ");
		return;
	}
	PostOrder(root->left);
	PostOrder(root->right);
	// do something
	printf("%d ", root->val);
	return;
}

层级遍历

二叉树的层序遍历需要通过队列数据结构来实现,首先将根节点放入,进入循环,当队列不为空时,出队列,获取队头元素,并将其子节点推入队列中,以此类推,直到队列中没有元素。其中的原理是,在访问结点时会将子节点推到队尾,而在其之前的节点早已建立了相应的顺序,可以实现层序遍历。在此对齐进行一个小改进,就是使其可以一层一层的访问,在函数中增添一个参数,统计每一层的节点数量,当参数不断自减,当为零时表示访问完毕并对下一层访问,代码实现如下:

void BinaryTreeLevelOrder(BinaryTreeNode* root) {
	int levelSize = 0;
	Queue* queue = (Queue*)malloc(sizeof(Queue));
	if (queue == NULL) {
		perror("Queue: malloc failed!");
		exit(1);
	}
	QueueInit(queue);
	if (root == NULL) {
		return;
	}
	QueuePush(queue, root);
	levelSize = 1;
	while (!isQueueEmpty(queue)) {
		while (levelSize--){
			BinaryTreeNode* cur = QueueFront(queue);
			QueuePop(queue);
			//do something
			printf("%d ", cur->val);
			if (cur->left != NULL) {
				QueuePush(queue, cur->left);
			}
			if (cur->right != NULL) {
				QueuePush(queue, cur->right);
			}
		}
		levelSize = QueueSize(queue);
		printf("\n");
	}
	QueueDestory(queue);
	return;
}

3.4 节点个数

对于统计二叉树的节点个数,很多人第一反应是安装遍历的思路逐个遍历,计数即可,但实际上在细节上很多人会忽略函数栈帧的问题,因此建立临时变量进行统计,可实际上当函数栈帧销毁是,局部变量退出作用域后回销毁,因此无法达到计数的目的。为此会有对此改进,进行一个全局变量的设置,不过当有多个统计节点函数同时调用时,容易出现错误。在此采用另一种思路,同样采取分治的策略,对于每个子树的根来说,统计的是左右子树的节点个数。从根开始,逐级递归统计左右子树的节点数目,当遇到空子树时返回0并结束递归,当返回时返回左右子树节点之和以及自身,代码示例如下:

int BinaryTreeSize(BinaryTreeNode* root) {
	return root == NULL ?
		0 : BinaryTreeSize(root->right) + BinaryTreeSize(root->left) + 1;
}

3.5 叶子结点个数

同样采用分治的策略,对于每棵子树都统计其左右子树的叶节点的个数,当遇到空子树时返回0,当遇到叶子节点时返回1,代码示例如下:

int BinaryTreeLeafSize(BinaryTreeNode* root) {
	if (root == NULL) {
		return 0;
	}
	if (root->right == NULL && root->left == NULL) {
		return 1;
	}
	return BinaryTreeLeafSize(root->right) + BinaryTreeLeafSize(root->left);
}

3.6 树的高度

同样采用分治策略,分别统计左右子树高度,返回最深者,当遇到空树时返回0,否则返回最大高度与这身高度之和。需要注意的是,这里要将统计好的高度保存下来,否则会出现重复运算的问题,对于递归算法来说,时间复杂度是较大的,因此要尽量避免重复计算情况。以下将会提供错误写法与正确写法:

int BinaryTreeHeight(BinaryTreeNode* root) {
	if (root == NULL) {
		return 0;
	}
	int leftTreeHeight = BinaryTreeHeight(root->left);
	int rightTreeHeight = BinaryTreeHeight(root->right);
	return leftTreeHeight > rightTreeHeight ? leftTreeHeight + 1 : rightTreeHeight + 1;
}
// 错误写法
int BinaryTreeHeight(BinaryTreeNode* root) {
	if (root == NULL) {
		return 0;
	}
	return  BinaryTreeHeight(root->left) > BinaryTreeHeight(root->right)
		? BinaryTreeHeight(root->left) + 1 : BinaryTreeHeight(root->right) + 1;
}

3.7 第k层的节点个数

同样采用分治的策略,如果要统计某k层的节点数,需要从根节点出发,分治给下层节点统计某 k -1 层的节点数,以此类推,直到为1层数时,进行统计,空节点算0,其他节点算1,代码示例如下:

int BinaryTreeLevelKSize(BinaryTreeNode* root, int k) {
	if (root == NULL)
		return 0;
	if (k == 1) {
		return 1;
	}
	return BinaryTreeLevelKSize(root->left, k - 1) + BinaryTreeLevelKSize(root->right, k - 1);
}

3.8 方法总结

对于二叉树的各种方法实现,使用到最多的就是递归,换句话说就是使用了分治的思想,因此面对其他问题实现时,可以考虑能否使用二叉树的链式结构特点,通过将问题转换为子方法实现与父方法整合的方向,从而实现递归解决。


补充:

  1. 代码将会放到:C++/C/数据结构代码链接 ,欢迎查>看!
  2. 欢迎各位点赞、评论、收藏与关注,大家的支持是我更新的动力,我会继续不断地分享更多的知识!

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

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

相关文章

世界65个国家贸易开放度数据 2005-2019年

一、数据介绍 数据名称&#xff1a;UNtrade数据库 数据年份&#xff1a;2005-2019年 数据范围&#xff1a;世界65个国家 数据来源&#xff1a;各地方统计局 部分数据如下&#xff1a; 二、参考文献 用途&#xff1a;研究人民币实际汇率与贸易差额之间的关系等。 [1]卢向…

R语言文本挖掘tf-idf,主题建模,情感分析,n-gram建模研究

数据集中的Usenet公告板包括新汽车&#xff0c;体育和密码学等主题。最近我们被客户要求撰写关于主题建模的研究报告&#xff0c;包括一些图形和统计输出。我们对20个Usenet公告板的20,000条消息进行分析。 相关视频&#xff1a;文本挖掘&#xff1a;主题模型&#xff08;LDA&a…

SpringBoot SpringBoot 原理篇 2 自定义starter 2.1 记录系统访客独立IP访问次数案例介绍

SpringBoot 【黑马程序员SpringBoot2全套视频教程&#xff0c;springboot零基础到项目实战&#xff08;spring boot2完整版&#xff09;】 SpringBoot 原理篇 文章目录SpringBootSpringBoot 原理篇2 自定义starter2.1 记录系统访客独立IP访问次数案例介绍2.1.1 介绍2.1.2 需求…

SpringCloud 组件Gateway服务网关【断言工厂过滤器工厂】

目录 1&#xff1a;断言工厂 2&#xff1a;过滤器工厂 2.1&#xff1a;路由过滤器的种类 2.2&#xff1a;请求头过滤器 2.3&#xff1a;默认过滤器 2.4&#xff1a;总结 1&#xff1a;断言工厂 路由断言工厂Route Predicate Factory 路由配置包括&#xff1a; 1. 路由…

C++_串口编程_官方示例:监视通信事件

这是微软官方的一个例子&#xff0c;这个例子中&#xff0c;如果不做修改&#xff0c;那么他是可以异步运行的&#xff0c;会出现一个错误&#xff1a;官方也说了一下&#xff0c;但是不太好懂&#xff0c;我拷贝过来放在这里&#xff0c;作为参考。 如果无法立即完成重叠的操作…

1_MyBatis入门

原生JDBC实现CURD的问题 1 编码繁琐 2 需要我们自己将结果集映射成对象 3 性能不太好 连接池 缓存 4 SQL语句和java代码的耦合度特别高 5 … … MyBatis 本是Apache的一个开源项目iBatis, 2010年这个项目由Apache Software Foundation 迁移到了Google Code&#xff0c;且改名为…

python学习笔记(10)

目录 第八章 函数 1.模块化程序设计&#xff08;模块化---封装、复用、可替代&#xff09; 2.定义函数 3.函数调用 4.return语句 5.函数参数 6.变量作用域 7.函数的递归调用 8.匿名函数 9.迭代器 10.生成器 11.装饰器 第八章 函数 1.模块化程序设计&#xff08;…

【线性表】—不带头单向非循环链表的增删查改

小菜坤日常上传gitee代码&#xff1a;https://gitee.com/qi-dunyan&#xff08;所有的原码都放在了我上面的gitee仓库里&#xff09; 数据结构知识点存放在专栏【数据结构】后续会持续更新 ❤❤❤ 个人简介&#xff1a;双一流非科班的一名小白&#xff0c;期待与各位大佬一起努…

dns网络服务器未响应是什么原因(如果各自方法都尝试后无法使用,请尝试重启猫)

事件起因&#xff1a;周六晚上&#xff0c;看法国对丹麦世界杯&#xff0c;突然网页浏览器无法工作。 1.然后尝试修改dns路由修改为114.114.114.114&#xff0c;又还原system32/driver/hosts仍然无法使用 2.查看是否有浏览器代理&#xff0c;查询无 3.查看是否有可疑进程&…

游戏开发24课 cocoscreator scrollview优化

分享一个 ScrollView 优化组件 增加一个 在线演示地址 显得高大上一点 image.png32762116 263 KB 目前支持的功能 水平/垂直滑动 可变尺寸动态更新 平滑滚动到底部 Grid 正序排列、倒序排列 无限循环滚动 单向、双向 下拉刷新 &#xff08;只是这么叫 实际上就是两种类型…

深度学习第四课——卷积神经网络(week 2)

目录 二、深度卷积网路 2.1 经典网络结构 2.1.1 LeNet - 5 2.1.2 AlexNet 2.1.3 VGG - 16 2.2 残差网络&#xff08;ResNets - Residual Networks&#xff09; 2.3 残差网络为什么有用 2.4 网络中的网络及11卷积 2.5 Inception网络 2.5.1 介绍 2.5.2 应用 2.6 使用…

24. [Python GUI] PyQt5中的模型与视图框架-表格部件QTableWidget

PyQt5的表格部件QTableWidget QTableWidget 类继承自 QTableView&#xff0c;该类是一个由 Qt 实现的标准的表格部件&#xff0c;该类的数据项由 QTableWidgetItem 类管理。 当前单元格(或当前项目)与当前索引或当前选择是相同的&#xff0c;即可以同时选择多个单元格&#x…

Spark 3.0 - 7.LR 多分类实现影评预测电影评分与指标评测

目录 一.引言 二.LR 多分类分析 三.LR 多分类实战 1.数据准备 Comment -> RDD -> DF 2.数据处理 JieBaTokenizer -> HashingVector 3.模型训练 LR 4.模型评估 Metrics 5.人工校验 DIY 四.总结 一.引言 Spark 3.0 - 5.ML Pipeline 实战之电影影评情感分析 通…

浅析数据采集工具Flume

title: Flume系列 第一章 Flume基础理论 1.1 数据收集工具产生背景 Hadoop 业务的一般整体开发流程&#xff1a; 任何完整的大数据平台&#xff0c;一般都会包括以下的基本处理过程&#xff1a; 数据采集 数据 ETL 数据存储 数据计算/分析 数据展现 其中&#xff0c;数据…

Nacos注册中心和服务方式

目录 一、服务治理介绍 常见的注册中心 二、Nacos注册中心介绍 三、运用Nacos搭建环境 四、DiscoveryClient实现负载均衡 五、Ribbon实现负载均衡 六、基于Feign实现服务调用 七、Feign传参 一、服务治理介绍 通过上一章的操作&#xff0c;我们已经可以实现微服务之间的调…

【Android +Tensroflow Lite】实现从基于机器学习语音中识别指令讲解及实战(超详细 附源码)

需要源码和配置文件请点赞关注收藏后评论区留言~~~ 一、基于机器学习的语音推断 Tensorflow基于分层和模块化的设计思想&#xff0c;整个框架以C语言的编程接口为界&#xff0c;分为前端和后端两大部分 Tensorflow框架结构如下图 二、Tensorflow Lite简介 虽然Tensorflow是一…

WMS类图结构分析-android12

为什么要分析类图&#xff1f; WMS是一个复杂的模块&#xff0c;就像一个很大的家族&#xff0c;里面有各种角色&#xff0c;认识类图就像是认识WMS模块中的各个角色&#xff0c;不先把人认清楚了&#xff0c;怎么更好的理解他们之间的交互&#xff1f; 我觉得&#xff0c;这…

【MATLAB教程案例47】基于双目相机拍摄图像的三维重建matlab仿真

欢迎订阅《FPGA学习入门100例教程》、《MATLAB学习入门100例教程》 本课程学习成果预览: 目录 1.软件版本 2.基于双目相机拍摄图像的三维重建原理概述

GII全球创新指数2013-2020

1、数据来源&#xff1a;世界知识产权组织发布的《2021年全球创新指数报告》 2、时间跨度&#xff1a;2013-2020 3、区域范围&#xff1a;全球 4、指标说明&#xff1a; 全球创新指数&#xff08;Global Innovation Index&#xff0c;GII&#xff09;是世界知识产权组织、康…

20221127-1Spring_day01(资料来自黑马程序)

Spring_day01 今日目标 掌握Spring相关概念完成IOC/DI的入门案例编写掌握IOC的相关配置与使用掌握DI的相关配置与使用 1&#xff0c;课程介绍 对于一门新技术&#xff0c;我们需要从为什么要学、学什么以及怎么学这三个方向入手来学习。那对于Spring来说: 1.1 为什么要学? …