数据结构之二叉树

树的概念

在介绍二叉树之前,我们首先了解一下关于树的知识。

树的定义

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

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

关于树的一些小概念

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

树的表示

可以看到,树的结构非常复杂,表示起来比较有难度,它有很多的表示方法:双亲表示法、孩子表示法、孩子兄弟表示法等等,那么其中最常用的就是孩子兄弟表示法。

这种方法能够很方便的表示出每个节点对应的关系,比如下面的树:
在这里插入图片描述
孩子兄弟表示法很明显需要用链表来进行存储,每一个节点中要包含三部分,一个是节点的值,一个是该节点的孩子节点(如果有多个孩子,指向左孩子),一个是该节点的兄弟节点。
在这里插入图片描述
孩子兄弟表示法如上图,可以看出,能够很清晰的表示出树的结构。

二叉树的概念及结构

概念

一棵二叉树是结点的一个有限集合,该集合或者为空,或者是由一个根节点加上两棵别称为左子树和右子树的二叉树组成。
二叉树的特点:
1.每个结点最多有两棵子树,即二叉树不存在度大于2的结点。
2.二叉树的子树有左右之分,其子树的次序不能颠倒。

数据结构中的二叉树

在这里插入图片描述

特殊的二叉树

在二叉树中,有两种特殊的二叉树,一个是满二叉树,一个是完全二叉树。
满二叉树:一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是说,如果一个二叉树的层数为K,且结点总数是(2^k) -1 ,则它就是满二叉树。
在这里插入图片描述
完全二叉树:完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为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:Log2(n+1)是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,无右孩子

二叉树的存储结构

顺序存储

二叉树的顺序结构存储就是用数组存储,数组存储就是用下标表示节点的编号,节点的值存储在数组中。但是使用数组只适合表示完全二叉树(比如说堆),因为如果不是完全二叉树会有空间的浪费。
在这里插入图片描述
如图所示,完全二叉树的节点是连续的,所以用数组来存储是不会有空间浪费的;但是非完全二叉树中节点并不是连续的,因此有的节点为空,因此存在数组中会有空间的浪费,而且也不利于查找。

链式存储

二叉树的链式存储,这里我们不用孩子兄弟表示法,而是采用一种更加直观的方式,每一个节点存储相应的值和它的左右孩子。

struct BinaryTreeNode
{
	BTDataType val;
	struct BinaryTreeNode* left;
	struct BinaryTreeNode* right;
}

在二叉树结构的实现中,最重要的就是二叉树的遍历。
所谓遍历,就是沿着某条搜索路线,依次对树中的每个节点做一次并且仅做一次访问。遍历分为前序遍历、中序遍历、后序遍历和层序遍历,下面我们来一一讲解。

前序遍历

前序遍历也称先序遍历,指的是访问根节点的操作发生在遍历其左右子树之前,下面是先序遍历的示意图,红色表示的是第一次访问的路线,蓝色表示返回的路线:
在这里插入图片描述

那么用代码应该怎么实现呢?
二叉树的遍历都是可以通过递归来完成,递归结束的条件是访问到空节点结束,每一次按照根节点、左孩子、右孩子的操作来完成。

//前序遍历
void BinaryTreePrevOrder(BTNode* root)
{
	//如果根节点为NULL说明到了叶子节点则返回
	if (root == NULL)
	{
		printf("NULL  ");
		return;
	}
	else
	{
		printf("%c  ", root->data);
		BinaryTreePrevOrder(root->left);
		BinaryTreePrevOrder(root->right);
	}
}

中序遍历

中序遍历指的是访问先访问左孩子,在访问根节点,最后访问右孩子。
在这里插入图片描述
代码实现:

//中序遍历
void BinaryTreeInOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("NULL  ");
		return;
	}
	else
	{
		BinaryTreeInOrder(root->left);
		printf("%c  ", root->data);
		BinaryTreeInOrder(root->right);
	}
}

后序遍历

后序遍历指的是先访问左孩子,再访问右孩子,最后访问根节点。
在这里插入图片描述
代码实现:

//后序遍历
void BinaryTreePosOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("NULL  ");
		return;
	}
	else
	{
		BinaryTreePosOrder(root->left);
		BinaryTreePosOrder(root->right);
		printf("%c  ", root->data);
	}
}

层序遍历

层序遍历是指从二叉树所在的根节点出发,首先访问第一层的根节点,然后从左往右访问第二层的节点,接着是第三层,以此类推,自上而下,从左到右逐层访问树的节点的过程。
在这里插入图片描述
层序遍历的实现就不能用递归了,因为层序遍历并不需要返回,依次遍历即可。按照这个思想,我们可以使用队列来完成层序遍历的过程,因为队列有先进先出的性质,而层序遍历从上到下、从左到右正好符合先进先出的思想。

以上面的图为例:
先把A入队,第一层结束;然后A出队,B、C入队,第二层结束;然后B、C出队,D、E、F入队,第三层结束;然后D、E、F出队,G入队,第四层结束;最后G出队。这样就完成了层序遍历。

代码如下:

//层序遍历
void BinaryTreeLevelOrder(BTNode* root)
{
	//层序遍历运用队列来实现,利用先入先出的思想
	Quene q;
	QueneInit(&q);
	//先入最开始的根节点
	if (root)
		QuenePush(&q, root);

	//然后开始循环入队出队
	while (!QueneEmpty(&q))
	{
		BTNode* front = QueneFront(&q);
		printf("%c->", front->data);
		//如果左节点或者右节点不为空则入队
		if (front->left)
			QuenePush(&q, front->left);
		if (front->right)
			QuenePush(&q, front->right);
		QueuePop(&q);
	}
	printf("NULL\n");
	QueneDestory(&q);
}

以上就是二叉树中比较重要的内容,二叉树中还有一些其他的功能,我把代码放在下面供大家参考:

//构造节点
BTNode* BuyBinaryTreeNode(BTDataType x)
{
	BTNode* Node = (BTNode*)malloc(sizeof(BTNode));
	if (Node == NULL)
	{
		printf("malloc failed\n");
		exit(-1);
	}
	Node->data = x;
	Node->left = NULL;
	Node->right = NULL;
	return Node;
}

//前序遍历
void BinaryTreePrevOrder(BTNode* root)
{
	//如果根节点为NULL说明到了叶子节点则返回
	if (root == NULL)
	{
		printf("NULL  ");
		return;
	}
	else
	{
		printf("%c  ", root->data);
		BinaryTreePrevOrder(root->left);
		BinaryTreePrevOrder(root->right);
	}
}

//中序遍历
void BinaryTreeInOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("NULL  ");
		return;
	}
	else
	{
		BinaryTreeInOrder(root->left);
		printf("%c  ", root->data);
		BinaryTreeInOrder(root->right);
	}
}

//后序遍历
void BinaryTreePosOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("NULL  ");
		return;
	}
	else
	{
		BinaryTreePosOrder(root->left);
		BinaryTreePosOrder(root->right);
		printf("%c  ", root->data);
	}
}


//层序遍历
void BinaryTreeLevelOrder(BTNode* root)
{
	//层序遍历运用队列来实现,利用先入先出的思想
	Quene q;
	QueneInit(&q);
	//先入最开始的根节点
	if (root)
		QuenePush(&q, root);

	//然后开始循环入队出队
	while (!QueneEmpty(&q))
	{
		BTNode* front = QueneFront(&q);
		printf("%c->", front->data);
		//如果左节点或者右节点不为空则入队
		if (front->left)
			QuenePush(&q, front->left);
		if (front->right)
			QuenePush(&q, front->right);
		QueuePop(&q);
	}
	printf("NULL\n");
	QueneDestory(&q);
}


//二叉树销毁
void BinaryTreeDestory(BTNode** root)
{
	//可以通过后序遍历来进行销毁
	if (*root == NULL)
	{
		return;
	}
	else
	{
		BinaryTreeDestory(&(*root)->left);
		BinaryTreeDestory(&(*root)->right);
		free(*root);
		*root = NULL;
	}
}

//二叉树节点个数
int BinaryTreeSize(BTNode* root)
{
	//这样写是将返回的变量保存下来,还可以直接用三目运算符
	//int count1 = 0;
	//int count2 = 0;
	//if (root == NULL)
	//{
	//	return 0;
	//}
	//else
	//{
	//	count1 = BinaryTreeSize(root->left) + 1;
	//	count2 = BinaryTreeSize(root->right) + 1;
	//}
	//return count1 + count2 - 1;

	//如果为空就返回0,否则返回左子树和右子树节点和再加上根节点
	return root == NULL ? 0 : BinaryTreeSize(root->left) + BinaryTreeSize(root->right) + 1;
}


//二叉树叶子节点个数
int BinaryLeafSize(BTNode* root)
{
	//如果根节点为空,返回0
	if (root == NULL)
		return 0;
	//如果左右子树均为空则为叶子节点,返回1
	if (root->left == NULL && root->right == NULL)
		return 1;
	//如果左右节点有一个不为空,则为分支节点,返回左右节点的个数之和
	return BinaryLeafSize(root->left) + BinaryLeafSize(root->right);
}

//二叉树第K层节点个数
int BinaryTreeLevelKSize(BTNode* root, int k)
{
	//找第K层的节点个数,可以用分治的思想,划分为多个子问题

	//如果K=1,说明到了第K层,为空返回0,不为空返回1
	if (k == 1)
		return root == NULL ? 0 : 1;
	//K不等于1,接着向下递归,返回左右节点第K-1层的节点个数
	return BinaryTreeLevelKSize(root->left, k - 1) + BinaryTreeLevelKSize(root->right, k - 1);
}

//二叉树查找值为x的节点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{
	//采用前序遍历,如果找到直接返回

	//如果根节点为空,直接返回
	if (root == NULL)
		return NULL;
	//如果根节点对应的值为x,返回节点地址
	if (root->data == x)
		return root;
	//走到这里说明根节点对应的值不是要找的
	//从左子树开始向下找
	BTNode* ans_left = BinaryTreeFind(root->left, x);
	//左子树没有找到再去找右子树
	if (ans_left == NULL)
		return BinaryTreeFind(root->right, x);
	else
		return ans_left;
}

//判断是否是完全二叉树
int BinaryTreeComplete(BTNode* root)
{
	//采用的是层序遍历的思想,但是有一点不同,当队头为空时检查队列,如果队列中还有不为空的元素就不是完全二叉树
	Quene q;
	QueneInit(&q);
	//先入最开始的根节点
	QuenePush(&q, root);

	//然后开始循环入队出队
	while (!QueneEmpty(&q))//判断空是判断的地址对应的是不是0
	{
		BTNode* front = QueneFront(&q);
		QueuePop(&q);

		//队头元素为空,如果是完全二叉树,队列应该为空,如果不是,队列中还有其他的元素
		//取队头元素取的也是元素的值
		if (front == NULL)
			break;
		//如果左节点或者右节点不为空则入队
		//就算入的是空指针,但是只是元素的值为空指针,元素是有地址的
		QuenePush(&q, front->left);
		QuenePush(&q, front->right);
	}
	while (!QueneEmpty(&q))
	{
		BTNode* front = QueneFront(&q);
		QueuePop(&q);
		if (front)
			return 0;
	}
	//用完要销毁队列
	QueneDestory(&q);
	return 1;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值