二叉树(3)——链式二叉树

定义节点的结构

typedef int BTDataType;//二叉树中存储的数据类型,这里以int为例

typedef struct BTNode
{
	BTDataType data;//当前节点要存储的数据
	struct BTNode* left;//存储当前节点的左孩子的地址
	struct BTNode* right;//存储当前节点的右孩子的地址
}BTNode;

二叉树的前序遍历

前序遍历又称为先根遍历,前序遍历的访问顺序是:根 左子树 右子树。
假设我们要对一棵二叉树进行前序遍历,则对于这棵二叉树及其所有的子树,我们都要按根、左子树、右子树的顺序依次访问。

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

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

例如:我们要对下面这棵二叉树进行前序遍历。
在这里插入图片描述

先访问根节点1,再访问1的左子树;
1的左子树以结点2为根,先访问2,再访问2的左子树;
2的左子树以3为根,先访问3;
3的左子树是空,返回;
3的左子树访问完成之后,访问3的右子树,3的右子树是空,返回;
此时3的左右子树都访问完成,返回;
此时2的左子树访问完成了,访问2的右子树,2的右子树是空,返回;
此时2的左右子树都访问完成了,返回;
此时1的左子树访问完成了,访问1的右子树;
1的右子树以节点4为根,先访问4,再访问4这棵树的左子树;
4的左子树以5为根,先访问5,再访问5这棵树的左子树;
5的左子树是空,返回;
此时5的左子树访问完了,访问5的右子树;
5的右子树是空,返回;
此时5的左右子树都访问完了,返回;
此时4的左子树访问完了,访问4这棵树的右子树;
4这棵树的右子树以6为根,先访问6,再访问6这棵树的左子树;
6的左子树是空,返回;
此时6的左子树访问完了,访问6的右子树;
6的右子树是空,返回;
此时6的左右子树都访问完了,返回;
此时4的左右子树都访问完了,返回;
此时1的左右子树都访问完了,返回。
前序遍历的结果是:1 2 3 NULL NULL NULL 4 5 NULL NULL 6 NULL NULL

前序遍历递归图解:
在这里插入图片描述

二叉树的中序遍历

中序遍历又称为中根遍历,中序遍历的访问顺序是:左子树 根 右子树。
假设我们要对一棵二叉树进行中序遍历,则对于这棵二叉树及其所有的子树,我们都要按左子树、根、右子树的顺序依次访问。

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

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

例如:我们要对下面这棵二叉树进行中序遍历。
在这里插入图片描述

遇到根节点1,先不访问,先访问1的左子树;
1的左子树以2为根,先不访问2,先访问2的左子树;
2的左子树以3为根,先不访问3,先访问3的左子树;
3的左子树是空,返回;
此时3的左子树访问完成,接着访问3,然后访问3的右子树;
3的右子树是空,返回;
此时3这棵树的左子树、根、右子树都依次访问完成了,返回;
此时2的左子树访问完成了,接着访问2,然后访问2的右子树;
2的右子树是空,返回;
此时2这棵树的左子树、根、右子树都依次访问完成了,返回;
此时1这棵树的左子树访问完成了,接着访问1,然后访问1的右子树;
1的右子树以4为根,先不访问4,先访问4的左子树;
4的左子树以5为根,先不访问5,先访问5的左子树;
5的左子树是空,返回;
此时5的左子树访问完成了,接着访问5,然后访问5的右子树;
5的右子树是空,返回;
此时5这棵树的左子树、根、右子树都依次访问完成了,返回;
此时4这棵树的左子树访问完成了,接着访问4,然后访问4的右子树;
4的右子树以6为根,先不访问6,先访问6的左子树;
6的左子树是空,返回;
此时6的左子树访问完成,接着访问6,然后访问6的右子树;
6的右子树是空,返回;
此时6这棵树的左子树、根、右子树都依次访问完成了,返回;
此时4这棵树的左子树、根、右子树都依次访问完成了,返回;
此时1这棵树的左子树、根、右子树都依次访问完成了,返回。
中序遍历的结果是:NULL 3 NULL 2 NULL 1 NULL 5 NULL 4 NULL 6 NULL

中序遍历递归图解:
在这里插入图片描述

二叉树的后序遍历

后序遍历又称为后根遍历,后序遍历的访问顺序是:左子树 右子树 根。
假设我们要对一棵二叉树进行后序遍历,则对于这棵二叉树及其所有的子树,我们都要按左子树、右子树、根的顺序依次访问。

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

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

例如:我们要对下面这棵二叉树进行后序遍历。
在这里插入图片描述

遇到根节点1,先不访问1,先访问1的左子树;
1的左子树以2为根,先不访问2,先访问2的左子树;
2的左子树以3为根,先不访问3,先访问3的左子树;
3的左子树是空,返回;
此时3的左子树访问完成了,接着访问3的右子树;
3的右子树是空,返回;
此时3的右子树访问完成了,接着访问3;
访问3之后,3这棵树的左子树、右子树、根都依次访问完成了,返回;
此时2的左子树访问完成了,接着访问2的右子树;
2的右子树是空,返回;
此时2的右子树访问完成了,接着访问2;
访问2之后,2这棵树的左子树、右子树、根都依次访问完成了,返回;
此时1这棵树的左子树访问完成了,接着访问1的右子树;
1的右子树以4为根,先不访问4,先访问4的左子树;
4的左子树以5为根,先不访问5,先访问5的左子树;
5的左子树是空,返回;
此时5的左子树访问完成了,接着访问5的右子树;
5的右子树是空,返回;
此时5的右子树访问完成了,接着访问5;
访问5之后,5这棵树的左子树、右子树、根都依次访问完成了,返回;
此时4这棵树的左子树访问完成了,接着访问4的右子树;
4的右子树以6为根,先不访问6,先访问6的左子树;
6的左子树是空,返回;
此时6的左子树访问完成了,接着访问6的右子树;
6的右子树是空,返回;
此时6的右子树访问完成了,接着访问6;
访问6之后,6这棵树的左子树、右子树、根都依次访问完成了,返回;
此时4这棵树的右子树访问完成了,接着访问4;
访问4之后,4这棵树的左子树、右子树、根都依次访问完成了,返回;
此时1这棵树的右子树访问完成了,接着访问1;
访问1之后,1这棵树的左子树、右子树、根都访问完成了,返回。
后序遍历的结果是:NULL NULL 3 NULL 2 NULL NULL 5 NULL NULL 6 4 1

后序遍历递归图解:
在这里插入图片描述

二叉树的层序遍历

设二叉树的根节点所在层数为1,层序遍历就是从所在二叉树的根节点出发,首先访问第一层的树根节点,然后从左到右访问第2层上的节点,接着是第三层的节点,以此类推,自上而下,自左至右逐层访问树的结点的过程就是层序遍历。
如下图二叉树层序遍历的结果是:1 2 4 3 5 6
在这里插入图片描述
层序遍历需要用到队列。层序遍历方法如下:
1.根节点先进队列;
2.保存队头数据,然后出队头数据;
3.依次进队头结点的左右孩子。
根节点进队列后,循环进行步骤二和步骤三,直到队列为空。若队列为空,则循环结束,层序遍历也就完成了。

void LevelOrder(BTNode* root)
{
	Queue q;//创建队列
	QueueInit(&q);//初始化队列
	if (root)//根节点进队列
		QueuePush(&q, root);

	while (!QueueEmpty(&q))//若队列不为空,就进入循环
	{
		BTNode* front = QueueFront(&q);//保存队头数据
		printf("%d ",front->data);
		QueuePop(&q);//删除队头数据

		if(front->left)//若队头左孩子不为空,则入队列
			QueuePush(&q, front->left);
		if (front->right)//若队头右孩子不为空,则入队列
			QueuePush(&q, front->right);
	}
	QueueDestroy(&q);//销毁队列
}

二叉树节点个数

求二叉树的结点个数,要求出根节点的左子树的节点个数、根节点的右子树的结点个数,再把根节点的左右子树的结点个数相加再加上根节点自己,就可以求出二叉树的节点个数。

int BTSize(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	return BTSize(root->left) + BTSize(root->right) + 1;
}

二叉树的高度

求二叉树的高度,只要求出根节点的左右子树的高度,然后将根节点的左右子树的高度进行比较,返回高度高的那个+1就可以了。(还要+1是因为根节点自己还有一层)。

int BTHeight(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	int left = BTHeight(root->left);//求根节点的左子树的高度
	int right = BTHeight(root->right);//求根节点的右子树的高度
	//比较左右子树的高度,返回高的那个高度+1
	if (left > right)
	{
		return left + 1;
	}
	else
	{
		return right + 1;
	}
}

二叉树第k层节点个数

二叉树第k层结点个数 = 左子树的第k-1层节点个数+右子树的第k-1层结点个数
随着函数递归的调用,k会递减到1,当k等于1的时候,就到了二叉树的第k层。当k等于1且结点不为空时,返回1。

int BTKLevel(BTNode* root, int k)
{
	assert(k > 0);//k不能小于等于0
	if (root == NULL)
	{
		return 0;
	}
	if (k == 1)
	{
		return 1;
	}
	//求左子树的第k-1层节点个数
	int left = BTKLevel(root->left, k - 1);
	//求右子树的第k-1层结点个数
	int right = BTKLevel(root->right, k - 1);
	return left + right;
}

二叉树查找值为x的节点

二叉树查找值为x的节点,若找到,则返回该节点的地址,若有多个值为x的节点,则返回第一个结点的地址。
二叉树查找值为x的节点的方法如下:
1.先查找根节点,若根节点符合,就返回根节点;
2.若根节点不符合,就去根节点的左子树找,找到则返回;
3.若根节点和根节点的左子树都找不到,就去根节点的右子树中找,找到则返回。若找不到,则说明这棵树(或子树)没有值为x的结点,返回NULL。

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

	if (root->data == x)
		return root;
	
	BTNode* lret = BTFind(root->left, x);
	if (lret)
		return lret;
	
	BTNode* rret = BTFind(root->right, x);
	if (rret)
		return rret;

	return NULL;
}

二叉树叶子节点个数

根据前面我们所学的概念,可以知道,叶子结点是既没有左孩子,也没有右孩子的结点。求二叉树的叶子结点个数,需要递归遍历整棵树。若结点为NULL,则返回0。若结点满足是叶子结点,则返回1。若结点不为空也不是叶子结点,则继续去左右子树中找叶子结点,并将左右子树中的叶子结点个数相加。

int BTLeafSize(BTNode* root)
{
	if (root == NULL)//结点为空,返回0
	{
		return 0;
	}
	//满足条件,说明当前节点是叶子结点
	if (root->left == NULL && root->right == NULL)
	{
		return 1;
	}
	//找不到叶子结点就继续去左子树和右子树中找
	return BTLeafSize(root->left) + BTLeafSize(root->right);
}

判断二叉树是否是完全二叉树

判断二叉树是否是完全二叉树,需要用到完全二叉树的一个性质:完全二叉树的结点是连续的。
若一棵二叉树是完全二叉树,则这棵二叉树满足以下两点:
1.该二叉树除最后一层外,其他层必须是满的;
2.该二叉树的最后一层从左到右必须是连续的。
根据以上两点可知,完全二叉树的节点是连续的。若一棵二叉树的空节点后面还有结点,则说明不是完全二叉树;若一棵二叉树的空节点后面全是空节点,则说明是完全二叉树。
判断二叉树是否是完全二叉树,也需要用到队列,方法如下:
1.按层序遍历走;
2.若队头数据是空节点,则跳出循环;
3.遍历队列中剩下的数据,若有非空结点,则返回false;若都是空节点,则返回true。

bool BTComplete(BTNode* root)
{
	Queue q;//创建队列
	QueueInit(&q);//初始化队列
	if (root)//根节点入队列
		QueuePush(&q, root);

	while (!QueueEmpty(&q))
	{
		BTNode* front = QueueFront(&q);//取队头数据
		QueuePop(&q);//出队头数据

		if (front == NULL)//队头数据是空节点,则跳出循环
		{
			break;
		}
		else//若队头数据是非空结点,则依次入队头的左右孩子
		{
			QueuePush(&q,front->left);
			QueuePush(&q,front->right);
		}
	}
    //判断是否是完全二叉树
	while (!QueueEmpty(&q))
	{
		BTNode* front = QueueFront(&q);//取队头数据
		QueuePop(&q);//出队头数据
        //若有一个不是空节点,则该二叉树不是完全二叉树,返回false
		if (front != NULL)
		{
			QueueDestroy(&q);
			return false;
		}
	}
	QueueDestroy(&q);
	//若程序可以走到这一步,说明该二叉树是完全二叉树,返回true
	return true;
}

二叉树的销毁

销毁二叉树需要用后序遍历,因为如果用前序遍历或者中序遍历销毁二叉树,那么根结点的销毁就会在销毁其孩子结点之前,根节点销毁后,就找不到它的孩子节点,就无法销毁它的孩子结点,因此用后序遍历来销毁二叉树是最合适的。

void BTDestory(BTNode* root)
{
	if (root == NULL)
		return;
	BTDestory(root->left);
	BTDestory(root->right);
	free(root);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值