【数据结构与算法】树与二叉树

在这里插入图片描述

目录

  • 一.树
    • 1.树的定义
    • 2.结点的分类与关系
    • 3.树的相关概念
    • 4.树的表示方法
    • 二.二叉树
      • 1.二叉树的定义
      • 2.特殊二叉树
      • 3.二叉树的性质
      • 4.二叉树的顺序结构
      • 5.二叉树的链式结构
        • (1)链式结构的创建
        • (2)结点的创建
        • (3)二叉树的手动构建
        • (4)前中后序遍历
        • (5)二叉树结点个数
        • (6)二叉树的高度
        • (7)第k层的结点个数
        • (8)查找二叉树中为x的结点
        • (9)层序遍历
        • (10)判断是否为完全二叉树
        • (11)销毁二叉树

一.树

1.树的定义

除了之前我们讲的栈、队列、链表等线性结构,数据结构中还有着一对多的非线性结构———
树是有n个结点组成的有限集,当n=0时为空树,在任意一颗非空树中,有且仅有一个特定的根结点 ;当n>1时,其余结点又可以分为一棵树,称为根的子树
如下图所示:
在这里插入图片描述
A为整棵树的根节点,分别以B、C为根节点能组成两颗子树:
在这里插入图片描述

2.结点的分类与关系

结点拥有的子树个数称为结点的度,一颗树结点为各个结点度的最大值。度为0的结点称为叶结点或者终端结点
一棵树中结点之间的关系,结点的子树的根结点称为该结点的孩子,所以该结点称为孩子结点的双亲结点。同一个双亲的孩子结点之间称为兄弟结点

3.树的相关概念

结点的层次就是从何根开始定义为第一层,根的孩子为第二层以此类推。树中结点的最大层次称为树的深度或者高度
森林是指由多颗互不相交的树的集合。

4.树的表示方法

树常使用双亲表示法:

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

二.二叉树

1.二叉树的定义

二叉树是度为2的树,二叉树的子树有左右之分所以二叉树是有序树

2.特殊二叉树

  1. 斜树顾名思义,斜树一定是斜的:所有结点都只有左子树的二叉树叫做左斜树,所有结点都只有右子树的二叉树叫做右斜树。这两者统称为斜树。
  2. 满二叉树:在一棵二叉树中,如果所有分支结点都存在左子树和右子树,并且所有叶子结点又在同一层上,这样的二叉树叫做满二叉树
  3. 完全二叉树,假设完全二叉树的有K层则此二叉树在前K-1层为满二叉树并且在第K层至少有一个结点,在第K层从左到右连续。

3.二叉树的性质

  1. 在二叉树的第k层至多有2^(k-1)个结点。
  2. 深度为k的二叉树至多有2^k-1个结点。
  3. 对于任何一棵二叉树,叶子结点数为n0,度为2的结点数为n2,则有n0=n2+1
  4. 具有n个结点的完全二叉树的深度为log以2为底n的对数加1
  5. 具有n个结点的二叉树具有n-1个边。

4.二叉树的顺序结构

二叉树的顺序结构就是使用一维数组存储二叉树的结点,但是只适合储存完全二叉树,也就是前面讲过的堆,所以这里不过多赘述,想了解的点击链接[堆和堆排序]。(http://t.csdn.cn/Sgjar)

5.二叉树的链式结构

现在我们来看下二叉树真正适合的存储结构-----链式结构。

(1)链式结构的创建

一个结点可以存储一个数据元素,以及左孩子、右孩子。

typedef int BTDataType;

typedef struct BinaryTreeNode
{
	BTDataType data;//存储一个数据元素
	struct BinaryTreeNode* left;//指向左孩子
	struct BinaryTreeNode* right;//指向右孩子
}BTNode;

(2)结点的创建

我们编写一个用来创建结点存储数据的函数,并将左右孩子初始化为空。

BTNode* BuyNode(BTDataType x)
{
	BTNode* newnode = (BTNode*)malloc(sizeof(BTNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		return NULL;
	}

	newnode->data = x;
	newnode->left = NULL;
	newnode->right = NULL;

	return newnode;

}

(3)二叉树的手动构建

我们使用创建结点函数手动构建出一个二叉树便于验证后续函数代码的正确:

BTNode* CreatTree()
{
	BTNode* node1 = BuyNode(1);
	BTNode* node2 = BuyNode(2);
	BTNode* node3 = BuyNode(3);
	BTNode* node4 = BuyNode(4);
	BTNode* node5 = BuyNode(5);
	BTNode* node6 = BuyNode(6);
	BTNode* node7 = BuyNode(7);


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

	return node1;
}

在这里插入图片描述

(4)前中后序遍历

前中后序遍历的区别即是根结点如何访问的问题,eg:前序遍历:先访问根结点再访问左孩子最后右孩子。

void PreOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}

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

}

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

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

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

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

(5)二叉树结点个数

二叉树链式存储结构函数的编写多使用递归的方式,这里我们需要求得结点的个数只需要将左树的结点加上右树的再加1即可

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

(6)二叉树的高度

二叉树的高度为树中结点的最大层次,比较左子树和右子树高度的较大值再加一个即可,需要注意的是需要记录左右子树的高度,不然递归的效率将会大大降低。

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

	int leftHeight = TreeHeight(root->left);
	int rightHeight = TreeHeight(root->right);

	return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
}

(7)第k层的结点个数

利用分治递归的思想,想要求第k层的结点个数,只要分别求左右树第k-1层的结点个数并相加即可。


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

	if (k == 1)
		return 1;

	int leftKLevel = TreeKLevel(root->left, k - 1);
	int rightKLevel = TreeKLevel(root->right, k - 1);

	return leftKLevel + rightKLevel;
}

(8)查找二叉树中为x的结点

BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{
	if (root == NULL)
		return NULL;

	if (root->data == x)
		return root;

	BTNode* left = BinaryTreeFind(root->left, x);
	if (left)
		return left;

	BTNode* right = BinaryTreeFind(root->right, x);
	if (right)
		return right;

	return NULL;

}

(9)层序遍历

层序遍历即是将每一层都访问后,继续访问下一层,显然之前学习的队列非常适合实现这一操作:其中重要的是将队列原先存储的元素改为二叉树的结点指针类型


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

typedef struct QueueNode
{
	struct QueueNode* next;
	QDataType data;
}QNode;

typedef struct Queue
{
	QNode* head;
	QNode* tail;
	int size;
}Queue;


void QueueInit(Queue* pt);
void QueueDestroy(Queue* pt);

void QueuePush(Queue* pt, QDataType x);
void QueuePop(Queue* pt);
bool QueueEmpty(Queue* pt);
QDataType QueueFront(Queue* pt);

void QueueInit(Queue* pt)
{
	assert(pt);

	pt->head = pt->tail = NULL;
	pt->size = 0;
}

void QueueDestroy(Queue* pt)
{
	assert(pt);
	QNode* cur = pt->head;

	while (cur)
	{
		QNode* next = cur->next;
		free(cur);
		cur = next;
	}
	pt->head = pt->tail = NULL;
	pt->size = 0;
}


void QueuePush(Queue* pt, QDataType x)
{
	QNode* newnode = (QNode*)malloc(sizeof(QNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		return;
	}

	newnode->next = NULL;
	newnode->data = x;

	if (pt->head == NULL)
	{
		assert(pt->tail == NULL);
		pt->head = pt->tail = newnode;
	}
	else
	{
		pt->tail->next = newnode;
		pt->tail = newnode;
	}

	pt->size++;

}


void QueuePop(Queue* pt)
{
	assert(pt);
	assert(pt->head != NULL);

	if (pt->head->next == NULL)
	{
		free(pt->head);
		pt->head = pt->tail = NULL;
	}
	else
	{
		QNode* next = pt->head->next;

		free(pt->head);
		pt->head = next;
	}
	pt->size--;
}
bool QueueEmpty(Queue* pt)
{
	assert(pt);
	
	return pt->size == 0;
}

QDataType QueueFront(Queue* pt)
{
	assert(pt);
	assert(!QueueEmpty(pt));

	return pt->head->data;
}

void LevelOrder(BTNode* root)
{
	Queue q;
	QueueInit(&q);

	if (root)
		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);

	}



	QueueDestroy(&q);
}

(10)判断是否为完全二叉树

我们想要判断二叉树是否为完全二叉树,就要了解完全二叉树的特点:完全二叉树在前k-1层是满二叉树,在第k层的非空结点从左到右也是连续的。思路已有,代码实现:

bool BinaryTreeComplete(BTNode* root)
{
	Queue eth;
	QueueInit(&eth);

	if (root)
		QueuePush(&eth, root);

	while (!QueueEmpty(&eth))
	{
		BTNode* front = QueueFront(&eth);
		QueuePop(&eth);

		if (front == NULL)
			break;
		else
		{
			QueuePush(&eth, front->left);
			QueuePush(&eth, front->right);
		}
	}

	while (!QueueEmpty(&eth))
	{
		BTNode* front = QueueFront(&eth);
		QueuePop(&eth);

		if (front)
		{
			QueueDestroy(&eth);
			return false;
		}
	}

	return true;
}

(11)销毁二叉树

销毁二叉树我们依旧使用递归实现,需要注意的是在调用销毁函数后,需要在函数体外手动置空。

void TreeDestroy(BTNode* root)//外部手动置空
{
	if (root == NULL)
		return;

	TreeDestroy(root->left);
	TreeDestroy(root->right);

	free(root);

}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Docker目录迁移

介绍 在docker的使用中随着下载镜像越来越多,构建镜像、运行容器越来越多, 数据目录必然会逐渐增大;当所有docker镜像、容器对磁盘的使用达到上限时,就需要对数据目录进行迁移。 如何避免: 1.在安装前对/var/lib/docker&#x…

如何3步精读《PMBOK指南》(含PMP备考资料)

初学者学习《PMBOK指南》的确有点吃亏,比不得那些项目管理专业以及相关专业的毕业生,哪怕只稍微接触过项目的都比初学者强。 所以,有计划性的阅读就显得尤为重要,要克服的不仅是阅读上的枯燥,还有专业知识的理解&…

Java——JDK动态代理

1.动态代理 1.1什么是动态代理? 动态代理(理解) 基于反射机制 举个例子,生活中一般在打官司的时候都会请代理律师,为什么要请律师呢?是因为开庭的时候大部人对于打官司没有经验,只会说出自己案件的陈述,并不…

软硬皆施,WMS仓库管理系统+PDA,实现效率狂飙

人工经验Excel表格,是传统第三方仓储企业常用的管理模式。在这种管理模式下,对仓库员工的Excel操作能力、业务经验和工作素养要求极高。一旦员工的经验能力不足,就会导致仓库业务运行不顺畅,效率低下,而员工也会因长时…

【MySQL】基于GTID的半同步主从复制(实践)

一、GTID简介 什么是GTID? 全局事务标识符GTID的全称为Global Transaction Identifier,是在整个复制环境中对一个事务的唯一标识。 它是MySQL 5.6加入的一个强大特性,目的在于能够实现主从自动定位和切换,而不像以前需要指定文件和位置。 …

ArduPilot飞控之DIY-F450计划

ArduPilot飞控之DIY-F450计划1. 历史2. 源由3. 计划3.1 硬件3.2 软件4. 动手4.1 接线4.1.1 ELRS nano接收机4.1.2 BN880 GPS模块4.1.3 Radio Telemetry4.2 配置4.2.1 选择四轴机型4.2.2 电源参数调整4.2.3 校准加速度计4.2.4 校准磁力计4.2.5 遥控器校准4.2.6 电机设置4.2.7 电…

企业电子招投标采购系统——功能模块功能描述+数字化采购管理 采购招投标

​ 功能模块: 待办消息,招标公告,中标公告,信息发布 描述: 全过程数字化采购管理,打造从供应商管理到采购招投标、采购合同、采购执行的全过程数字化管理。通供应商门户具备内外协同的能力,为外…

Melis4.0[D1s]:4.测试笔记 - 内嵌的显示命令

文章目录1.配置将显示测试源码包含进工程(默认是包含了)2.不要启动melis桌面系统3.开始测试3.1 disp 命令3.1.1 disp不带参数时,打印显示信息:3.1.2 disp -c 0 8 测试4种颜色3.2 disp_layer_cfg 命令3.3 disp_mem 对显示内存写入内…

全球自动驾驶竞争力最新排行榜,4家中国企业上榜

发展至今,自动驾驶技术不仅是汽车行业的一个主战场,更是全球科技领域中备受关注和充满竞争的一个重要领域。近年来,各大汽车制造商和科技公司都在投入大量财力物力人力进行自动驾驶技术的研发,并进一步争夺市场份额。 当然&#…

人工智能前沿——「小海带」超全视觉注意力机制资源分享(附下载链接)

📚📚 人工智能 | 计算机视觉 —— 致力于目标检测领域科研Tricks改进与推荐 | 主要包括主干网络改进、轻量化网络、注意力机制、检测头部改进、空间金字塔池化、损失函数及NMS改进、ICCV/CVPR/ECCV视觉顶会创新点改进、各类数据集资源分享以及算法训练相…

智云通CRM:如何给客户创造尽可能安全的成交环境?

销售人员要想和客户顺利成交,给对方创造尽可能安全的成交环境尤为重要。销售人员的每一句话和每一个观点,应当都是客户所想的。客户是非常聪明的,有任何风吹草动,他们都会提高警惕,这就是客户跟销售人员之间关系的现状…

笔记:关于使用vitepress 制作静态站点并托管到gitee

笔记:关于使用vitepress 制作静态站点并托管到giteegiteejcLee95:https://blog.csdn.net/qq_28550263?spm1001.2101.3001.5343 邮箱 :291148484163.com 本文地址:https://blog.csdn.net/qq_28550263/article/details/129419979…

spring5(五):AOP操作

spring5(五):AOP操作前言一、代理模式1、场景模拟2、代理模式2.1 概念2.2 静态代理2.3 动态代理二、AOP概述1、什么是 AOP?2、相关术语3、作用三、AOP底层原理1、AOP 底层使用动态代理2、AOP(JDK 动态代理)2.1 编写 J…

二分查找(二)

2.练习题 3) 力扣https://leetcode.cn/problems/search-in-rotated-sorted-array-ii/这题需要分三种情况,第一种是区间有序,正常二分查找,第二种是区间 被旋转,左区间的值大于右区间,需要比较目标值和左区…

机器视觉行业公司2023今年最大的特点-老员工真香现象出现,事出反常必有妖

老员工真香现象出现,事出反常必有妖。-老人涨薪,新人不招或降薪,工作2-3年最值得跳槽,培训后好找工作。 你可能看到以上现象很反感,但是现在机器视觉行业环境就是这个样子的。 今年机器视觉行业最大的特点就是项目技术…

Atlassian Server用户新选择 | 迁移到数据中心版前,您需要做这些准备(2)

2024年2月,也就是一年不到,Atlassian将终止对Server产品及插件的所有支持。 此公告发布后,许多用户需要了解怎样的前进方向才是最适合企业的。为此,Atlassian不仅提供云版,还提供了本地部署的数据中心(Data…

排序算法之希尔排序

📝个人主页:爱吃炫迈 💌系列专栏:数据结构与算法 🧑‍💻座右铭:快给我点赞赞💗 文章目录1. 希尔排序2. 算法思路3. 算法实现4. 算法性能分析💞总结💞1. 希尔排…

帆软FineReport学习篇(一)

帆软FineReport学习篇(一) 1 FineReport 11版下载 1.1 进入下载官网 fineReport 11版本下载链接 1.2 选择合适的版本,点击下载即可 2 解决问题的途径 2.1 社区搜索 2.1.1 进入社区官网 社区官网 2.1.2 输入搜索内容,点击搜索 1.1.3 选择你所需要的答案,建议优先点击官方…

易基因-单细胞甲基化测序单细胞转录组测序

大家好,这里是专注表观组学十余年,领跑多组学科研服务的易基因。 易基因单细胞甲基化测序&单细胞转录组测序 高通量单细胞DNA甲基化测序简介 单细胞DNA甲基化组学研究很大程度上受制于建库技术,传统的文库构建方法或类似于基因组DNA的单…

【Python】1分钟就能制作精美的框架图?太棒啦

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录前言一、准备二、基本使用与例子1.初始化与导出2.节点类型3.集群块4.自定义线的颜色与属性总结前言 Diagrams 是一个基于Python绘制云系统架构的模块,它能…
最新文章