6. 树的入门

news/2024/4/27 6:28:33/文章来源:https://blog.csdn.net/weixin_43830137/article/details/130323772

6. 树的入门

之前我们实现的符号表中,不难看出,符号表的增删查操作,随着元素个数N的增多,其耗时也是线性增多的,时间复杂度都是O(n),为了提高运算效率,接下来我们学习树这种数据结构。

6.1 树的基本定义

树是我们计算机中非常重要的一种数据结构,同时使用树这种数据结构,可以描述现实生活中的很多事物,例如家

谱、单位的组织架构、等等。

树是由n(n>=1)个有限结点组成一个具有层次关系的集合。把它叫做“树”是因为它看起来像一棵倒挂的树,也就

是说它是根朝上,而叶朝下的。

树具有以下特点:

1.每个结点有零个或多个子结点;

2.没有父结点的结点为根结点;

3.每一个非根结点只有一个父结点;

4.每个结点及其后代结点整体上可以看做是一棵树,称为当前结点的父结点的一个子树;

6.2 树的相关术语

结点的度:

一个结点含有的子树的个数称为该结点的度;

叶结点:

度为0的结点称为叶结点,也可以叫做终端结点

分支结点:

度不为0的结点称为分支结点,也可以叫做非终端结点

结点的层次:

从根结点开始,根结点的层次为1,根的直接后继层次为2,以此类推

结点的层序编号:

将树中的结点,按照从上层到下层,同层从左到右的次序排成一个线性序列,把他们编成连续的自然数。

树的度:

树中所有结点的度的最大值

树的高度(深度):

树中结点的最大层次

森林:

m(m>=0)个互不相交的树的集合,将一颗非空树的根结点删去,树就变成一个森林;给森林增加一个统一的根

结点,森林就变成一棵树。

孩子结点:

一个结点的直接后继结点称为该结点的孩子结点

双亲结点(父结点):

一个结点的直接前驱称为该结点的双亲结点

兄弟结点:

同一双亲结点的孩子结点间互称兄弟结点

6.3 二叉树的基本定义

二叉树就是度不超过2的树(每个结点最多有两个子结点)

满二叉树:

一个二叉树,如果每一个层的结点树都达到最大值,则这个二叉树就是满二叉树。

完全二叉树:

叶节点只能出现在最下层和次下层,也就是说除了最下层别的层都是满的,并且最下面一层的结点都集中在该层最左边的若干位置的二叉树。

6.4 二叉查找树的创建

6.4.1 二叉树的结点类

根据对图的观察,我们发现二叉树其实就是由一个一个的结点及其之间的关系组成的,按照面向对象的思想,我们

设计一个结点类来描述结点这个事物。

结点类API设计:

代码实现:

package com.ynu.Java版算法.U6_树的入门.T4_二叉查找树的创建.S1_二叉树的节点类;// 二叉树的节点类
//  存放的是键值对
public class Node<Key,Value> {//存储键public Key key;//存储值   -- 值是私有的 不能直接 对象+点 访问private Value value;//记录左子结点public Node left;//记录右子结点public Node right;public Node() {}public Node(Key key, Value value, Node left, Node right) {this.key = key;this.value = value;this.left = left;this.right = right;}
}

6.4.2 二叉查找树API设计

6.4.3 二叉查找树实现

查询方法get实现思想:

从根节点开始:

  1. 如果要查询的key小于当前结点的key,则继续找当前结点的左子结点;

  2. 如果要查询的key大于当前结点的key,则继续找当前结点的右子结点;

  3. 如果要查询的key等于当前结点的key,则树中返回当前结点的value。

删除方法delete实现思想:

  1. 找到被删除结点;

  2. 找到被删除结点右子树中的最小结点minNode

  3. 让被删除结点的左子树成为最小结点minNode的左子树,让被删除结点的右子树成为最小结点minNode的右子树

  4. 让被删除结点的父节点指向最小结点minNode

二叉树的增删改查都和递归有关系。

package com.ynu.Java版算法.U6_树的入门.T4_二叉查找树的创建.S2_二叉查找树API设计;public class BinaryTree<Key extends Comparable<Key>,Value> {//记录根结点private Node root;//记录树中元素的个数private int N;// 获取树种元素的个数public int size(){return N;}//向当前的树x中添加key-value,并返回添加元素后新的树public void put(Key key,Value value){root = put(root,key,value);}// 增//向指定的树x中添加key-value,并返回添加元素后新的树public Node put(Node node,Key key,Value value){// 这就是递归的出口if (node==null){N++;  //树的节点个数加1return new Node(key,value,null,null);}int cmp = key.compareTo(node.key);if (cmp>0){//新结点的key大于当前结点的key,继续找当前结点的右子结点// 下一层递归node.right = put(node.right, key, value);}else if (cmp < 0){//新结点的key小于当前结点的key,继续找当前结点的左子结点// 下一层递归node.left = put(node.left,key,value);}else {//新结点的key等于当前结点的key,修改值node.value = value;}return node;}//查询当前树中指定key对应的valuepublic Value get(Key key){return get(root,key);}//从指定的树x中,查找key对应的值public Value get(Node node,Key key){// 递归出口if (node==null){return null;}int cmp = key.compareTo(node.key);if (cmp>0){//如果要查询的key大于当前结点的key,则继续找当前结点的右子结点;return get(node.right,key);}else if (cmp<0){//如果要查询的key小于当前结点的key,则继续找当前结点的左子结点;return get(node.left,key);}else {//如果要查询的key等于当前结点的key,则树中返回当前结点的value。return node.value;}}//删除树中key对应的valuepublic void delete(Key key){root = delete(root,key);}//删除指定树中key对应的valuepublic Node delete(Node node,Key key){if (node==null){return null;}int cmp = key.compareTo(node.key);if (cmp>0){//新结点的key大于当前结点的key,继续找当前结点的右子结点return delete(node.right,key);}else if (cmp<0){//新结点的key小于当前结点的key,继续找当前结点的左子结点return delete(node.left,key);}else {//新结点的key等于当前结点的key,当前x就是要删除的结点//1.如果当前结点的右子树不存在,则直接返回当前结点的左子结点if (node.left==null){return node.right;}//2.如果当前结点的左子树不存在,则直接返回当前结点的右子结点if (node.right==null){return node.left;}// 都不存在其实就是返回空了 无需单独判断// 3.左右节点都存在找到右子树中的最小节点替换当前节点// 3.1 去寻找右子树的最小节点Node pre = node;   // 记录最小节点的父节点Node minNode = node.right;while (minNode.left!=null){pre = minNode;minNode = minNode.left;}//3.2 删除右子树的最小节点并且替换到当前节点pre.left = null;minNode.left = node.left;minNode.right = node.right;node = minNode;N--;}return node;}private class Node {//存储键public Key key;//存储值   -- 值是私有的 不能直接 对象+点 访问private Value value;//记录左子结点public Node left;//记录右子结点public Node right;public Node() {}public Node(Key key, Value value, Node left, Node right) {this.key = key;this.value = value;this.left = left;this.right = right;}}@Overridepublic String toString() {StringBuilder sb = new StringBuilder();return "BinaryTree{}";}
}
package com.ynu.Java版算法.U6_树的入门.T4_二叉查找树的创建.S2_二叉查找树API设计;public class Main {public static void main(String[] args) {BinaryTree<Integer,String> binaryTree = new BinaryTree<>();binaryTree.put(10,"ybh");binaryTree.put(9,"ybh");binaryTree.put(12,"lmj");binaryTree.put(7,"czx");binaryTree.put(25,"lh");binaryTree.put(11,"lh");System.out.println(binaryTree.size());binaryTree.delete(12);System.out.println(binaryTree.size());}
}

6.4.4 二叉查找树其他便捷方法

6.4.4.1 查找二叉树中最小的键

在某些情况下,我们需要查找出树中存储所有元素的键的最小值,比如我们的树中存储的是学生的排名和姓名数

据,那么需要查找出排名最低是多少名?这里我们设计如下两个方法来完成:

6.5 二叉树的基础遍历

很多情况下,我们可能需要像遍历数组数组一样,遍历树,从而拿出树中存储的每一个元素,由于树状结构和线性

结构不一样,它没有办法从头开始依次向后遍历,所以存在如何遍历,也就是按照什么样的搜索路径进行遍历的问

题。

我们把树简单的画作上图中的样子,由一个根节点、一个左子树、一个右子树组成,那么按照根节点什么时候被访

问,我们可以把二叉树的遍历分为以下三种方式:

  1. 前序遍历;

    先访问根结点,然后再访问左子树,最后访问右子树

  2. 中序遍历; 中序遍历二叉查找树得到的是升序序列

    先访问左子树,中间访问根节点,最后访问右子树

  3. 后序遍历;

    先访问左子树,再访问右子树,最后访问根节点

如果我们分别对下面的树使用三种遍历方式进行遍历,得到的结果如下:

6.5.1 前序遍历

我们在6.4中创建的树上,添加前序遍历的API:

public Queue <Key> preErgodic():使用前序遍历,获取整个树中的所有键

private void preErgodic(Node x,Queue <Key> keys):使用前序遍历,把指定树x中的所有键放入到keys队列中实现过程中,我们通过前序遍历,把每个结点的键取出,放入到队列中返回即可。

// 前序遍历指定的树private void preErgodic(Node node){if (node==null){return;}queue.add(node.key); //遍历当前节点// 遍历左子树if (node.left!=null){preErgodic(node.left);}// 遍历右子树if (node.right!=null){preErgodic(node.right);}}

6.5.1 中序遍历

我们在6.4中创建的树上,添加前序遍历的API:

public Queue<Key> midErgodic():使用中序遍历,获取整个树中的所有键

private void midErgodic(Node x,Queue<Key> keys):使用中序遍历,把指定树x中的所有键放入到keys队列中 。

// 中序遍历整棵树public Queue<Key> midErgodic(){queue.clear();midErgodic(root);return queue;}// 中序遍历整棵树public void midErgodic(Node node){if (node==null){return;}if (node.left!=null){midErgodic(node.left);}queue.add(node.key);if (node.right!=null){midErgodic(node.right);}}

6.5.3 后序遍历

我们在6.4中创建的树上,添加前序遍历的API:

public Queue<Key> afterErgodic():使用后序遍历,获取整个树中的所有键

private void afterErgodic(Node x,Queue<Key> keys):使用后序遍历,把指定树x中的所有键放入到keys队列中。

// 后序遍历这整棵树public Queue<Key> afterErgodic(){queue.clear();afterErgodic(root);return queue;}public void afterErgodic(Node node){if (node==null){return;}if (node.left!=null){afterErgodic(node.left);}if (node.right!=null){afterErgodic(node.right);}queue.add(node.key);}

6.6 二叉树的层序遍历

所谓的层序遍历,就是从根节点(第一层)开始,依次向下,获取每一层所有结点的值,有二叉树如下:

那么层序遍历的结果是:EBGADFHC

我们在6.4中创建的树上,添加层序遍历的API:

public Queue<Key> layerErgodic():使用层序遍历,获取整个树中的所有键

实现步骤:

1.创建队列,存储每一层的结点;

2.使用循环从队列中弹出一个结点:

2.1 获取当前结点的key;

2.2 如果当前结点的左子结点不为空,则把左子结点放入到队列中

2.3 如果当前结点的右子结点不为空,则把右子结点放入到队列中

 // 层次遍历public Queue<Key> layerErgodic(){Queue<Key> keys  = new LinkedList<>();Queue<Node> nodes  = new LinkedList<>();nodes.add(root);while (!nodes.isEmpty()){Node x = nodes.poll();keys.add(x.key);if (x.left!=null){nodes.add(x.left);}if (x.right!=null){nodes.add(x.right);}}return keys;}

6.7 二叉树的最大深度问题

需求:

给定一棵树,请计算树的最大深度(树的根节点到最远叶子结点的最长路径上的结点数);

上面这棵树的最大深度为4。

实现:

我们在6.4中创建的树上,添加如下的API求最大深度:

public int maxDepth(): 计算整个树的最大深度

private int maxDepth(Node x): 计算指定树x的最大深度

// 计算整个树的最大深度
public int maxDepth(){return maxDepth(root);
}// 计算指定树x的最大深度
private int maxDepth(Node x){//1.如果根结点为空,则最大深度为0;if (x==null){return 0;}int maxL = 0;int maxR = 0;//2.计算左子树的最大深度;if (x.left!=null){maxL = maxDepth(x.left);}//3.计算右子树的最大深度;if (x.right!=null){maxR = maxDepth(x.right);}//4.当前树的最大深度=左子树的最大深度和右子树的最大深度中的较大者+1return maxL>maxR?maxL+1:maxR+1;}package com.ynu.Java版算法.U6_树的入门.T6_二叉树的层次遍历;
import java.util.Queue;public class Main {public static void main(String[] args) {BinaryTree<Integer, String> bt = new BinaryTree<>();bt.put(1,"A");bt.put(5,"A");bt.put(4,"A");bt.put(6,"A");bt.put(2,"A");bt.put(3,"A");bt.put(8,"A");bt.put(7,"A");// 树的高度System.out.println(bt.maxDepth());}
}

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

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

相关文章

PerformanceTest, monitoring command

PerformanceTest, monitoring command 1、数据库 #查看最大连接数 show variables like max_connections; #例如:查看mysql连接数 show status like Threads%; 说明: threads_cached //查看线程缓存内的线程的数量 threads_connected //查看当前打开的连接的数量(打开的…

Pytorch的几种常用优化器

文章目录 AdagradSGDRMSpropAdamAdamW Adagrad Adagrad是一种可以自动调节每个参数更新的梯度的优化器&#xff0c;也可以做到在梯度平缓时走的步长大&#xff0c;在梯度小时走的步长小&#xff0c;从而防止loss出现剧烈震荡的情况。这里默认你已知道了他的原理了&#xff0c;…

离散数学-考纲版-01-命题逻辑

文章目录 1. 命题逻辑的等值演算与推理演算参考1.1 命题1.2 常用联结词1.3 命题公式命题公式的分类-重言式-矛盾式-可满足式等价关系式-逻辑等价 logically equivalent 1.4 命题的等值演算与推理基本等价式逻辑蕴涵重言式 logically implication重言蕴涵推到归结法 1.5 命题公式…

机器学习——SVM的易错题型

问&#xff1a;支持向量机仅可以用于处理二分类任务 答&#xff1a;错误。支持向量机可以用于处理多分类任务&#xff0c;通过使用一对多或一对一的方法&#xff0c;将多个类别分别与其他类别做二分类。也可以使用多类支持向量机算法&#xff0c;直接将多个类别一起纳入训练和…

3、Typescript中补充的六个类型

1、元组 元组可以看做是数组的拓展&#xff0c;它表示已知元素数量和类型的数组。确切地说&#xff0c;是已知数组中每一个位置上的元素的 类型&#xff0c;来看例子&#xff1a; let tuple: [string, number, boolean]; tuple ["a", 2, false]; tuple [2, "…

网络设备发现工具

什么是网络设备发现 网络设备发现是识别和映射网络基础架构&#xff08;如路由器、交换机、集线器、防火墙、无线接入点、服务器、虚拟机等&#xff09;中存在的设备和接口的过程。网络发现是网络管理的第一步&#xff0c;也是成功监控解决方案的关键。该过程不仅涉及发现网络…

LINUX SVN 新建项目

从第三方代码创建代码库&#xff1a; 1、通过客户端进入服务端 2、在对应的目录创建新的项目/目录 在对应的目录右击 &#xff1a;creat folder... 例&#xff1a;创建testSvn 3、在客户端checkout(co) testSvn 4、将第三方源码(srcTest)拷贝到客户端下的对应路径 防止L…

Cesium 实战-最新版(1.104.0)通过异步方式初始化地球,加载影像以及高程图层

Cesium 实战-最新版&#xff08;1.104.0&#xff09;通过异步方式初始化地球&#xff0c;加载影像以及高程图层 遇到问题初始化底图初始化高程&#xff08;监听载入完成事件&#xff0c;开启关闭高程&#xff09;初始化 3dtile在线示例 Cesium 最新版&#xff08;1.104.0&#…

DIN11系列 大电流输出信号隔离模块线性驱动器0~100mA/0~500mA/0~2A/0-4A

主要特性 精度、线性度误差等级&#xff1a; 0.1、0.2、0.5 级4-20mA/0-5V/0-10V 等标准信号输入0~100mA/0~500mA/0~1A/0-5A 等电流信号输出0~1V(max 5A)/0~10V/0-24V(max 5A) 等电压信号输出信号输入/信号输出 3000VDC 隔离辅助电源&#xff1a;12V、15V 或 24V 直流单电源供…

NeRFStudio系列 Part 1:PipeLines概述

前言&#xff1a;Why NeRFStudio? NeRF社区是近两年来计算机领域最活跃的学术社区之一&#xff0c;各种具有milestone意义的算法层出不穷&#xff0c;各位作者的开源工作也做得非常扎实&#xff0c;非常多的工作都自带了code、data、project page。 但是后继者想要在这些伟大的…

jenkins自动化部署配置

文章目录 1. jenkins 插件安装2. 配置2.1 全局工具配置2.2 全局配置2.2.1 gitee 配置 3. 创建任务添加gitee ssh jenkins 开机自启动 1. jenkins 插件安装 ant Build Failure AnalyzerBuild Monitor ViewBuild Timeout dockerEmail Extension Plugin giteegithubgradle javama…

Python科研数据可视化

在过去的20 年中&#xff0c;随着社会产生数据的大量增加&#xff0c;对数据的理解、解释与决策的需求也随之增加。而固定不变是人类本身&#xff0c;所以我们的大脑必须学会理解这些日益增加的数据信息。所谓“一图胜千言”&#xff0c;对于数量、规模与复杂性不断增加的数据&…

广域通信网 - 流量控制(停等协议、滑动窗口协议)

文章目录 1 概述2 流量控制协议2.1 停等协议2.2 滑动窗口协议 1 概述 #mermaid-svg-c9cNIYsOvLpoO4AV {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-c9cNIYsOvLpoO4AV .error-icon{fill:#552222;}#mermaid-svg-c9c…

跳槽必备,全面总结Android面试知识点

在最近的 Android 开发&#xff08;社招&#xff09;面试中总结的 Android 基础知识点&#xff0c;已经拿到心仪的offer&#xff0c;回馈同学们&#xff0c;感谢其他大佬的分享。 Android中大厂面试都很重视基础知识的考察&#xff0c;面试前不仅要熟悉这些知识点&#xff0c;…

设计模式之访问者模式(C++)

作者&#xff1a;翟天保Steven 版权声明&#xff1a;著作权归作者所有&#xff0c;商业转载请联系作者获得授权&#xff0c;非商业转载请注明出处 一、访问者模式是什么&#xff1f; 访问者模式是一种行为型的软件设计模式&#xff0c;表示一个作用于某对象结构中的各元素的操作…

智能家居代码架构---简单工厂模式

(11条消息) 智能家居 (10) ——人脸识别祥云平台编程使用(编译libcurl库支持SSL&#xff0c;安装SSL依赖库libssl、libcrypto)openssl 依赖库行稳方能走远的博客-CSDN博客 看上面这个博客的往期文章 代码设计经验的总结&#xff0c;稳定&#xff0c;拓展性更强。一系列编程思…

DAX:概述ALL函数

简单的说&#xff0c;当ALL用作表函数时&#xff0c;忽略应用到表上的任何过滤器&#xff0c;并返回数据表&#xff1b;当ALL用作CALCULATE和CALCULATETABLE函数中修饰器时&#xff0c;ALL函数从扩展表中移除已经应用的过滤上下文。 注意自动存在(auto-eixist)对ALL()函数的影响…

选址-路径问题(Location-Routing Problem, LRP)

今天为大家介绍的是选址-路径问题(Location-Routing Problem, LRP)&#xff0c;首先上目录 目录 问题简介 基础模型、扩展问题及应用 算法 参考文献 1 问题简介 为了更好地了解这个问题&#xff0c;我们不妨当一波老板。 想象一下我们是经营一家口罩生产企业的老板&am…

案例——数据表的基本操作

目录 案例目的&#xff1a; 创建表&#xff1a; 创建offices&#xff1a; 创建employees表&#xff1a; 修改表&#xff1a; 将 employees 的 mobile 字段移动到 officeCode 字段后&#xff1a; 将 birth 字段名称改为 employee_birth: 修改 sex 字段&#xff0c;数据类…

Vue的路由实现:hash模式 和 history模式原理及区别

目录标题 1、hash模式2、history模式 Vue-Router有两种模式: ** hash 模式和 history**模式。默认的路由模式是hash模式。 1、hash模式 简介&#xff1a;hash模式是开发中默认的模式&#xff0c;它的URL带着一个#&#xff0c;例如:http://www.abc.com/#/vue&#xff0c;它的…