手撕二叉树(图解+代码)

news2024/11/24 13:44:41

二叉树

  • 🌳1.树的概念
  • 🌳2.二叉树的概念及性质
    • 🍎2.1 二叉树的概念
    • 🍎2.2 二叉树的性质
  • 🌳3.二叉树的基本操作
    • 🍎3.1 二叉树的遍历
    • 🍎3.2 获取树中节点的个数
    • 🍎3.3 获取叶子节点的个数
    • 🍎3.4 获取第K层节点的个数
    • 🍎3.5 获取二叉树的高度
    • 🍎3.6 检查值为value的元素是否存在
    • 🍎3.7 层次遍历
    • 🍎3.8 判断二叉树是不是完全二叉树

在学习二叉树之前,我们有必要了解一下树的概念和常用术语。接下来以下面这棵树为例来回顾下树相关概念:
在这里插入图片描述

🌳1.树的概念

树的概念:一种非线性的数据结构,由n(n >=0)个节点组成的一个具有层次关系的集合。 树的术语:

  • 节点的度 一个节点含有子树的个数,例如上图中A的度为6.
  • 树的度 树上所有节点的度的最大值,例如上图中树的度为6.
  • 叶子结点 度为0的节点,例如上图中的K、L、M…
  • 根节点 树中没有双亲节点的节点。例如,上图中树的根节点为A。、
  • 子节点 一个节点含有的子树的根,如上如,B是A的子节点
  • 父节点 一个节点有子节点,则称这个节点为其子节点的父节点。如上图A时B的父节点

🌳2.二叉树的概念及性质

🍎2.1 二叉树的概念

二叉树是树的一种特殊形式,空树是二叉树,或者树中所有节点的度小于二的树称为二叉树。如下图所示,接下来介绍二叉树我们以这张图片上的树形为准:

在这里插入图片描述

🍎2.2 二叉树的性质

  • 规定根节点的层次为1,则一颗非空二叉树的第 i 层上最多有:2k-1个节点
  • 规定根节点的二叉树深度为1,则深度为k的那层的二叉树的最大节点数为:2k-1(k>=0)
  • 对于任何一颗 二叉树 ,如果器叶子节点数为n0,度为2的节点数为n2,则有n0 = n2 + 1
  • 具有 n 个节点的完全二叉树的深度k 为:log2(n+1) 向上取整.

🌳3.二叉树的基本操作

🍎3.1 二叉树的遍历

🌳前序遍历
前序遍历的二叉树将按照先访问根节点,再访问左子树,最后访问右子树的过程递归进行,最终遍历结束所有的节点。
前序遍历的过程如下:
在这里插入图片描述

public void preOrder(TreeNode root) {
	if(root != null) {
		System.out.print(root.val+" ");
		preOrder(root.left);
		preOrder(root.right);		
	}	
}

按照前序遍历结果,则上述二叉树的遍历结果为:A->B->D->E->H->C->F->G

🌳中序遍历
中序遍历的二叉树将按照先访问左子树,再访问根节点,最后访问右子树的过程递归进行,最终遍历结束所有的节点。
中序遍历的过程如下:
在这里插入图片描述

public void inOrder(TreeNode root) {
    if (root != null) {
        inOrder(root.left);
        System.out.print(root.val + " ");
        inOrder(root.right);
    } }

根据上图的遍历过程,我们可以得到上述二叉树的中序遍历结果为:D->B->E->H->A->F->C->G

🌳后序遍历
后序遍历的二叉树将按照先访问左子树,再访问右子树,最后访问根节点的过程递归进行,最终遍历结束所有的节点。
后续遍历的过程如下:
在这里插入图片描述

    public void postOrder(TreeNode root) {
        if (root != null) {
            postOrder(root.left);
            postOrder(root.right);
            System.out.print(root.val + " ");
        }
    }

由上图后续遍历过程可知,图中二叉树的遍历结果为:D->H->E->B->F->G->C->A

🌳层次遍历
二叉树的层次遍历即按照二叉树的层次顺序从左到右遍历节点,过程如下图所示:
在这里插入图片描述
因为二叉树的遍历无法直接按照遍历顺序进行层次节点的访问,因此我们需要借助队列的这种数据结构来实现二叉树的层次遍历。层次遍历的原理即上述二叉树的详细入出队列过程如下:
在这里插入图片描述

    public void levelOrder(TreeNode root) {
        ArrayDeque<TreeNode> charQueue = new ArrayDeque<>();  //创建一个队列
        charQueue.offer(root);  //将根节点入队列
        while(!charQueue.isEmpty()) {
            TreeNode pollNode = charQueue.poll();	//只要队列不为空,就从队列中取出节点访问
            System.out.print(pollNode.val + " ");
            if(pollNode.left != null) {	
                charQueue.offer(pollNode.left);
            }
            if(pollNode.right != null) {
                charQueue.offer(pollNode.right);
            }
        }
    }

🍎3.2 获取树中节点的个数

我们可以通过定义计数器以及任何一种遍历方式,拿到二叉树的节点个数,这种方法很简单,那么有没有什么别的方法可以不用定义计数器就获取到节点的个数呢?
不妨这样去向,从根节点开始,这颗二叉树的节点个数等于左子树+右子树+根节点;而对于左子树来说,左子树的节点个数同样等于它的左子树+有稀疏+根节点的个数。既然这样,我们能否通过直接返回节点个数的方法直接求出总的节点数呢》显而易见,按照上面的方法定义递归算法,可以很容易实现这件事情:

    public int countNode(TreeNode root) {
        if (root == null) {
            return 0;
        }
        int leftCount = countNode(root.left);
        int rightCount = countNode(root.right);
        return leftCount + rightCount + 1;
    }

这种求节点个数的原理如下图所示:
在这里插入图片描述

🍎3.3 获取叶子节点的个数

与上边求节点的个数类似,我们让当前根节点判断是否有左右子树即可。
原理与上边统计节点个数的原理相同,这里就不画图了。

    public int getLeafNodeCount(TreeNode root) {
        if(root == null) {
            return 0;
        }
        if(root.left == null && root.right == null) {
            return 1;
        }
        int leftLeafCount = getLeafNodeCount(root.left);
        int rightLeafCount = getLeafNodeCount(root.right);
        return leftLeafCount + rightLeafCount;
    }

🍎3.4 获取第K层节点的个数

若规定二叉树根节点的层次为1,则对根节点层次来说,求得是其第k层的节点个数;对第二层次来说求的是其第k-1层的节点个数…,那么,我们使用递归算法,当k的相对值变为1时,则返回1代表第k层上节点的个数,将左右子树的值相加返回即可.

/**
 * 求二叉树第k层上的节点的个数  
 * @param root  二叉树的根节点  
 * @param k 求解的层数  
 * @return 返回节点个数  
 */
  public int getLevelNodeCount(TreeNode root, int k) {
    if (root == null) {
        return 0;
    }
    if (k == 1) {
        return 1;
    }
    int leftCount = getLevelNodeCount(root.left, k - 1);
    int rightCount = getLevelNodeCount(root.right, k - 1);
    return leftCount + rightCount; }

这个求解过程如下图所示:
在这里插入图片描述

🍎3.5 获取二叉树的高度

某棵二叉树的高度等于它的左子树高度的和右子树高度的最大值加上自己,即高度height = leftTree + rightTree + 1。这仍然是典型的递归算法。当左子树或者右子树为null时,说明二叉树的这一层已经没有节点了,那么返回给上一层递归结果0即可。这个递归算法的程序设计如下:

/**
 * 获取二叉树的高度  *  
 * @param root 二叉树的根节点 
 * @return 返回二叉树的高度 
 */ 
 public int getHeight(TreeNode root) {
    if (root == null) {
        return 0;
    }
    int leftTreeHeight = getHeight(root.left);
    int rightTreeHeight = getHeight(root.right);
    return Math.max(leftTreeHeight, rightTreeHeight) + 1; 
 }

这个递归过程如下图所示:
在这里插入图片描述

🍎3.6 检查值为value的元素是否存在

遍历二叉树的所有节点,如果存在值为value的节点,则返回这个节点,否则返回null。检查的过程为,检查根节点的值是否为value;检查左子树中是否存在;检查右子树中是否存在。递归算法的程序设计如下:

/**
 * 查找二叉树中是否存在值为value的节点
 *  
 * @param value 待查找的值  
 * @return 返回查找结果  
 */
 public TreeNode getValNode(TreeNode root, char value) {
    //如果遍历到最底层还没有找到,返回null
    if (root == null) {
        return null;
    }
    if(root.val == value) {
        return root;
    }
    //从左子树中查找
    TreeNode valNodeInL = getValNode(root.left, value);
    if(valNodeInL != null) {
        return valNodeInL;
    }
    //从右子树中查找
    TreeNode valNodeInR = getValNode(root.right, value);
    if(valNodeInR != null) {
        return valNodeInR;
    }
    return null; 
   }

这个算法的递归过程与先序遍历的过程类似。可以看上边先序遍历的图。

🍎3.7 层次遍历

所谓层析遍历,就是指将二叉树从左到右从上到下依次遍历。为了实现这种遍历效果,我们需要使用队列这种数据结构将当前节点的左右子树根节点依次入栈并在出栈式再依次将出战节点的左右子树根节点入栈,依次进行下去,直到当前栈为空。该算法的程序设计如下:

public void levelOrder(TreeNode root) {
    if(root == null) {
        return;
    }
    Queue<TreeNode> queue = new LinkedList<>();
    queue.offer(root);
    while(!queue.isEmpty()) {
        TreeNode poll = queue.poll();
        System.out.print(poll.val + " ");
        if(poll.left != null) {
            queue.offer(poll.left);
        }
        if(poll.right != null) {
            queue.offer(poll.right);
        }
    } 
}

该算法的运行过程如下:
在这里插入图片描述

🍎3.8 判断二叉树是不是完全二叉树

我们将该二叉树的依次按照层次遍历的顺序入队列(包含最后一层的左右空子树),在出队列时进行判断,如果出队列的节点为null,就停止入队列,并检查队列中剩余节点是否还有非空节点,如果有非空节点,说明这不是一个完全二叉树。例如,下面的这几种二叉树都不是完全二叉树,均满足上述的判定方法:
在这里插入图片描述

public boolean isCompleteTree(TreeNode root) {
    if (root == null) {
        return false;
    }
    Queue<TreeNode> queue = new LinkedList<>();
    queue.offer(root);
    while (!queue.isEmpty()) {
        TreeNode poll = queue.poll();   //如果在出队列时遇到null,则停止入队列
        if (poll != null) {
            queue.offer(poll.left);
            queue.offer(poll.right);
        } else {
            break;
        }
    }
    while (!queue.isEmpty()) {
        if (queue.poll() != null) { //如果队列中剩余节点还有非空的,就说明这不是一颗完全二叉树
            return false;
        }
    }
    return true;
 }

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

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

相关文章

Python学习5:计算弓形的面积

类型&#xff1a;数值运算 描述‪‬‪‬‪‬‪‬‪‬‮‬‪‬‫‬‪‬‪‬‪‬‪‬‪‬‮‬‪‬‭‬‪‬‪‬‪‬‪‬‪‬‮‬‫‬‮‬‪‬‪‬‪‬‪‬‪‬‮‬‭‬‫‬‪‬‪‬‪‬‪‬‪‬‮‬‫‬‪‬‪‬‪‬‪‬‪‬‪‬‮‬‭‬‫‬‪‬‪‬‪‬‪‬‪‬‮‬‫‬‪‬ …

大数据学习初级入门教程(十七) —— Elasticsearch 8.7.0 完全分布式集群的安装、配置、启动和测试

好久没用 Elasticsearch 集群了&#xff0c;参考以前写的《大数据学习初级入门教程&#xff08;八&#xff09; —— Elasticsearch 7.6.2 单节点的安装、启动和测试_elasticsearch 7.6.2需要专属网络_孟郎郎的博客-CSDN博客》、《大数据学习初级入门教程&#xff08;九&#x…

面向对象与面向过程的区别

“劳累一天回到家中“ ”对象赶忙问我想吃些什么&#xff1f;“ “望着窗外淅淅沥沥的小雨 蛋炒饭吧” “雨声洗涤了心灵 炒饭温暖了肚子” “我没有问她炒饭是怎么做的&#xff0c;他也没有管我吃相有多难看” “我面向对象&#xff0c;她也面向对象” 面向对象和面向过程的区…

Unity3D:编辑场景模板

场景模板 Inspector 场景模板 Inspector 包含以下部分&#xff1a; Details&#xff1a;指定模板使用哪个场景&#xff0c;并包含模板的描述&#xff0c;该内容将出现在 New Scene 对话框中。Thumbnail&#xff1a;提供用于为模板创建预览图像的选项。Scene Template Pipelin…

ajax的介绍及使用

ajax的介绍 开发流程 前端 ajax:前后端沟通的桥梁 后端 ajax介绍 ajax叫做异步的Javascript和xml ajax通过浏览器与服务器&#xff08;后端&#xff09;进行少量数据交互&#xff0c;进行页面异步更新&#xff08;网页不会重新加载&#xff09; 优点&#xff1a; 减轻服务器负…

Java基础1

一、标识符 给类、接口、方法、变量等取名时用的字符序列 如&#xff1a; public class 类名-标识符{public static void 方法名-标识符(String[] args){int 变量或标识符 1000;} } 二、标识符的命名规范 组成部分 英文大小写字母、数字字符、美元($)符号、下划线(_)、中文…

「ChatGPT」十分钟学会如何在本地调用API_KEY(最新版 | 附源码)

&#x1f482;作者简介&#xff1a; THUNDER王&#xff0c;一名热爱财税和SAP ABAP编程以及热爱分享的博主。目前于江西师范大学会计学专业大二本科在读&#xff0c;同时任汉硕云&#xff08;广东&#xff09;科技有限公司ABAP开发顾问。在学习工作中&#xff0c;我通常使用偏后…

1.2 行列式的性质和计算

学习目标&#xff1a; 当学习行列式性质和计算时&#xff0c;以下是一些具体的学习目标&#xff1a; 理解行列式的定义和计算方法&#xff0c;能够准确计算给定的行列式。&#xff08;最基本的&#xff09;熟练掌握行列式的基本性质&#xff0c;包括交换行列式的两行或两列、…

VRP开源的算例资源

VRP开源的算例资源 开源的算例资源 开源的MIP算例网址 1. MISOCP网址 Benchmark instances&#xff1a;多种问题的算例数据 TSP算例网址 VRP标杆算例网址 1. Networking and Emerging Optimization发布的VRP算例 2. PRP算例 3. 一个学者的主页上的算例 4. Chair in L…

Linux学习笔记(3)一些数据类型

1&#xff09;_u32 是一个无符号的32位整数类型&#xff0c;它在 Linux 内核中定义为 typedef unsigned int __u32。其中&#xff0c;__u32 是为了避免名称冲突而定义的特殊类型。无符号整数是一种表示正整数的数据类型&#xff0c;其取值范围为 0 到 4294967295&#xff08;2…

docker容器:本地私有仓库、harbor私有仓库部署与管理

目录 一、本地私有仓库 1、本地私有仓库简介 2、搭建本地私有仓库 3、容器重启策略介绍 二、harbor私有仓库部署与管理 1、什么是harbor 2、Harbor的特性 3、Harbor的构成 4、harbor部署及配置 ①部署docker-compose ②部署Harbor服务 ③登录创建项目 ④登录仓库并…

对话到行动:通过行动级生成构建面向任务的对话系统

目录 对话到行动:通过行动级生成构建面向任务的对话系统 1介绍 2框架描述 2.1概述 2.2第一步:对话动作构造 2.3步骤2:响应标准化响应标准化 2.4步骤3:动作序列预测 2.5步骤4:生成响应 3实验 3.1实验设置 3.2主要结果 3.3深度分析 4结论 5主持人简介 6公司简介 对…

V-Ray渲染教程:又快又好的V-Ray渲染参数!

Chaos V-Ray 是适用于大部分主流3D设计软件和CAD程序的3D渲染插件&#xff0c;它可以与 3ds Max、Cinema 4D、Houdini、Maya、Nuke、Revit、Rhino、SketchUp、Unreal 无缝协作。借助 V-Ray渲染器强大的功能&#xff0c;艺术家和设计师可以产生出非常逼真的渲染效果。 那么&…

服务提供者 Eureka + 服务消费者(Rest + Ribbon)实战

1、Ribbon背景介绍 Ribbon是Netflix发布的开源项目&#xff0c;主要功能是提供客户端的软件负载均衡算法&#xff0c;将Netflix的中间层服务连接在一起。Ribbon客户端组件提供一系列完善的配置项如连接超时&#xff0c;重试等。简单来说&#xff0c;就是在配置文件中列出Load B…

Word中截取部分内容并保存为jpg图片的方法

private void button1_Click(object sender, EventArgs e) { var appWord new Microsoft.Office.Interop.Word.Application(); var doc new Microsoft.Office.Interop.Word.Document(); object oMissing System.Reflection.Missing.Value;//这个是什么东西&#xff0c;我始终…

等级保护5个级别详细说明-行云管家

目前我国正在严格执行等保政策&#xff0c;但不少企业对于等级保护的5个级别不是很清楚&#xff0c;这里我们行云管家小编就来给大家详细说明一下。 等级保护5个级别详细说明-行云管家 根据《信息安全技术网络安全等级保护基本要求》&#xff0c;安全保护等级分为5个级别&…

Linux 中 top信息详解,CPU负载详解

一、top信息的三个参数到底是什么意思&#xff1f; 6.68、7.67、8.08 分别代表前一分钟&#xff0c;五分钟&#xff0c;十五分钟的平均CPU负载&#xff0c;最重要的指标是最后一个数字&#xff0c;即前15分钟的平均CPU负载&#xff0c;这个数字越小越好。所谓CPU负载指的是一段…

蔡春久:主数据标准化如何建设

亿信华辰「2023数字赋能季」主数据管理专场第一期成功举办。我们邀请到了中国数据标准化及治理专家蔡春久为大家带来主数据管理从理论到工具层面的分享&#xff0c;全程干货&#xff0c;深度解读&#xff0c;以下是演讲全文。 蔡春久&#xff1a;中国大数据技术标准推进委员会…

认识AI三大类工具,让你效率加倍

在当今的互联网时代&#xff0c;各种新奇有趣的AI工具充斥着我们的生活。其中&#xff0c;AI写作、AI绘画、AI剪辑等工具更是给运营人带来了前所未有的“速成”体验。今天就来介绍一些好玩有趣的AI工具&#xff0c;让你在各个领域都能快速、轻松地展现自己的创意和才华。 第一…

Docker资源控制

一、CPU 资源控制 cgroups&#xff0c;是一个非常强大的linux内核工具&#xff0c;他不仅可以限制被 namespace 隔离起来的资源&#xff0c; 还可以为资源设置权重、计算使用量、操控进程启停等等。 所以 cgroups&#xff08;Control groups&#xff09;实现了对资源的配额和度…