数据结构初阶——二叉树(Part2)

今天我们来继续谈一谈二叉树~~~

目录

一、关于递归

二、二叉树遍历

1、前序遍历(PreOrder)

2、中序遍历(InOrder)

3、后序遍历(PostOrder)

三、统计二叉树的节点数

1、思路

2、代码

四、计算二叉树的深(高)度

1、思路

2、代码

五、二叉树叶子节点个数

1、思路

2、代码

六、二叉树第k层节点个数

1、思路

2、代码

七、二叉树查找值为x的节点

1、思路

2、代码

3、递归展开图 

八、二叉树的创建 

(1)思路

(2)代码

九、二叉树的销毁

(1)思路

(2)代码


一、关于递归

在谈二叉树之前,我们先谈一谈递归。

递归就是函数自己调用自己。但特殊的是,函数每被调用一次,系统就会为它开启一个栈帧,直到函数返回时,系统才会把栈帧收回。因此,对于递归,还有另一层理解:递归的栈帧可以用来保存一些数据和这个栈帧的地址,直到栈帧销毁。

523357efd35549c4b627b9c7a831da22.png

相关的例子会在下文讲解~~~

二、二叉树遍历

1、前序遍历(PreOrder)

前序遍历的访问顺序是:根  ->  左子树  ->  右子树

e209bc0485234988881e600414862e94.png

因此,代码应该是这样的:

void PreOrder(BTNode* root) /* 根 左子树 右子树 */
{
	if (root == NULL)
		return;

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

2、中序遍历(InOrder)

中序遍历的访问顺序是:左子树 -> 根 -> 右子树

代码:

void InOrder(BTNode* root) /* 左子树 根 右子树 */
{
	if (root == NULL)
		return;

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

3、后序遍历(PostOrder)

后序遍历的访问顺序是:左子树 -> 右子树 -> 根

代码:

void PostOrder(BTNode* root) /* 左子树 右子树 根 */
{
	if (root == NULL)
		return;

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

三、统计二叉树的节点数

1、思路

为了解决这个问题,我在这里先引用一个比喻:

在大学里,有校长、院长、班主任等等。而他们的关系就像这张图:
ad1ac92f649c4c4891501db333b8e0d2.png

 这时候校长要统计全校的人数。显然,校长是不可能自己亲自去宿舍一个一个数人的,而是叫两个院长去统计他们学院里的人数,最后再加上自己就行了。而同理,院长也不会自己亲自去数人数,也是叫辅导员去统计系里的人数,然后再加上自己....到最后,通过班长数的人数再逐步汇总到辅导员,再到院长,最后到校长。

因此,面对这个问题,我们可以先统计左右子树的节点数,最后再加上根节点自己。

2、代码

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

	return BinaryTreeSize(root->_left) + BinaryTreeSize(root->_right) + 1;
}

四、计算二叉树的深(高)度

1、思路

和统计节点数一样,这里我也可以打个比喻:校长要选出学校里身高最高的人。那怎么选呢,校长总不会亲自逐个逐个量学生的身高然后比较吧,因此校长一般会叫院长去统计他们院里身高最高的同学,然后再在这些同学里面选出最高的那个。

因此,在代码上,我们可以先统计左右子树的高度,然后再比较它们的高度,最后j加上根节点自己的高度返回就行了。

2、代码

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

这段代码大家肯定很快就写出来了,但这样就对了吗?如果你是个细心的人,就不难发现,在比较高度时,我们的代码在比较和返回时各递归了一次,这无疑会增加机器的开销。因此,我们可以用两个变量分别储存这两棵子树高度的值,然后再比较,再返回,这样就可以只递归一次了。

所以,正确的代码应该这样写: 

int BinaryTreeHeight(BTNode *root)
{
    if (root == NULL)
        return 0;
    
    int leftHeight = BinaryTreeHeight(root->left);
    int rightHeight = BinaryTreeHeight(root->right);

    if (leftHeight > rightHeight)
        return leftHeight + 1;
    else
        return rightHeight + 1;
}

五、二叉树叶子节点个数

1、思路

还是用校长那个例子来打比方,这个问题就等同于校长要统计学生人数。而怎么操作呢,其实不难想出,校长可以叫院长去统计他们自己学院的学生人数,最后再汇总起来就好了。

以二叉树的角度来说,就是通过从根节点开始向左右子树递归,当该节点为叶子节点时,就返回1,如果为空节点,返回0。

2、代码

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

	if (root != NULL && root->_left == NULL && root->_right == NULL)
		return 1;

	return BinaryTreeLeafSize(root->_left) + BinaryTreeLeafSize(root->_right);
}

六、二叉树第k层节点个数

1、思路

对于这个问题,还是用校长那个例子来讲,就是等同于校长要统计辅导员的人数。而怎么解决呢?同样,校长应该找院长统计他们学院里辅导员的人数,最后再汇总就行了。但要注意的是,因为数的是辅导员的人数,因此就不关班长的事了。

从二叉树的角度来看,可以从根节点分别向左右子树递归,然后把它们加在一起就行了。但值得注意的是:递归的逻辑是第k层的节点数等于左右孩子的k-1层。

2、代码

int BinaryTreeLevelKSize(BTNode* root, int k)
{
	if (root == NULL)
		return 0;

	if (k == 1)
		return 1;

	return BinaryTreeLevelKSize(root->_left, k - 1) + BinaryTreeLevelKSize(root->_right, k - 1);
}

七、二叉树查找值为x的节点

1、思路

以校长的角度来看,就是在学校里找人。那么怎么找呢?同样,他一定会叫院长去找,然后院长叫辅导员去找...找到了就返回这个人所在的位置,找不到就返回“不在”。

从二叉树的角度看,先比较根节点的值,如果一样就返回它的地址,不一样的话就叫左子树找,如果左子树还找不到就去右子树找,如果再找不到就返回空。

2、代码

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

	if (root->_data == x)
		return root;

	BTNode* leftRet = BinaryTreeFind(root->_left, x);
	if (leftRet != NULL)
	{
		return leftRet;
	}

	BTNode* rightRet = BinaryTreeFind(root->_right, x);
	if (rightRet != NULL)
	{
		return rightRet;
	}

	return NULL;
}

3、递归展开图 

89a2c2c7ccfb4f02ab952b9034859304.png

八、二叉树的创建 

(1)思路

这里,我们的大体思路是先malloc一个根节点,然后用递归的方式分别malloc一个左子树和右子树,最后让根节点的 left 和 right 指针分别指向左右子树就行了。

但要注意的是:我们需要 i 变量来遍历数组。但如果递归的话,对于每个栈帧来说 i 的值是无法被记住的,一旦这个栈帧被销毁,i 的值就会变成上一层栈帧中 i 的值。因此,我们在传 i 时要传 i 的地址,不要传值。

(2)代码

BTNode* BuyTreeNode(BTDataType a)
{
	BTNode* newNode = (BTNode*)malloc(sizeof(BTNode));
	assert(newNode != NULL);
	newNode->_data = a;
	newNode->_left = newNode->_right = NULL;

	return newNode;
}

// 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树
BTNode* BinaryTreeCreate(BTDataType* a, int n, int* pi)
{
	if (*pi == n)
		return NULL;

	if (a[*pi] == '#')
	{
		(*pi)++;
		return NULL;
	}

	BTNode* root = BuyTreeNode(a[(*pi)++]);

	BTNode* leftTree = BinaryTreeCreate(a, n, pi);
	BTNode* rightTree = BinaryTreeCreate(a, n, pi);

	root->_left = leftTree;
	root->_right = rightTree;

	return root;

}

九、二叉树的销毁

(1)思路

从整体上看,我们可以先把左右子树销毁,最后再把根节点销毁。当然,如果递归是遇到空树,直接返回就行了。

(2)代码

void BinaryTreeDestory(BTNode** root)
{
	if (*root == NULL)
		return;

	BinaryTreeDestory(&((*root)->_left));
	BinaryTreeDestory(&((*root)->_right));

	free(*root);
	*root = NULL;
}

好了,这就是二叉树的基本操作了。

1. 什么是二叉树二叉树是一种树形结构,其中每个节点最多有两个子节点。一个节点的左子节点比该节点小,右子节点比该节点大。二叉树通常用于搜索和排序。 2. 二叉树的遍历方法有哪些? 二叉树的遍历方法包括前序遍历、中序遍历和后序遍历。前序遍历是从根节点开始遍历,先访问根节点,再访问左子树,最后访问右子树。中序遍历是从根节点开始遍历,先访问左子树,再访问根节点,最后访问右子树。后序遍历是从根节点开始遍历,先访问左子树,再访问右子树,最后访问根节点。 3. 二叉树的查找方法有哪些? 二叉树的查找方法包括递归查找和非递归查找。递归查找是从根节点开始查找,如果当前节点的值等于要查找的值,则返回当前节点。如果要查找的值比当前节点小,则继续在左子树中查找;如果要查找的值比当前节点大,则继续在右子树中查找。非递归查找可以使用栈或队列实现,从根节点开始,每次将当前节点的左右子节点入栈/队列,直到找到要查找的值或者栈/队列为空。 4. 二叉树的插入与删除操作如何实现? 二叉树的插入操作是将要插入的节点与当前节点的值进行比较,如果小于当前节点的值,则继续在左子树中插入;如果大于当前节点的值,则继续在右子树中插入。当找到一个空节点时,就将要插入的节点作为该空节点的子节点。删除操作需要分为三种情况:删除叶子节点、删除只有一个子节点的节点和删除有两个子节点的节点。删除叶子节点很简单,只需要将其父节点的对应子节点置为空即可。删除只有一个子节点的节点,需要将其子节点替换为该节点的位置。删除有两个子节点的节点,则可以找到该节点的后继节点(即右子树中最小的节点),将其替换为该节点,然后删除后继节点。 5. 什么是平衡二叉树? 平衡二叉树是一种特殊的二叉树,它保证左右子树的高度差不超过1。这种平衡可以确保二叉树的查找、插入和删除操作的时间复杂度都是O(logn)。常见的平衡二叉树包括红黑树和AVL树。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

胎粉仔

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

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

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

打赏作者

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

抵扣说明:

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

余额充值