二叉树小结

树是一种数据结构,其表现形式有很多种,可以是顺序表,也可以是链表。然而树中应用最多的那便是二叉树了,下面我就来总结一下二叉树。

一.  二叉树-堆

首先是二叉树-堆。堆是一种完全二叉树,分为大堆和小堆,大堆是父节点都大于子节点的二叉树,小堆是父节点小于子节点的二叉树。建堆的过程我们需要用向上调整法。以下我们都以大堆为例:

void AdjustUp(int* a,int child)
{
	int parent = (child - 1) / 2;
	while (child > 0)
	{
		if (a[child] > a[parent])
			swap(&a[child], &a[parent]);

		child = parent;
		parent = (child - 1) / 2;
	}
}

这就是向上调整的过程,其中父节点的下标(parent)一定是(chlid-1)/ 2。相反,左子节点的下标是parent*2+1,右节点的下标是parent*2+2,这个会在下下面用到。

下面是建堆的结果:

前一行是一个无序数组,后一行是建成的大堆。当然,遍历建堆的过程我就省略了,主要是介绍向上调整法。显然我们这样就已经建成了一个大堆。


建堆看起来很容易,那么删除堆元素呢?为了考虑堆的实用性,我们删除堆的元素,一般都是删除堆顶元素。所以我们就要采用向下调整法。其思路与向上调整法差不多,只不过是将最后一个元素与第一个元素调换,再将数组长度-1,然后堆顶元素向下调整。核心代码如下:

void AdjustDwon(int* a, int n, int root)
{
	int child = root * 2 + 1;

	while (child < n)
	{
		if (a[child] < a[child + 1]&&child+1<n)
			child++;

		if (a[root] < a[child])
			swap(&a[child], &a[root]);

		root = child;
		child = root * 2 + 1;
	}
}

这就可以实现删除堆顶元素了。

其实掌握了这两个算法,就可以实现堆排序了。但是心急吃不了热豆腐,我们放在排序讲解。

二.  链式二叉树

由上面的图片可以看出,二叉树用链式表示会更加形象,在用链来连接二叉树的各个节点之后,我们可以灵活的解一下二叉树的题,以及对于遍历二叉树有着不错的效果。

我们定义,root是头节点,left是左子节点,right是右子节点。接下来我们都会采用这种形式进行讲解。

// 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树
BTNode* BinaryTreeCreate(BTDataType* a, int n, int* pi);
// 二叉树销毁
void BinaryTreeDestory(BTNode** root);
// 二叉树节点个数
int BinaryTreeSize(BTNode* root);
// 二叉树叶子节点个数
int BinaryTreeLeafSize(BTNode* root);
// 二叉树第k层节点个数
int BinaryTreeLevelKSize(BTNode* root, int k);
// 二叉树查找值为x的节点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x);
// 二叉树前序遍历 
void BinaryTreePrevOrder(BTNode* root);
// 二叉树中序遍历
void BinaryTreeInOrder(BTNode* root);
// 二叉树后序遍历
void BinaryTreePostOrder(BTNode* root);
// 层序遍历
void BinaryTreeLevelOrder(BTNode* root);

上面是二叉树的一些基本问题。我们逐一来看。

1.前序,中序,后序

讲到链式二叉树就不得不讲二叉树的前序中序后序遍历了。前序遍历的顺序是,先root再left后right,中序是先left再root后right,后序是先left再right后root。其实可以总结为访问根的顺序不同。

接下来我们有一个二叉树,我们来看看三种遍历的结果。

假设我们的二叉树是“ABD##E#H##CF##G##”,其中“#”代表NULL;

前中后序遍历的结果就是这三行。但是如何实现呢,在二叉树中,由于连接性很强,树与树的结构也相似,所以我们通常用递归实现,代码也不复杂。如下:

// 二叉树前序遍历 
void BinaryTreePrevOrder(BTNode* root)
{
	if (root == NULL)
		return;

	printf("%c ", root->data);
	BinaryTreePrevOrder(root->left);
	BinaryTreePrevOrder(root->right);
}

// 二叉树中序遍历
void BinaryTreeInOrder(BTNode* root)
{
	if (root == NULL)
		return;

	BinaryTreeInOrder(root->left);
	printf("%c ", root->data);
	BinaryTreeInOrder(root->right);
}

// 二叉树后序遍历
void BinaryTreePostOrder(BTNode* root)
{
	if (root == NULL)
		return;

	BinaryTreePostOrder(root->left);
	BinaryTreePostOrder(root->right);
	printf("%c ", root->data);
}

这就是三种遍历,代码看着虽然简短,但是其内部的递归还需大家仔细理解。

2.遍历数组构建二叉树

第一问是让我们通过前序遍历来创造二叉树,我们就用递归来实现遍历数组创造二叉树。

BTNode* BinaryTreeCreate(BTDataType* a, int* pi)
{
	if (a[*pi])
	{
		if (a[*pi] == '#')
		{
			++(*pi);
			return NULL;
		}

		else
		{
			BTNode* root = (BTNode*)malloc(sizeof(BTNode));
			root->data = a[*pi];
			++(*pi);
			root->left = BinaryTreeCreate(a, pi);
			root->right = BinaryTreeCreate(a, pi);

			return root;
		}
	}
	return NULL;
}

这就是遍历过程,如果有些晦涩难懂的话,可以画一画递归图,这样就会很好理解。大家也可以尝试中序,后序遍历的代码,这里我就不做过多讲解。

3.oj题

可以看到,在上面的列表里面,有三个oj题,这里我就对第一个进行分析,有兴趣的小伙伴可以试试第二个第三个,都不难,主要是锻炼我们的递归理解。

// 二叉树节点个数
int BinaryTreeSize(BTNode* root);

题目要求我们求二叉树的节点个数,那我们想,是不是可以通过前序遍历,当他遇到了非空节点,我们返回1,遇到空节点,我们就返回0,再通过递归层层返回相加,是不是就可以得到总结点个数了。思路构建好了,我们就可以实战看看代码。

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

	return BinaryTreeSize(root->left) + BinaryTreeSize(root->right) + 1;
}

这就是这道题的实现,我们看看结果,还是以上面的树为例:

可以见得并没有错。

4.层序遍历

为什么把层序遍历放到最后来讲呢,其实层序遍历用的是队列来实现的,并不是链式二叉树,这是因为它也是二叉树的一种遍历形式。所以放在了一起。

这个我们简单看看,思路是,每当二叉树的节点出队列实,都会带入两个子节点。这样我们出的数据的排序,就是二叉树的层序遍历。代码如下:

// 层序遍历
void BinaryTreeLevelOrder(BTNode* root)
{
	Queue q;
	QueueInit(&q);

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

	while (!QueueEmpty(&q))
	{

		BTNode* ele= QueueFront(&q);
		printf("%c ", ele->data);


		if(ele->left)
			QueuePush(&q,ele->left);

		if(ele->right)
			QueuePush(&q,ele->right);

		QueuePop(&q);
	}

	QueueDestroy(&q);
}

可以看到层序遍历与之前三个都不一样,这里没有链式,也就不存在递归一说。其中QueueInit等函数都是队列中的函数,知道是什么作用即可。

当然,二叉树的销毁没有讲,但是大家应该很快可以想到,可以用后序遍历来销毁,因为头节点必须是最后销毁的,不然就找不到子树了。

三:总结

二叉树其实并不难,只要理解了递归结构,那就大差不差了,此外一些选择题的方法,大家可以找找自己学校的卷子,看看解题方法即可。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值