二叉树的链式结构和遍历(下)

又见面了,小伙伴们。今天我们继续来学习二叉树,今天的内容相对来说比较容易理解,前提是需要你们自己动手画图才会好理解。眼过千遍不如手过一遍。所以小伙伴们要多动手哦。直接开始今天的学习吧

1.二叉树链式结构的实现

1.1 前置说明
在学习二叉树的基本操作前,需先要创建一棵二叉树,然后才能学习其相关的基本操作。由于现在我们对二叉树结构掌握还不够深入,为了降低学习成本,此处手动快速创建一棵简单的二叉树,快速进入二叉树操作学习,等二叉树结构了解的差不多时,我们反过头再来研究二叉树真正的创建方式。
typedef int BTDataType;
typedef struct BinaryTreeNode
{
	BTDataType data;
	struct BinaryTreeNode* left;
	struct BinaryTreeNode* right;
}BTNode;

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

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

	return node;
}

BTNode* CreatBinaryTree()
{
	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;
	node5->left = node7;

	return node1;
}
注意:上述代码并不是创建二叉树的方式,真正创建二叉树方式后序详解重点讲解。
我们前面学过二叉树的概念是
1. 空树
2. 非空:根节点,根节点的左子树、根节点的右子树组成的
从概念中可以看出,二叉树定义是递归式的,因此后序基本操作中基本都是按照该概念实现的。
任何一个二叉树都可以看成有3个部分组成:根,左子树,右子树。每个子树又可以分成上述3个部分,一直分到它们的左右子树都为空时停止。
1.2 二叉树的遍历
1.2.1前序、中序以及后序遍历
学习二叉树结构,最简单的方式就是遍历。所谓 二叉树遍历 (Traversal) 是按照某种特定的规则,依次对二叉 树中的节点进行相应的操作,并且每个节点只操作一次 。访问结点所做的操作依赖于具体的应用问题。 遍历是二叉树上最重要的运算之一,也是二叉树上进行其它运算的基础。
按照规则,二叉树的遍历有: 前序 / 中序 / 后序的递归结构遍历
1. 前序遍历 (Preorder Traversal 亦称先序遍历 )—— 访问根结点的操作发生在遍历其左右子树之前。
2. 中序遍历 (Inorder Traversal)—— 访问根结点的操作发生在遍历其左右子树之中(间)。
3. 后序遍历 (Postorder Traversal)—— 访问根结点的操作发生在遍历其左右子树之后。
由于被访问的结点必是某子树的根, 所以 N(Node )、 L(Left subtree )和 R(Right subtree )又可解释为 根、根的左子树和根的右子树 NLR LNR LRN 分别又称为先根遍历、中根遍历和后根遍历。
下面来看一个二叉树,小伙伴们可以先试试它的前序,中序,后序都是什么。
第一次写这个的时候小伙伴们可能还搞不懂,就是直到左右子树都是空的时候才会停止,只要还能继续往下走,就要一直继续走。记住这句话应该就没有问题了。建议一定要自己画图才能理解其中的意思。
来看代码吧(如果自己想在电脑上试一下的话,要把开头给的代码补上才完整)
//前序
void PrevOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("N ");
		return;
	}

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

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

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

//后序
void PostOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("N ");
		return;
	}
	PostOrder(root->left);
	PostOrder(root->right);
	printf("%d ", root->data);
}

int main()
{
	BTNode* root = CreatBinaryTree();
	PrevOrder(root);
	printf("\n");

	InOrder(root);
	printf("\n");

	PostOrder(root);
	printf("\n");
}

前序遍历结果: 1 2 3 4 5 6
中序遍历结果: 3 2 1 5 4 6
后序遍历结果: 3 2 5 6 4 1

总体的思想就是递归思想,我会画一个前序的递归图帮助小伙伴们更好的理解,其它的遍历小伙伴们可以自己试一下哦。

1.2.2 层序遍历
层序遍历 :除了先序遍历、中序遍历、后序遍历外,还可以对二叉树进行层序遍历。设二叉树的根节点所在层数为1 ,层序遍历就是从所在二叉树的根节点出发,首先访问第一层的树根节点,然后从左到右访问第 2 层 上的节点,接着是第三层的节点,以此类推,自上而下,自左至右逐层访问树的结点的过程就是层序遍历。(不能用递归来表示)
代码实现:
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);
	}
	printf("\n");
	QueueDestroy(&q);
}

如果想添加以前写的队列的代码的话,可以把队列文件的.c和.h 文件复制然后添加到二叉树文件的里面,如图所示

添加完之后需要对头文件做一些修改

2.求二叉树的各种节点问题

2.1计算节点个数

计算节点个数有2个方法:

方法1:把size定义成全局变量,然后遍历整棵数,如果不空的话,size++

int size = 0;
void BTreeSize(BTNode* root)
{
	if (root == NULL)
		return 0;

	++size;

	BTreeSize(root->left);
	BTreeSize(root->right);
}
BTreeSize(root);//方法1
printf("BTreeSize:%d\n", size);

方法2:分治法。左子树+右子树+1(1代指的是根)

举个例子,假如学校校长想要统计学生个数,那么是不是校长先给院长下达命令,然后院长在给辅导员下达命令,最后辅导员再给各班班长下达命令,让班长统计人数,然后依次上报,最后校长就知道学生有多少人了。递归思想就是这样

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

	return root==NULL?0: BTreeSize(root->left) + BTreeSize(root->right) + 1; 
}
printf("BTreeLeafSize:%d\n", BTreeLeafSize(root));
2.2 计算叶子节点个数

这个我们应该很好想到,就是当左子树和右子树为空的时候就是到叶子节点了。递归的终止条件有2个。当树为空时返回0,当左子树和右子树都为空的时候返回1

int BTreeLeafSize(BTNode* root)
{
	if (root == NULL)
		return 0;
	if (root->left == NULL && root->right == NULL)
		return 1;
	return BTreeLeafSize(root->left) + BTreeLeafSize(root->right);
}
printf("BTreeLeafSize:%d\n", BTreeLeafSize(root));
2.3 计算二叉树高度

二叉树的高度由最长路径决定的,哪个路线最长,树的高度就是多少

先定义2个变量分别计算左右子树的长度,哪个树长树的高度就是它。

int BTreeHight(BTNode* root)
{
	if (root == NULL)
		return 0;
	int leftHight = BTreeHight(root->left);
	int rightHight = BTreeHight(root->right);
	return leftHight > rightHight ? BTreeHight(root->left) +1: BTreeHight(root->right)+1;

}

当然也可以这样写,不过这种写法的效率非常低,当数据量非常大的时候就会浪费很大的时间

int BTreeHight(BTNode* root)
{
	if (root == NULL)
		return 0;
	return BTreeHight(root->left) > BTreeHight(root->right) 
		? BTreeHight(root->left) + 1 
		: BTreeHight(root->right) + 1;
}
2.4 计算第K层节点个数

可以转换成左子树的第k-1层和右子树的第k-1层。递归的结束条件是k==1且节点不为空。

int BTreeLevelKSize(BTNode* root, int k)
{
	assert(k);
	if (root == NULL)
		return 0;
	if (k == 1)
		return 1;
	return BTreeLevelKSize(root->left, k - 1) + BTreeLevelKSize(root->right, k - 1);
}
	printf("BTreeLevelKSize:%d\n", BTreeLevelKSize(root,3));

2.5 查找值为x的节点

我先展示一下经典的错位写法,当然我开始也是这样想的

BTNode* BTreeFind(BTNode* root, BTDataType x)//错误写法,找到还要返回上一层
{
	if (root == NULL)
		return NULL;
	if (root->data == x)
		return root;
	BTreeFind(root->left, x);
	BTreeFind(root->right, x);

}

错误的原因就是找到值的话不是直接退出递归,而是要返回上一层,一直到开头的地方。其实只要自己画一个递归展开图就知道是怎么回事了。讲递归的时候我们就知道是有去有回,不是直接结束

正确思路就是定义变量要记录找到的值,然后直接返回就行。

BTNode* BTreeFind(BTNode* root, BTDataType x)
{
	if (root == NULL)
		return NULL;
	if (root->data == x)
		return root;

	BTNode* ret1 = BTreeFind(root->left, x);
	if (ret1)
		return ret1;
	BTNode* ret2 = BTreeFind(root->right, x);
	if (ret2)
		return ret2;

	return NULL;
}
2.6 判断是否为完全二叉树

这时候就会有人想可不可以用节点个数来判断是不是完全二叉树,只要在范围之内就是完全二叉树,那么这种想法是错误的,这个想法只能用来判断是不是满二叉树。我们已经知道完全二叉树的特征是最后一层可以不满,但叶子节点必须是连续的,所以说用节点个数来判断是行不通的。

具体实现如下:我们可以通过层序遍历来判断,只要队列不为空时,就继续往下走

代码如下:

bool BTreeComplete(BTNode* root)
{
	Queue q;
	QueueInit(&q);

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

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

		//遇到空就跳出
		if (front == NULL)
			break;

		QueuePush(&q, front->left);
		QueuePush(&q, front->right);
	}
	//检查后面的节点有没有非空
	//有非空,就不是完全二叉树
	while (!QueueEmpty(&q))
	{
		BTNode* front = QueueFront(&q);
		QueuePop(&q);

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

	QueueDestroy(&q);
	return true;
	
}

 好了,小伙伴们,今天的学习就到这里,下一节我们来练习一些二叉树有关的习题,关于二叉树的初级部分就学完了。高级部分要等到我们学完C++后才能更好的理解。感谢大家的阅读。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值