当前位置: 首页 > news >正文

数据结构和算法之树形结构(1)

文章出处: 数据结构和算法之树形结构(1)  关注码农爱刷题,看更多技术文章!!

        树形结构是数据结构四种逻辑结构之一,也是被广泛使用的一种逻辑结构,它描述的是数据元素之间一对多的逻辑关系。树是一种非线性的数据结构,它是由n(n>=0)个有限结点组成一个具有层次关系的集合;它有一个起始节点,称之为根节点;从根节点往下逐层延伸,每个节点都可以有零个或多个子节点,有且只有一个父节点,根节点无父节点,形体像一棵倒挂的树,因而被称之为树形结构。 树形结构逻辑上有序的意思,就是从根节点往下延伸的顺序,因而和线性结构一样是有序结构。

       树形结构常用于表示具有层次关系的数据,例如文件系统、组织结构、目录结构等。它提供了一种便捷的方式来组织和访问数据。树形结构的应用非常广泛,例如在计算机科学中,树型结构被用于实现搜索算法(如二叉搜索树)、存储和检索数据(如B树、堆)、表达抽象语法树等。在现实生活中,树型结构也有很多应用,比如家谱、图书分类、产品组织关系等。

 一、基本概念

图片

 二、二叉树
      二叉树的定义

      二叉树是典型和常见的树形结构,也是最简单的树形结构。二叉树每个节点最多有两棵子树(二叉树中不存在度大于2的节点),并且二叉树的子树有左右之分,顺序不能颠倒。

     二叉树根据样式不同,还可以分为满二叉树和完全二叉树,例如下图:

图片

     若一棵树的层数为k,它总结点数是2^k-1,则这棵树就是满二叉树。上图编号2的树就是一棵满二叉树,该树4层节点数15 = 2^4-1;满二叉树的特点是叶子节点都在最底层,除了叶子节点,每个节点都有左右两个子节点。

      若一棵树的层数为k,它前k-1层的总结点数是2^(k-1)-1,第k层的结点按照从左往右的顺序排列,则这棵树就是完全二叉树。上图编号3的树就是一棵完全二叉树,该树4层前3层节点数7 = 2^(4-1)-1;完全二叉树的特点是:叶子节点都在最底下两层,最后一层的叶子节点都靠左排列,并且除了最后一层,其他层的节点个数都要达到最大值2。看下图识别完全二叉树和非完全二叉树的区别。

图片

      此外, 满二叉树一定是一棵完全二叉树,但完全二叉树不一定是满二叉树。 

      二叉树的存储结构

      二叉树在实际落地时,可以采用顺序存储结构和链式存储结构,关于两者的区别可以参看前面的文章数据结构和算法之基本概念。如果是采用链式存储结构,一个节点分为三部分,其中数据域存储数据,另外两个指针域,一个指向左边的子节点,一个指向右边的子节点,这是一个典型的链表结构(关于链表结构说明可以参看文章数据结构和算法之线性结构)。通常,这是二叉树常用的存储结构。

      如果采用顺序存储结构,通常是采用数组来实现。那数组是如何存储二叉树节点数据的呢?  其存储规则是:从根节点开始,逐层往下、从左至右依次存储,看下图能更形象地理解其存储规则:

图片

       从上图可以看出:如果节点 X 存储在数组中下标为 i 的位置,下标为 2 * i 的位置存储的就是左子节点,下标为 2 * i + 1 的位置存储的就是右子节点;反过来,下标为 i/2 的位置存储就是它的父节点。通过这种方式,我们只要知道根节点存储的位置(一般情况下,为了方便计算子节点,根节点会存储在下标为1的位置),这样就可以通过下标计算,把整棵树都串起来。

       二叉树的遍历

        二叉树的遍历通常有两种搜索遍历方法:深度优先遍历广度优先遍历。深度优先遍历(Depth-First Search,简称DFS)即优先深入地探索一个节点的某一条路径,直到该路径都已被探索完,然后再回溯到该节点,探索该节点另一条路径,以此类推;广度优先遍历(Breadth-First Search,简称BFS)则是同时对所有路径逐层进行探索。

      深度优先遍历根据遍历的顺序不同,又可以细分为三种不同的遍历顺序:前序遍历、中序遍历、后序遍历。三种遍历顺序定义如下:

图片

      无论哪种遍历顺序,左节点都要优先右节点遍历, 三种遍历顺序实际遍历结果以下图为例:

图片

       前序遍历结果是:ABDHIEJCFG; 中序遍历结果是:HDIBJEAFCG;后序遍历结果是:HIDJEBFGCA。代码层面,通常我们可以通过递归实现前述深度优先遍历的三种遍历顺序,代码如下:

public class Solution { private static class Node { public int data;  // 节点值public Node left;  // 左节点  public Node right;  // 右节点public Node(int data, Node left, Node right) { this.data = data; this.left = left; this.right = right; } } public static void preDfs(Node treeNode) { if (treeNode == null) { return; } System.out.println(treeNode.data);  // 访问节点本身preDfs(treeNode.left);  // 遍历左节点preDfs(treeNode.right);  // 遍历右节点} public static void middleDfs(Node treeNode) { if (treeNode == null) { return; }        middleDfs(treeNode.left);  // 遍历左节点System.out.println(treeNode.data);  // 访问节点本身middleDfs(treeNode.right);  // 遍历右节点} public static void lastDfs(Node treeNode) { if (treeNode == null) { return; }        lastDfs(treeNode.left);  // 遍历左节点      lastDfs(treeNode.right);  // 遍历右节点System.out.println(treeNode.data);  // 访问节点本身} 
}

       如果是广度优先遍历,上面二叉树图遍历的结果则是:ABCDEFGHIJ;广度优先遍历符合队列先进先出的特点,实现时可以考虑用队列,如下面代码:


public static void bfs(Node root) {if (root == null) {return;}LinkedList<Node> queue = new LinkedList<Node>();Node current = null;// 根节点入队queue.offer(root);// 左侧数的深度int leftNum = 0;// 右侧数的深度int rightNum = 0;// 只要队列中有元素,就可以一直执行,非常巧妙地利用了队列的特性while (!queue.isEmpty()) {// 出队队头元素 先进先出current = queue.poll();System.out.print("-->" + current.data);// 左子树不为空,入队if (current.left != null) {queue.offer(current.left);leftNum++;}// 右子树不为空,入队if (current.right != null) {queue.offer(current.right);rightNum++;}}System.out.println(rightNum + "\t" + leftNum);
}

       上述代码中Node类的定义与之前代码里的Node类一致。代码逻辑分析:先是根节点入队,第一次循环,根节点出队(第一层),然后是第二层左右节点入队;第二次循环,左节点出队,左节点左右两孙节点入队;第三次循环,右节出队,右节点左右两孙节点入队,至此二层节点全部出队;第四次循环,左节点左孙节点出队,其左右子节点再入队,以此类推。

       深度广度优先遍历和广度优先遍历不仅仅用于二叉树的遍历,事实上,两者是两种基本且重要的图与树的遍历算法,它们的应用场景和优缺点如下图表:

图片

       本章主要介绍了树形结构的基本概念和特点,以及二叉树的定义、存储结构和遍历算法,关于树形结构的深入知识将在后续文章继续介绍,烦请关注!!    

码农爱刷题

为计算机编程爱好者和从业人士提供技术总结和分享 !为前行者蓄力,为后来者探路!

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • SaaS架构:流程架构分析
  • web 动画库
  • C++第八节课 日期类的补充
  • Python开发深度学习常见安装包 error 解决
  • 【基于轻量型架构的WEB开发】【章节作业】
  • 上传富文本插入文件时报错:JSON parse error: Unexpected character解决办法
  • 半导体器件制造5G智能工厂数字孪生物联平台,推进制造业数字化转型
  • Paragon NTFS for Mac和Tuxera NTFS for Mac,那么两种工具有什么区别呢?
  • SpringBoot 入门实践
  • ConvexHull-凸包-原理-代码实现
  • 【pandas操作:如何写XLSX文档】
  • 游戏陪玩系统源码搭建教程,如何配置陪玩系统的第三方云储存
  • React + React Image支持图像的各种转换,如圆形、模糊等效果吗?
  • 【网络】TCP/IP 五层网络模型:网络层
  • python基础知识(六)--字典遍历、公共运算符、公共方法、函数、变量分类、参数分类、拆包、引用
  • 收藏网友的 源程序下载网
  • 【399天】跃迁之路——程序员高效学习方法论探索系列(实验阶段156-2018.03.11)...
  • 【402天】跃迁之路——程序员高效学习方法论探索系列(实验阶段159-2018.03.14)...
  • git 常用命令
  • Meteor的表单提交:Form
  • PV统计优化设计
  • Python学习笔记 字符串拼接
  • React+TypeScript入门
  • Spring Cloud Feign的两种使用姿势
  • win10下安装mysql5.7
  • WordPress 获取当前文章下的所有附件/获取指定ID文章的附件(图片、文件、视频)...
  • 从setTimeout-setInterval看JS线程
  • 读懂package.json -- 依赖管理
  • 飞驰在Mesos的涡轮引擎上
  • 技术胖1-4季视频复习— (看视频笔记)
  • 如何进阶一名有竞争力的程序员?
  • 探索 JS 中的模块化
  • 用Python写一份独特的元宵节祝福
  • ​业务双活的数据切换思路设计(下)
  • # 服务治理中间件详解:Spring Cloud与Dubbo
  • # 职场生活之道:善于团结
  • #Spring-boot高级
  • #常见电池型号介绍 常见电池尺寸是多少【详解】
  • ( 用例图)定义了系统的功能需求,它是从系统的外部看系统功能,并不描述系统内部对功能的具体实现
  • (4)STL算法之比较
  • (C++17) optional的使用
  • (附源码)python房屋租赁管理系统 毕业设计 745613
  • (附源码)ssm本科教学合格评估管理系统 毕业设计 180916
  • (十五)Flask覆写wsgi_app函数实现自定义中间件
  • (学习日记)2024.03.12:UCOSIII第十四节:时基列表
  • (一)u-boot-nand.bin的下载
  • (转)C#调用WebService 基础
  • (转)mysql使用Navicat 导出和导入数据库
  • .bat批处理出现中文乱码的情况
  • .mat 文件的加载与创建 矩阵变图像? ∈ Matlab 使用笔记
  • .NET delegate 委托 、 Event 事件,接口回调
  • .NET企业级应用架构设计系列之开场白
  • @Autowired标签与 @Resource标签 的区别
  • [ 常用工具篇 ] AntSword 蚁剑安装及使用详解
  • [.net 面向对象程序设计进阶] (19) 异步(Asynchronous) 使用异步创建快速响应和可伸缩性的应用程序...