算法练习-二叉树的节点个数【完全/普通二叉树】(思路+流程图+代码)

难度参考

        难度:中等

        分类:二叉树

        难度与分类由我所参与的培训课程提供,但需要注意的是,难度与分类仅供参考。且所在课程未提供测试平台,故实现代码主要为自行测试的那种,以下内容均为个人笔记,旨在督促自己认真学习。

题目

        给出一棵完全二叉树,求出该树的节点个数!

        输入:root=[1,2,3,4,5,6]
        输出:6
        示例2:
        输入:root=[]
        输出:0
        示例3:
        输入:root=[1]
        输出:1
        提示:
        树中节点的数目范围是[0,5*10^4]
        0<=Node.val<=5*10^4
        题目数据保证输入的树是完全二叉树

思路

        对于这道题目要求计算一个完全二叉树的节点个数,可以利用完全二叉树的性质来解决,而不需要对整个树进行遍历。

        完全二叉树是指除了最后一层外,每一层的节点都被填满,并且最后一层的节点靠左排列的二叉树。简单来说,完全二叉树是一种具有最优结构的二叉树。

        下面是一个完全二叉树的示例:

      A
    /   \
   B     C
  / \   /
 D   E F

        在这个示例中,树的每一层都被节点填满,最后一层的节点靠左排列。

        需要注意的是,对于完全二叉树,叶节点只会出现在最后两层,并且最后一层的叶节点都靠左排列。

        完全二叉树的性质包括

  1. 对于树的任意节点,如果它的深度(从根节点到达该节点的路径的长度)为 d,则以该节点为根的子树一定是一个高度为 h-d+1 的满二叉树。

    • 这是因为完全二叉树的定义要求除了最后一层外,其他层都是满的,而最后一层的节点从左到右连续排列。
  2. 一棵高度为 h 的满二叉树,其节点个数为 2^h-1。

        完全二叉树与满二叉树有一定的关系,但并不是完全一样的概念。

        满二叉树是一种特殊的二叉树它的每一层都被节点填满,所有的叶节点都在同一层上。换句话说,满二叉树的节点数达到了最大值。满二叉树的层数是固定的。

        而完全二叉树除了最后一层外,每一层的节点都被填满,并且最后一层的节点靠左排列。完全二叉树的节点数可以少于满二叉树,最后一层不一定是满的

        所以可以说,满二叉树是完全二叉树的一种特殊情况,但完全二叉树不一定是满二叉树。完全二叉树的层数可以少于满二叉树的层数,节点数也可以少于满二叉树的节点数。

        下面是一个满二叉树的示例:

      A
    /   \
   B     C
  / \   / \
 D   E F   G

        基于上述性质,我们可以采用以下步骤来计算完全二叉树的节点个数:

  1. 首先,计算树的左子树和右子树的深度 leftDepth 和 rightDepth。

  2. 如果 leftDepth 等于 rightDepth,则说明左子树是一个满二叉树,且其节点个数可以通过公式 2^leftDepth-1 计算得到。

    • 在这种情况下,我们只需要递归计算右子树的节点个数,然后加上左子树的节点个数和根节点,即可得到完整的节点个数。
  3. 如果 leftDepth 不等于 rightDepth,则说明右子树是一个满二叉树,且其节点个数可以通过公式 2^rightDepth-1 计算得到。

    • 在这种情况下,我们只需要递归计算左子树的节点个数,然后加上右子树的节点个数和根节点,即可得到完整的节点个数。

        通过实现上述步骤,我们可以快速地计算出完全二叉树的节点个数。

示例

        假设我们有以下二叉树:

     1
   /   \
  2     3
 / \   /
4   5 6

        首先,根节点存在,我们开始计算左子树的高度。从根节点1开始,我们通过左子节点2,再通过左子节点4,没有更多的左子节点,此时左子树的高度为3。

        接下来,我们计算右子树的高度。从根节点1开始,我们通过右子节点3,再通过左子节点6,没有更多的右子节点,此时右子树的高度为2。

        由于左子树的高度(3)不等于右子树的高度(2),所以我们知道右子树是完全二叉树。现在,我们递归地计算右子树的节点个数。右子树只有一个节点6,所以它的节点个数为1。

        由于左子树的高度(3)大于右子树的高度(2),所以我们知道左子树不是完全二叉树。我们需要递归地计算左子树的节点个数。左子树有两个子节点4和5,因此左子树的节点个数为2。

        最后,我们将左子树的节点个数(2)、右子树的节点个数(1)和根节点(1)相加,得到整个二叉树的节点个数为6。

梳理

        我们通过判断左子树的高度和右子树的高度来确定给定二叉树是否是完全二叉树。

        判断一个二叉树是否是完全二叉树的方法如下:

  1. 首先,我们计算左子树和右子树的高度,使用两个循环分别计算它们的高度。
  2. 如果左子树的高度等于右子树的高度,说明左子树是一个满二叉树(即每个节点都有两个子节点),而右子树可能是一个完全二叉树。在完全二叉树中,所有的节点都尽量靠左排列,且最后一层节点从左到右依次填充。因此,在满二叉树的情况下,我们可以使用一个简单的公式计算出左子树的节点个数:(1 << leftHeight) - 1。其中,<< 是位移运算符,表示将1左移leftHeight位,即乘以2的leftHeight次方,然后减去1。
  3. 如果左子树的高度不等于右子树的高度,说明左子树不是一个满二叉树,而右子树可能是一个完全二叉树。在这种情况下,我们递归地计算左子树和右子树的节点个数,并将结果相加再加上根节点的1。

        通过这种判断逻辑,我们可以正确地计算给定二叉树的节点个数。

代码

#include<iostream>
#include<vector>
using namespace std;
  
// 二叉树的节点定义
struct TreeNode {
    int val;
    TreeNode *left;
    TreeNode *right;
    TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};

// 计算完全二叉树的节点个数
int countNodes(TreeNode* root) {
    if (root == NULL) {
        return 0;
    }
    
    int leftHeight = 0;
    int rightHeight = 0;
    TreeNode* leftNode = root;
    TreeNode* rightNode = root;
    
    // 计算左子树的高度
    while (leftNode != NULL) {
        leftNode = leftNode->left;
        leftHeight++;
    }
    
    // 计算右子树的高度
    while (rightNode != NULL) {
        rightNode = rightNode->right;
        rightHeight++;
    }
    
    // 如果左子树的高度等于右子树的高度,则说明左子树是完全二叉树
    if (leftHeight == rightHeight) {
        // 计算左子树的节点个数:2^leftHeight-1,加上根节点1,即为总节点个数
        return (1 << leftHeight) - 1;
    } else {
        // 如果左子树的高度不等于右子树的高度,则说明右子树是完全二叉树
        // 计算右子树的节点个数:2^rightHeight-1,加上根节点1,即为总节点个数
        return countNodes(root->left) + countNodes(root->right) + 1;
    }
}

int main() {
    // 构建示例树 [1,2,3,4,5,6]
    TreeNode* root = new TreeNode(1);
    root->left = new TreeNode(2);
    root->right = new TreeNode(3);
    root->left->left = new TreeNode(4);
    root->left->right = new TreeNode(5);
    root->right->left = new TreeNode(6);
  
    // 计算节点个数
    int count = countNodes(root);
  
    // 输出结果
    cout << "节点个数: " << count << endl;
  
    return 0;
}

进阶

        如果是一棵普通二叉树?

思路

        当求任意一棵二叉树的节点个数时,可以使用递归的方式来解决。下面是一种求二叉树节点个数的思路和题解:

  1. 如果根节点为空,则树中没有节点,返回节点个数为0。
  2. 否则,递归计算根节点的左子树的节点个数,记为 leftCount
  3. 递归计算根节点的右子树的节点个数,记为 rightCount
  4. 节点个数等于左子树节点个数、右子树节点个数和根节点本身的总和(即 leftCount + rightCount + 1)。
  5. 返回节点个数作为结果。

示例

        直接用了之前的输入(普通也包含完全)。

  1. 要计算整个树的节点个数,开始于根节点1。
  2. 根节点1本身是一个节点,因此开始时计数为1。
  3. 接下来,递归地计算节点2的子树节点个数。节点2有两个孩子,节点4和节点5,它们都没有孩子,所以子树2的节点个数是1(节点2本身)+1(节点4)+1(节点5)=3。
  4. 同样,节点3也被递归地计算。节点3有一个孩子,节点6,它没有孩子,所以节点3的子树节点个数是1(节点3本身)+1(节点6)=2。
  5. 将所有计数相加:1(根节点1)+3(节点2及其子树的节点数)+2(节点3及其子树的节点数)=6。

        所以最终,整棵树的节点个数为6。

        这个过程是通过代码中的递归函数countNodes实现的。函数countNodes按照上述逻辑,以树的根节点开始访问每个节点,并递归地遍历它们的左右子树,计算每个子树的节点数,并将它们与当前节点的数量(1)相加。当遇到空子树(即NULL,或没有子节点的节点)时,递归调用返回0,表示这里没有更多的节点需要计数。通过这种方式,所有节点被计数并求和,得到整棵树的节点总数。   

梳理

        普通二叉树和完全二叉树在结构上存在显著不同,因此求解它们节点个数的最佳方法往往也不同。这些不同主要表现在:

  1. 普通二叉树:它的结构没有特定的规则,节点可以任意地分布在左右子树中。为了计算普通二叉树中的节点数,我们通常采用递归方法,对每个节点进行遍历。递归过程中,当遇到一个非空节点时,就将计数加1,并继续递归计算它的左右子节点。

  2. 完全二叉树:它是一种特殊的二叉树,所有的层都是满的,除了最后一层,最后一层的节点尽量都向左排列。这种结构使得我们可以利用完全二叉树的性质来用非递归的方法来计算节点数。例如,可以通过计算树高以及最后一层节点的数量来求得总节点数。因为在完全二叉树中,除了最后一层外,其余每层的节点数都是已知的(即 2^层级数 - 1)。

        可以使用普通二叉树计算节点个数的方法来计算完全二叉树的节点数,即递归遍历每个节点,这种方法在任何类型的二叉树中都适用。然而,当树的高度较大时,这种方法相对较慢,因为它需要访问树中的每个节点。

        针对完全二叉树,利用其特殊性质(每层节点数等比递增、最后一层节点尽量左对齐等),可以设计出更为高效的算法(其他方法回头补)。

代码

#include <iostream>
using namespace std;

// 定义二叉树节点结构体
struct TreeNode {
    int val;
    TreeNode *left;
    TreeNode *right;
    TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};

// 用于计算二叉树节点数的递归函数
int countNodes(TreeNode* root) {
    if (root == NULL) {
        return 0;
    } else {
        return 1 + countNodes(root->left) + countNodes(root->right);
    }
}

// 主函数
int main() {
    // 创建二叉树
    // 注意:这里仅作为示例,具体创建二叉树的方法取决于输入格式和树结构
    TreeNode* root = new TreeNode(1);
    root->left = new TreeNode(2);
    root->right = new TreeNode(3);
    root->left->left = new TreeNode(4);
    root->left->right = new TreeNode(5);
    root->right->left = new TreeNode(6);

    // 计算节点数并输出结果
    cout << "Number of nodes: " << countNodes(root) << endl;

    // 清理申请的内存(此处未显示,但在实际使用中需要处理)
    // 如通过后序遍历删除所有节点
    // deleteTree(root);

    return 0;
}

打卡

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

ubuntu22.04 安装部署01:禁用内核更新

一、前言 ubunut22.04系统安装以后&#xff0c;内核更新会导致各种各样的问题&#xff0c;因此锁定初始安装环境特别重要&#xff0c;下面介绍如何锁定内核更新。 二、操作方法 2.1 查看可用内核 dpkg --list | grep linux-image dpkg --list | grep linux-headers dpkg --…

STM32--USART串口(2)串口外设

一、USART简介 可配置数据位&#xff1a;不需要校验就是8位&#xff0c;需要校验就选9位&#xff1b; 停止位&#xff1a;决定了帧的间隔; STM32F103C8T6USART&#xff1a;USART1挂载在APB2总线上&#xff0c;USART2和USART3挂载在APB1总线上&#xff1b; 二、USART框图 TXE…

Python中使用Opencv-python库绘制直线、矩形、圆、文本

Python中使用Opencv-python库绘制直线、矩形、圆、文字 在Python中使用Opencv-python绘制直线、矩形、圆、文本非常简单&#xff0c;分别使用到line、rectangle、circle、putText这几个函数&#xff0c;具体可以参考https://docs.opencv.org/4.9.0/d6/d6e/group__imgproc__dra…

如何部署Node.js服务并实现无公网ip远程访问本地项目【内网穿透】

文章目录 前言1.安装Node.js环境2.创建node.js服务3. 访问node.js 服务4.内网穿透4.1 安装配置cpolar内网穿透4.2 创建隧道映射本地端口 5.固定公网地址 前言 Node.js 是能够在服务器端运行 JavaScript 的开放源代码、跨平台运行环境。Node.js 由 OpenJS Foundation&#xff0…

【C++】类与对象(三)—运算符重载|const成员函数|取地址及const取地址操作符重载

前言 运算符重载&#xff0c;自增自减运算符重载&#xff0c;const成员函数&#xff0c;取地址及const取地址操作符重载 文章目录 一、运算符重载自增和自减运算符重载 二、const 成员函数三、取地址及const取地址操作符重载&#xff08;了解即可&#xff09; 一、运算符重载 运…

P1967 [NOIP2013 提高组] 货车运输

[NOIP2013 提高组] 货车运输 题目背景 NOIP2013 提高组 D1T3 题目描述 A 国有 n n n 座城市&#xff0c;编号从 1 1 1 到 n n n&#xff0c;城市之间有 m m m 条双向道路。每一条道路对车辆都有重量限制&#xff0c;简称限重。 现在有 q q q 辆货车在运输货物&#x…

Unity Meta Quest MR 开发(三):Scene API 配置+实现虚拟与现实之间的碰撞

文章目录 &#x1f4d5;教程说明&#x1f4d5; Scene 配置⭐开启场景理解功能和应用访问空间数据的权限⭐OVRSceneManager⭐制作 Plane Prefab 和 Volume Prefab⭐运行场景⭐添加透视材质 &#x1f4d5;虚拟与现实物体的碰撞&#xff08;弹球 Demo&#xff09;&#x1f4d5;Mes…

【JavaSE篇】——继承

目录 &#x1f393;继承 ✅为什么需要继承 ✅继承概念 ✅继承的语法 ✅父类成员访问 &#x1f6a9;子类中访问父类的成员变量 1. 子类和父类不存在同名成员变量的情况 2. 子类和父类成员变量同名 &#x1f6a9;子类中访问父类的成员方法 1. 成员方法名字不同 2. 成员…

MyBatis常见面试题汇总

说一下MyBatis执行流程&#xff1f; MyBatis是一款优秀的基于Java的持久层框架&#xff0c;它内部封装了JDBC&#xff0c;使开发者只需要关注SQL语句本身&#xff0c;而不需要花费精力去处理加载驱动、创建连接等的过程&#xff0c;MyBatis的执行流程如下&#xff1a; 加载配…

车载测试Vector工具——基于DoIP的ECU/车辆的连接故障排除

车载测试Vector工具——基于DoIP的ECU/车辆的连接故障排除 我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师(Wechat:gongkenan2013)。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 屏蔽力是信息过载时代一个人的特殊竞争力,任何消耗你的人和…

计算huggingface模型占用硬盘空间的实战代码

大家好,我是herosunly。985院校硕士毕业,现担任算法研究员一职,热衷于机器学习算法研究与应用。曾获得阿里云天池比赛第一名,CCF比赛第二名,科大讯飞比赛第三名。拥有多项发明专利。对机器学习和深度学习拥有自己独到的见解。曾经辅导过若干个非计算机专业的学生进入到算法…

景联文科技受邀出席全国信标委生物特征识别分委会二届五次全会

全国信息技术标准化技术委员会生物特征识别分技术委员会&#xff08;SAC/TC28/SC37&#xff0c;以下简称“分委会”&#xff09;二届五次全会于2024年1月30日在北京顺利召开&#xff0c;会议由分委员秘书长王文峰主持。 分委会由国家标准化管理委员会批准成立&#xff0c;主要负…

N 叉树的层序遍历

给定一个 N 叉树&#xff0c;返回其节点值的层序遍历。&#xff08;即从左到右&#xff0c;逐层遍历&#xff09;。 树的序列化输入是用层序遍历&#xff0c;每组子节点都由 null 值分隔&#xff08;参见示例&#xff09;。 示例 1&#xff1a; 输入&#xff1a;root [1,null…

配置实例—VLAN间跨三层通信的交换机配置实例

一、组网需求 企业的不同用户拥有相同的业务&#xff0c;且位于不同的网段。现在相同业务的用户所属的VLAN不相同&#xff0c;需要实现不同VLAN中的用户相互通信。 如图1所示&#xff0c;User1和User2中拥有相同的业务&#xff0c;但是属于不同的VLAN且位于不同的网段。现需要…

【笔记】React Native实战练习(仿网易云游戏网页移动端)

/** * 如果系统看一遍RN相关官方文档&#xff0c;可能很快就忘记了。一味看文档也很枯燥无味&#xff0c; * 于是大概看了关键文档后&#xff0c;想着直接开发一个Demo出来&#xff0c;边学边写&#xff0c;对往后工作 * 开发衔接上能够更顺。这期间肯定会遇到各种各样的问题&a…

基于SpringBoot Vue学生成绩管理系统

大家好✌&#xff01;我是Dwzun。很高兴你能来阅读我&#xff0c;我会陆续更新Java后端、前端、数据库、项目案例等相关知识点总结&#xff0c;还为大家分享优质的实战项目&#xff0c;本人在Java项目开发领域有多年的经验&#xff0c;陆续会更新更多优质的Java实战项目&#x…

分布式事务 笔记

为什么使用分布式事务 分布式环境下一个业务可能会涉及到多个模块之间的调用&#xff0c;为了保证操作的原子性&#xff0c;分布式事务是最好的解决方案。假设会员服务异常&#xff0c;这是已经完成锁库&#xff0c;锁库无法回滚。 本地事务 事务特性&#xff08;ACID&#…

计算机服务器中了DevicData勒索病毒如何解密,DevicData勒索病毒解密流程

网络数据安全一直是企业关心的主要话题&#xff0c;近期&#xff0c;云天数据恢复中心接到很多企业的求助&#xff0c;企业的计算机服务器遭到了DevicData勒索病毒攻击&#xff0c;导致企业计算机服务器瘫痪无法正常工作&#xff0c;严重影响了工作业务开展。经过云天数据恢复中…

debian12 解决 github 访问难的问题

可以在 /etc/hosts 文件中添加几个域名与IP对应关系&#xff0c;从而提高 github.com 的访问速度。 据搜索了解&#xff08;不太确定&#xff09;&#xff0c;可以添加这几个域名&#xff1a;github.com&#xff0c;github.global.ssl.fastly.net&#xff0c;github.global.fa…

计算机组成原理(0)冯诺依曼体系结构

文章目录 定义**主要特点&#xff1a;****缺陷&#xff1a;** 定义 冯诺依曼体系结构&#xff08;Von Neumann architecture&#xff09;&#xff0c;也称为普林斯顿体系结构&#xff08;Princeton architecture&#xff09;&#xff0c;是一种计算机架构理论&#xff0c;由匈…