详解数据结构-二叉树

本文详细介绍了树和二叉树的概念、结构及其基本术语,包括度、叶子结点、分支结点等。重点讲解了二叉树的创建、遍历(前序、中序、后序和层序)以及相关操作,如结点个数、叶子结点个数、深度计算等。此外,还探讨了完全二叉树的判断和二叉树的销毁方法。
摘要由CSDN通过智能技术生成

文章目录


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

树的概念
在任意一棵非空的树中应满足:
1.有且仅有一个特殊的结点,称为根结点。
2.在树形结构中,子树之间不能有交集,否则就不是树形结构。
3.除根结点外,其余结点被分成M(M>0)个互不相交的集合T1、T2、……、Tm,其中每一个集合Ti(1<= i <= m)又是一棵结构与树类似的子树。
4.显然,树的定义是递归的,即在树的定义中又用到了自身,树是一种递归的数据结构。树作为一种逻辑结构,同时也是一种分层结构,具有以下两个特点:
1.树的根结点没有前驱,除根结点外的所有结点有且只有一个前驱。
2.树中所有结点可以有0个或多个后继。

树的结构图及基本术语
在这里插入图片描述
结点的度:一个结点含有的子树的个数称为该结点的度; 如上图: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)棵互不相交的树的集合称为森林;

什么是二叉树?
二叉树是另一种树形结构,二叉树也以递归的形式定义。二叉树是n (n≥0) 个结点的有限集合。二叉树的两种情况:
1.为空二叉树,即n=0。
2.由一个根结点和两个互不相交的被称为根的左子树和右子树组成。左子树和右子树又分别是一棵二叉树。
在这里插入图片描述

二叉树的概念

  1. 二叉树的每个结点至多只有两棵子树( 即二叉树中不存在度大于2的结点)。
  2. 二叉树的子树有左右之分,次序不能颠倒,因此二叉树是有序树。
    对于任意的二叉树都是由以下5种基本形态复合而成的:
    在这里插入图片描述

二叉树的性质
![在这里插入图片描述](https://img-blog.csdnimg.cn/59616005d6484f35a55d56f321b59c06.png

特殊的二叉树

  1. 满二叉树:一个二叉树,如果每一个层的结点数都达到最大值,并且除叶子结点之外的每个结点度数均为2,则这个二叉树就是满二叉树。反过来说,如果一个二叉树的层数为K,且结点总数是 (2的k次方-1),则它就是满二叉树。
  2. 完全二叉树:完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为K的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时称之为完全二叉树。 要注意的是满二叉树是一种特殊的完全二叉树。

链式二叉树的实现
接下来我们按照下面的图片中的二叉树结构来进行代码实现:
在这里插入图片描述

二叉树的类型创建

typedef int BTDatetype;
//定义一个结构体,data存储数据
//结构体指针left指向左子树,结构体指针right指向右子树
typedef struct BinaryTreeNode
{
	struct BinaryTreeNode* left;
	struct BinaryTreeNode* right;
	BTDatetype data;
}BTNode;

二叉树如何创建一个结点

//生成结点的函数
BTNode* BuyNode(BTDatetype x)
{   
    //空间需要我们malloc申请,申请的空间大小就是结构体的大小
	BTNode* tmp = (BTNode*)malloc(sizeof(BTNode));
	assert(tmp);
	//左孩子初始化为空,右孩子初始化为空
	tmp->left = NULL;
	tmp->right = NULL;
	tmp->data = x;
	return tmp;
}
BTNode* CreatBinaryTree()
{
    //这里我们申请6个结点出来,方便后面使用
	BTNode* node1 = BuyNode(1);
	BTNode* node2 = BuyNode(2);
	BTNode* node3 = BuyNode(3);
	BTNode* node4 = BuyNode(4);
	BTNode* node5 = BuyNode(5);
	BTNode* node6 = BuyNode(6);
    //这时候结点之间还没有联系
    //下面就是按照"链式二叉树下面的图"的结构链接起来的二叉树
	node1->left = node2;
	node1->right = node4;
	node2->left = node3;
	node4->left = node5;
	node4->right = node6;
	//最后把第一个结点,也就是根结点返回
	return node1;
}

链式二叉树的基本接口函数

//前序遍历
void Preorder(BTNode* root);
//中序遍历
void Inorder(BTNode* root);
//后序遍历
void Postorder(BTNode* root);
//层序遍历
void Levelorder(BTNode* root);
//求二叉树的结点个数
int TreeSize(BTNode* root);
//求二叉树的叶子结点个数
int TreeLeafSize(BTNode* root);
//求二叉树的第k层的结点个数
int TreeKLevel(BTNode* root, int k);
//求二叉树的深度
int TreeDepth(BTNode* root);
//二叉树的查找数据
BTNode* TreeFind(BTNode* root, BTDatetype x);
//判断这个二叉树是否是完全二叉树
int BinaryTreeComplete(BTNode* root);
//二叉树的销毁
void BinaryTreeDestory(BTNode* root);

二叉树的遍历
我们先来了解一下二叉树是如何进行以下几种遍历方式的,以及遍历的结果,画图详解:

//前序遍历
void Preorder(BTNode* root);
//中序遍历
void Inorder(BTNode* root);
//后序遍历
void Postorder(BTNode* root);
//层序遍历
void Levelorder(BTNode* root);

![在这里插入图片描述](https://img-blog.csdnimg.cn/37ab05c7195d4e15bc833f3a0305f540.png

前序遍历
通过递归来完成遍历,先打印根结点的数据,再递归根结点的左子树,左子树递归完,再递归右子树,这里用眼睛看比较容易混乱,建议画一下递归展开图,会非常清晰。

void Preorder(BTNode* root)  //前序遍历顺序 根->左子树->右子树
{
//如果结点为NULL 就打印#号来代替 防止意外使用NULL结点
    if (root == NULL)
	{
		printf("# ");
		return;
	}
	printf("%d ", root->data); //先打印根 根不是空 就去找根的左子树
	Preorder(root->left);      //直到为空,再去找右子树,直到为空
	Preorder(root->right);
}

中序遍历
和前序遍历类似,不过中序遍历是先递归根结点的左子树进行打印,然后根,最后再去递归右子树,形成左子树->根->右子树的遍历方式。

void Inorder(BTNode* root)  //中序遍历顺序 左子树->根->右子树
{
	if (root == NULL)
	{
		printf("# ");
		return;
	}
	Inorder(root->left);
	printf("%d ", root->data);
	Inorder(root->right);
}

后序遍历
和前面两个遍历方式类似,后序遍历是先递归访问左子树,然后右子树,最后是根。

void Postorder(BTNode* root)  //后序遍历顺序 左子树->右子树->根
{
	if (root == NULL)
	{
		printf("# ");
		return;
	}
	Postorder(root->left);
	Postorder(root->right);
	printf("%d ", root->data);
}

层序遍历
这里我们需要借用下对列来实现层序遍历,因为队列的特性是先进先出,所以要借助队列完成层序遍历就需要先添加这个结点进入队列,然后打印,如果这个结点的左右子树都不为空就都添加进队列,凭借队列的先进先出,先进去左子树,后进去右子树,然后出队列时一个一个访问,这样就形成了层序遍历。

void Levelorder(BTNode* root)        
{
	Queue q;
	QueueInit(&q);   //队列中数据的类型是BTNode*  二叉树的结点指针类型

	if (root != NULL)//结点不等于空就往队列中添加数据
	{
		QueuePush(&q, root);
	}
	while (!QueueEmpty(&q)) //队列不为空
	{
		BTNode* front = QueueFront(&q);
		printf("%d ", front->data);
		QueuePop(&q);
		if (front->left != NULL)//如果这个结点的左右子树不为空就添加进队列,为空就不添加
		{                       //出队列出一个数据就添加这个数据的左右子树,形成层序遍历
			QueuePush(&q, front->left);
		}
		if (front->right != NULL)
		{
			QueuePush(&q, front->right);
		}
	}
	printf("\n");
	QueueDestroy(&q);
}

求二叉树的结点个数
利用分治的思想,先递归根结点的左子树,然后把左子树这个结点也看作小的一颗二叉树,然后再递归它的左子树,一直到NULL为止,然后把右子树也看作一颗小的二叉树,再一直递归它的右子树,最后返回这个结点的左子树个数+右子树的个数+1,一直回到根结点,最终得到的返回值就是这颗二叉树的所有结点个数了。

int TreeSize(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}       
	        //加1是自身结点 即使没有左子树和右子树也有自身结点1
	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);
}

求二叉树第k层的结点个数

int TreeKLevel(BTNode* root, int k)
{
	assert(k >= 1);
	if (root == NULL)
	{
		return 0;
	}
	if (k == 1)     //转换为子问题 求k层的结点数,就是求根结点左子树的
	{               //k-1层 + 右子树的k-1层,层层递归
		return 1;
	}
	return TreeKLevel(root->left, k - 1) + TreeKLevel(root->right, k - 1);
}

求二叉树的深度
一颗二叉树的深度,是叶子结点距离根结点最远的那个结点,这个距离就是二叉树的深度或高度。

int TreeDepth(BTNode* root) 
{
	int left, right, tmp;
	if (root != NULL)
	{
		left = TreeDepth(root->left);   //计算左子树深度
		right = TreeDepth(root->right); //计算右子树深度
		tmp = left > right ? left : right; //比较大小
		return tmp + 1;                    //返回大的深度+1;
	}
	else
		return 0;
}

二叉树的查找数据

BTNode* TreeFind(BTNode* root, BTDatetype x)
{
	if (root == NULL)
	{
		return NULL;
	}
	if (root->data == x)              //如果root直接找到了就直接返回
	{
		return root;
	}
	BTNode* left = TreeFind(root->left, x);  //找不到就去左子树找
	if (left!=NULL)
	{
		return left;
	}
	BTNode* right = TreeFind(root->right, x);//找不到就去右子树找
	if (right!=NULL)
	{
		return right;
	}
	return NULL;                      //最后找不到返回NULL
}

判断这个二叉树是否是完全二叉树
判断是否是完全二叉树的方法:
1.根据完全二叉树的性质,如果是完全二叉树的话,那么按照层序遍历的方式进行遍历,所有的结点都是连续的,不会存在中间有NULL结点,NULL结点只会在末尾出现,并且一出现NULL,那后面就都是NULL结点了。
2.按照 1 的描述,如果出现了NULL结点之后,后面的结点如果还有非空的结点,那么这棵二叉树就不是完全二叉树。

这里我们需要借用队列来完成,利用队列的先进先出特性,先把整棵树的所有结点依次按照层序遍历的方式添加进队列中,然后进行上面的方法对比判断是否是完全二叉树。

int BinaryTreeComplete(BTNode* root)
{
	Queue q;
	QueueInit(&q);

	if (root != NULL)
	{
		QueuePush(&q, root);
	}
	while (!QueueEmpty(&q))
	{
		BTNode* front = QueueFront(&q);
		QueuePop(&q);
		if (front != NULL) //只要这个结点不为空 就添加它的左右子树进
		{                 //队列,左右子树不管为不为空 为空也添加进去
			QueuePush(&q, front->left);
			QueuePush(&q, front->right);
		}
		else
		{
			break;   //遇到空结点了 就跳出循环
		}
	}
	//1.如果后面的结点全是空就是完全二叉树
	//2.如果后面有非空的结点就不是完全二叉树
	while (!QueueEmpty(&q))
	{
		BTNode* front = QueueFront(&q);
		QueuePop(&q);
		if (front != NULL)
		{
			QueueDestroy(&q);
			return false;
		}
	}
	return true;

	QueueDestroy(&q);
}

二叉树的销毁
销毁这里我们采用后序的方式来进行销毁,先销毁左子树->右子树->根。

//用后序的方式
void BinaryTreeDestory(BTNode* root)
{
	if (root == NULL)
	{
		return;
	}
	BinaryTreeDestory(root->left);
	BinaryTreeDestory(root->right);
	free(root);
}

所有接口函数的运行结果

int main()
{
	BTNode* root = CreatBinaryTree();
	Preorder(root);  //前序遍历
	printf("\n");
	Inorder(root);   //中序遍历
	printf("\n");
	Postorder(root); //后序遍历
	printf("\n");

	printf("二叉树的结点个数:%d\n", TreeSize(root));

	printf("二叉树的叶子结点个数:%d\n", TreeLeafSize(root));

	printf("二叉树第k层的结点个数:%d\n", TreeKLevel(root,2));
	printf("二叉树第k层的结点个数:%d\n", TreeKLevel(root,3));

	printf("二叉树的深度:%d\n", TreeDepth(root));

	Levelorder(root);  //层序遍历

	int ret=BinaryTreeComplete(root);//判断完全二叉树 0为假,非0为真
	printf("是完全二叉树吗:%d\n", ret);

	BinaryTreeDestory(root); //销毁二叉树
	root = NULL;
	return 0;
}

在这里插入图片描述

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

JuLiJuLi.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值