自学内容网 自学内容网

二叉树的各种操作补充


我们任然沿用二叉树的基本信息:

typedef char BTDataType;
typedef struct BinaryTreeNode {
    BTDataType _data;
    struct BinaryTreeNode* _left;
    struct BinaryTreeNode* _right;
} BTNode;

求二叉树的结点数

我们可以在遍历二叉树的同时顺便统计结点个数。

  1. 遍历+全局变量记录

所有递归生成的函数都能调用通用的计数变量来记录。

// 遍历计数
int size = 0;
void BTreeSize(BTNode* root)
{
if (root == NULL)
return;
++size;
BTreeSize(root->_left);
BTreeSize(root->_right);
}

这么做有它的局限性:

  • 全局变量所有函数都能调用,因此只能在约定上约束全局变量的使用。
  • 在现实中有时会将函数定义和主函数(或其他调用定义的函数的函数)分离,此时需要通过关键字extern对外部全局变量进行声明。

我们除了局部变量,还可以通过上传局部变量的地址来记录。但除了这些我们还有更好的办法。

  1. 遍历+搜索

我们直接让函数带一个返回值,这样可以免去全局变量的局限性。这里用到分治的思想。

二叉树的很多操作都用到了分治的思想。

分治即分而治之。举个例子:校长统计人数,会问离自己最近的单位也就是下一级的单位,下一级的单位再问自己下一级的单位,如此往下划分,直到最低一级的单位报到等着被统计即可。

所以分治也可以理解为求助”别人“。但分治要注意:若只根据结果进行判断但不记录结果,则算法的时间复杂度会进一步增加。例如后面的求树高

// 二叉树节点个数
//搜索
int BinaryTreeSize(BTNode* root) {
if (root == NULL) {
return 0;
}
//每个子树负责上传自己的子结点数但不负责保存
return BinaryTreeSize(root->_left) + BinaryTreeSize(root->_right) + 1;
}

这个函数还能进一步简化:

int BinaryTreeSize(BTNode* root) {
return root == NULL ? 0 : BTreeSizeRecursion(root->_left)
+ BTreeSizeRecursion(root->_right) + 1;
}

求二叉树的叶结点数

叶结点没有子树,因此在递归遍历到叶结点时没必要继续递归下去,直接将这个叶结点上传统计。

// 二叉树叶子节点个数
int BinaryTreeLeafSize(BTNode* root) {
if (root == NULL) {
return 0;
}
if (root->_left == NULL && root->_right == NULL)//无度结点即为叶结点
return 1;
//每个子树分别记录自己的左、右子树的叶结点数
return BinaryTreeLeafSize(root->_left) + BinaryTreeLeafSize(root->_right);
}

因为二叉树在统计结点数时几乎不会二次遍历曾经遍历过的结点,所以记录子树结点数就没有很大的必要。

求二叉树的高度

还是前序遍历。但是对当前结点的处理是在子树的基础上加上自己的一份。

// 求二叉树的高度简化(但不知道哪边的树高)
int BTreeHeight(BTNode* root) {
if (root == NULL)
return 0;

return BTreeHeight(root->_left) > BTreeHeight(root->_right)
? BTreeHeight(root->_left) + 1 :
BTreeHeight(root->_right) + 1;
}

这个代码就有一个很严重的问题:重复遍历。例如表达式中存在两次BTreeHeight(root->left)即存在两次遍历左子树的问题。

比如这个OJ题:104. 二叉树的最大深度 - 力扣(LeetCode) ,用这个代码上去提交铁定超时。
请添加图片描述

解决方法是剔除不必要的重复遍历。此时我们可以记录每个子树的子树高度,然后将自己的子树高度中较大者返回给父结点。最后根结点返回的树高即为整个树的最大树高。

// 求二叉树的高度(规定一个结点的二叉树树高为1)
int BTreeHeight(BTNode* root) {
if (root == NULL)
return 0;

int leftHeight = BTreeHeight(root->_left);
int rightHeight = BTreeHeight(root->_right);

return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
}

这种处理方式还有另外的称呼:记忆化搜索。以后有机会再细谈。

求二叉树的第k层结点数

没错,还是前序遍历。当判断出当前结点即为第k层结点时,即可直接返回并统计。

这和统计叶结点一样都是统计具有某种特征的结点数。

// 二叉树第k层结点个数
int BTreeLevelKSize(BTNode* root, int k) {
if (root == NULL)
return 0;

if (k <= 1)//当确认当前结点为第k层的结点时直接返回并计数。
return 1;

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

查找指定结点

在前序遍历树root的过程中,若发现结点的数据是我们要找的x时将结点返回。

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

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

BTNode* ret1 = BinaryTreeFind(root->_left, x);
if (ret1)
return ret1;//需要思考递归时如何将结果带回主函数

BTNode* ret2 = BinaryTreeFind(root->_right, x);
if (ret2)
return ret2;

return NULL;
}

关于查找二叉树中的指定结点,有很大的学问。比如二叉搜索树(一种左子树根结点的数据小于父结点,右子树根结点的数据大于父结点的二叉树),以后有机会的话再整理。

层序遍历

比如对于这个树:
请添加图片描述

我们想得到这样的输出结果:1 2 4 3 5 6

想要得到这种遍历结果,我们可以通过队列实现:

  1. 根结点1入队。
  2. 处理队首结点。
  3. 队首结点的子结点入队。如果结点存在的话。
  4. 弹出队首。
  5. 重复2到4,直到队列为空。

队列用到了这里的

参考程序:

// 层序遍历
void BinaryTreeLevelOrder(BTNode* root) {
Queue q;
QueueInit(&q);
QueuePush(&q, root);//根结点入队
while (QueueSize(&q)) {
BTNode* t = QueueFront(&q);
printf("%c ", t->_data);
//子结点入队
if (t->_left)
QueuePush(&q, t->_left);
if (t->_right)
QueuePush(&q, t->_right);
QueuePop(&q);
}
}

这种层序遍历有一种固定的称呼:广度优先搜索(Breadth First Search,简称BFS )。这是个很重要的算法,在以后会总结这个算法的应用。

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

完全二叉树和满二叉树上对应的结点一一对应。所以可以在层序遍历的基础上进行改造:空结点也入队。层序遍历枚举到空结点时停止遍历,最后检查队列内还剩的结点是否全是空结点,不是的话则该二叉树不是完全二叉树。

参考程序:

// 判断二叉树是否是完全二叉树
int BinaryTreeComplete(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;
}

原文地址:https://blog.csdn.net/m0_73693552/article/details/143696486

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!