二叉树的各种操作补充

news2024/11/27 11:15:55

二叉树的各种操作补充

  • 求二叉树的结点数
  • 求二叉树的叶结点数
  • 求二叉树的高度
  • 求二叉树的第k层结点数
  • 查找指定结点
  • 层序遍历
  • 判断二叉树是否是完全二叉树

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

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;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2238458.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Go语言的常用内置函数

文章目录 一、Strings包字符串处理包定义Strings包的基本用法Strconv包中常用函数 二、Time包三、Math包math包概述使用math包 四、随机数包&#xff08;rand&#xff09; 一、Strings包 字符串处理包定义 Strings包简介&#xff1a; 一般编程语言包含的字符串处理库功能区别…

Perfetto中如何使用SQL语句

在使用 Perfetto 分析 Android 性能时&#xff0c;可以通过 Perfetto 提供的内置 SQL 查询来提取和分析不同的性能数据。Perfetto 允许你在 UI 界面或命令行中运行 SQL 查询&#xff0c;提取出 Trace 数据中包含的各种性能信息&#xff0c;比如 CPU 使用率、线程状态、内存分配…

QML项目实战:自定义TextField

目录 一.添加模块 import QtQuick.Controls 1.2 import QtQuick.Controls.Styles 1.4 import QtGraphicalEffects 1.15 二.自定义TextField 1.属性设置 2.输入框设置 3.按钮开关 三.效果 1.readonly为false 2.readonly为true 四.代码 一.添加模块 import QtQuick.…

【进阶】Stable Diffusion 插件 Controlnet 安装使用教程(图像精准控制)

Stable Diffusion WebUI 的绘画插件 Controlnet 最近更新了 V1.1 版本&#xff0c;发布了 14 个优化模型&#xff0c;并新增了多个预处理器&#xff0c;让它的功能比之前更加好用了&#xff0c;最近几天又连续更新了 3 个新 Reference 预处理器&#xff0c;可以直接根据图像生产…

DAF-FM DA与NO反应后,生成的产物能够发出强烈的绿色荧光,254109-22-3

一、基本信息 产品名称&#xff1a;DAF-FM DA&#xff08;一氧化氮NO荧光探针DAF-FM&#xff09; 英文名称&#xff1a;DAF-FM DA&#xff0c;DAF-FM diacetate CAS号&#xff1a;254109-22-3 分子式&#xff1a;C25H18F2N2O7 供应商&#xff1a;陕西新研博美生物科技 分…

在 Mac 和 Windows 系统中快速部署 OceanBase

OceanBase 是一款分布式数据库&#xff0c;具备出色的性能和高扩展性&#xff0c;可以为企业用户构建稳定可靠、灵活扩展性能的数据库服务。本文以开发者们普遍熟悉的Windows 或 Mac 环境为例&#xff0c;介绍如何快速上手并体验OceanBase。 一、环境准备 1. 硬件准备 OceanB…

使用Ant Design的Layout布局不能撑满整个屏幕问题解决方法

代码示例&#xff1a; import React, { useState } from react import {LaptopOutlined,NotificationOutlined,UserOutlined, } from ant-design/icons import type { MenuProps } from antd import { Layout, Menu, theme } from antd import routes from ./routes/index imp…

【ubuntu18.04】使用U盘制作ubuntu18.04启动盘操作说明

打开show application 打开Startup Disk 选择镜像 双击选择ubuntu的iso镜像 镜像下载地址 Ubuntu 18.04.6 LTS (Bionic Beaver) 制作镜像 注意&#xff1a; 制作镜像会格式化U盘&#xff0c;记得备份资料 点击Make Startup Disk,弹出如下对话框 点击Yes 输入管理员密码&a…

22.04Ubuntu---ROS2创建python节点

创建工作空间 mkdir -p 02_ros_ws/src 然后cd到该目录 创建功能包 在这条命令里&#xff0c;tom就是你的功能包 ros2 pkg create tom --build-type ament_python --dependencies rclpy 可以看到tom功能包已经被创建成功了。 使用tree命令&#xff0c;得到如下文件结构 此时…

《手写Spring渐进式源码实践》实践笔记(第十七章 数据类型转换)

文章目录 第十七章 数据类型转换工厂设计实现背景技术背景Spring数据转换实现方式类型转换器&#xff08;Converter&#xff09;接口设计实现 业务背景 目标设计实现代码结构类图实现步骤 测试事先准备属性配置文件转换器工厂Bean测试用例测试结果&#xff1a; 总结 第十七章 数…

使用docker形式部署jumpserver

文章目录 前言一、背景二、使用步骤1.基础环境准备2.拉取镜像3.进行部署4.备份记录启动命令 前言 记录一下使用docker形式部署jumpserver服务的 一、背景 搭建一个jumpserver的堡垒机&#xff0c;但是发现之前是二进制文件部署的&#xff0c;会在物理机上部署污染环境&#x…

(62)使用RLS自适应滤波器进行系统辨识的MATLAB仿真

文章目录 前言一、基本概念二、RLS算法原理三、RLS算法的典型应用场景四、MATLAB仿真代码五、仿真结果1.滤波器的输入信号、参考信号、输出信号、误差信号2.对未知系统进行辨识得到的系数 总结与后续 前言 RLS&#xff08;递归最小二乘&#xff09;自适应滤波器是一种用于系统…

算法每日双题精讲——滑动窗口(长度最小的子数组,无重复字符的最长子串)

&#x1f31f;快来参与讨论&#x1f4ac;&#xff0c;点赞&#x1f44d;、收藏⭐、分享&#x1f4e4;&#xff0c;共创活力社区。 &#x1f31f; 别再犹豫了&#xff01;快来订阅我们的算法每日双题精讲专栏&#xff0c;一起踏上算法学习的精彩之旅吧&#xff01;&#x1f4aa;…

MySQL数据库的备份与还原

目录 mysql 数据库的备份 生成SQL脚本 1 在控制台使用mysqldump命令可以用来生成指定数据库的脚本 ​编辑2 在数据库图形化界面工具&#xff1a;DateGrip 中操作&#xff1a;导出 mysql 数据库的还原 执行SQL脚本 1 在控制台使用 命令&#xff0c;指定将sql脚本导入到指定…

使用 IDEA 创建 Java 项目(二)

IDEA 创建 Java 项目 一般创建 Java 项目可以创建一个空项目&#xff0c;然后在空项目中添加模块&#xff0c;在模块中编写包&#xff0c;包中包含 Java 类。 如果你的 JDK 没有被 IDEA 自动找到的话&#xff0c;可以手动选择 JDK。我们先来学习 Intellij 构建系统下的 Java …

图论算法:最短路径算法详解【c语言版】(无权最短路径、Dijkstra算法)

别忘了请点个赞收藏关注支持一下博主喵&#xff01;&#xff01;&#xff01; 图论算法&#xff1a;最短路径算法详解 在图论中&#xff0c;最短路径问题是寻找图中两点之间具有最小总权重的路径。这个问题在许多实际应用中都有重要的作用&#xff0c;比如网络路由、城市交通规…

vue通过iframe方式嵌套grafana图表

文章目录 前言一、iframe方式实现xxx.xxx.com拒绝连接登录不跳转Cookie 的SameSite问题解决不显示额外区域(kiosk1) 前言 我们的前端是vue实现的&#xff0c;监控图表是在grafana中的&#xff0c;需要在项目web页面直接显示grafana图表 一、iframe方式实现 xxx.xxx.com拒绝连…

苹果系统安装Homebrew时CLT缺失的问题

前言 为了使用brew命令&#xff0c;必须安装Homebrew工具。但是在Howebrew安装的时候&#xff0c;会出现CLT&#xff08;Command Line Tools&#xff09;缺失的问题。本博客就是讨论如何来解决这个问题的。 1、问题的出现 2、解决途径 在命令行终端中输入命令&#xff1a;xcod…

LeetCode Hot100 49.字母异位词分组

题干&#xff1a; 思路&#xff1a; 输入的是一个字符串数组&#xff0c;输出是一个列表&#xff0c;首先我们需要通过遍历数组获得每一个字符串&#xff0c;我们想要判断获得的任意两个字符串是不是字母异位词&#xff0c;所以可以将获得的字符串排序&#xff08;转换为字符数…

小白初入Android_studio所遇到的坑以及怎么解决

1. 安装Android_studio 参考&#xff1a;Android Studio 安装配置教程 - Windows(详细版)-CSDN博客 Android Studio超级详细讲解下载、安装配置教程&#xff08;建议收藏&#xff09;_androidstudio-CSDN博客 想下旧版本的android_studio的地址&#xff08;仅供参考&#xf…