树(tree)

  • 树(Tree)的基本概念
    • 定义
    • 树的结构
      • 二叉树
      • 二叉树的特点
      • 满二叉树
      • 完全二叉树
      • 二叉查找树(Binary Search Tree - BST,又称二叉排序树、二叉搜索树)
      • AVL树
  • 二叉树的存储结构
    • 二叉树的顺序存储:
    • 二叉树的链式存储结构
  • 遍历二叉树的算法
  • 层次遍历
  • 普通遍历

树(Tree)的基本概念

定义

树是由结点或顶点和边组成的(可能是非线性的)且不存在着任何环的一种数据结构。没有结点的树称为空(null或empty)树。一棵非空的树包括一个根结点,还(很可能)有多个附加结点,所有结点构成一个多级分层结构
例如:

图 1(A) 是使用树结构存储的集合 {A,B,C,D,E,F,G,H,I,J,K,L,M} 的示意图。对于数据 A 来说,和数据 B、C、D 有关系;对于数据 B 来说,和 E、F 有关系。这就是“一对多”的关系。

将具有“一对多”关系的集合中的数据元素按照图 1(A)的形式进行存储,整个存储形状在逻辑结构上看,类似于实际生活中倒着的树(图 1(B)倒过来),所以称这种存储结构为“树型”存储结构。

树的结构

结点:使用树结构存储的每一个数据元素都被称为“结点”。例如,图 1(A)中,数据元素 A 就是一个结点;

父结点(双亲结点)、子结点和兄弟结点:对于图 1(A)中的结点 A、B、C、D 来说,A 是 B、C、D 结点的父结点(也称为“双亲结点”),而 B、C、D 都是 A 结点的子结点(也称“孩子结点”)。对于 B、C、D 来说,它们都有相同的父结点,所以它们互为兄弟结点。

树根结点(简称“根结点”):每一个非空树都有且只有一个被称为根的结点。图 1(A)中,结点A就是整棵树的根结点。
树根的判断依据为:如果一个结点没有父结点,那么这个结点就是整棵树的根结点。
叶子结点:如果结点没有任何子结点,那么此结点称为叶子结点(叶结点)。例如图 1(A)中,结点 K、L、F、G、M、I、J 都是这棵树的叶子结点。此外还有以下属性:

节点的度(degree):该节点子树的个数称为该节点的度。
树的度:所有节点中,度的最大值称为树的度。
非叶子节点:度不为零的节点。
高度(height):当前节点到最远叶子节点的路径长,所有树叶的高度为零。
深度(depth):对于任意节点n,n的深度为从根到n的唯一路径长。有些地方认为根深度为0,有些地方认为根深度为1。
兄弟节点:具有相同父节点的节点互相称为兄弟节点。
节点的层数(level):从根开始定义,根为第一层,根的子节点为第二层。以此类推。
堂兄弟节点:父节点在同一层的节点互为堂兄弟。
节点的祖先(ancestor):从根到该节点所经分支上的所有节点。
子孙(descendant):以某节点为根的子树中任一节点都称为该节点的子孙。
森林:由m(m >= 0)棵互不相交的树的集合称为森林。

一般来说数据结构如下(Java)


public class TreeNode<T> {public T value;public TreeNode<T> leftNode;public TreeNode<T> rightNode;//public List<TreeNode<T>> nodes;
}

二叉树

二叉树是每个节点最多有两个子树的树结构,左侧子树节点称为“左子树”(left subtree),右侧子树节点称为“右子树”(right subtree)。每个节点最多有2个子节点的树(即每个定点的度小于3)

二叉树的特点

至少有一个节点(根节点)

每个节点最多有两颗子树,即每个节点的度小于3。

左子树和右子树是有顺序的,次序不能任意颠倒。

即使树中某节点只有一棵子树,也要区分它是左子树还是右子树

满二叉树

除了叶子节点外每一个节点都有两个子节点,且所有叶子节点都在二叉树的同一高度上

完全二叉树

如果二叉树中除去底层节点后为满二叉树,且底层节点依次从左到右分布,则此二叉树被称为完全二叉树。

二叉查找树(Binary Search Tree - BST,又称二叉排序树、二叉搜索树)

二叉查找树根节点的值大于其左子树中任意一个节点的值,小于其右子树中任意一节点的值,且该规则适用于树中的每一个节点。


二叉查找树的查询效率介于O(log n)~O(n)之间,理想的排序情况下查询效率为O(log n),极端情况下BST就是一个链表结构(如下图),此时元素查找的效率相等于链表查询O(n)。

二叉查找树需要注意的是删除节点操作时的不同情况,删除节点根据节点位置会有以下三种情况:

删除节点的度为0,则直接删除

删除节点的度为1,则该子节点替代删除节点

删除节点的度为2,则从左子树中寻找值最大的节点替代删除节点。对树结构改动最少、节点值最进行删除节点值的必然是左子树中的最大叶子节点值与右子树中的最小叶子节点值
平衡二叉搜索树 (Balanced binary search trees,又称AVL树、平衡二叉查找树)

AVL树

AVL树是最早被发明的自平衡二叉搜索树,树中任一节点的两个子树的高度差最大为1,所以它也被称为高度平衡树,其查找、插入和删除在平均和最坏情况下的时间复杂度都是O(log n)。

平衡二叉搜索树由Adelson-Velskii和Landis在1962年提出,因此又被命名为AVL树。平衡因子(平衡系数)是AVL树用于旋转平衡的判断因子,某节点的左子树与右子树的高度(深度)差值即为该节点的平衡因子。

AVL树的特点

具有二叉查找树的特点(左子树任一节点小于父节点,右子树任一节点大于父节点),任何一个节点的左子树与右子树都是平衡二叉树

任一节点的左右子树高度差小于1,即平衡因子为范围为[-1,1] 如上左图根节点平衡因子=1,为AVL树;右图根节点平衡因子=2,固非AVL树,只是BST。
为什么选择AVL树而不是BST?

大多数BST操作(如搜索、最大值、最小值、插入、删除等)的时间复杂度为O(h),其中h是BST的高度。对于极端情况下的二叉树,这些操作的成本可能变为O(n)。如果确保每次插入和删除后树的高度都保持O(log n),则可以保证所有这些操作的效率都是O(log n)。

二叉树的存储结构

二叉树的存储结构有两种,分别为顺序存储和链式存储。本节先介绍二叉树的顺序存储结构。

二叉树的顺序存储:

指的是使用顺序表(数组)存储二叉树。需要注意的是,顺序存储只适用于完全二叉树。换句话说,只有完全二叉树才可以使用顺序表存储。因此,如果我们想顺序存储普通二叉树,需要提前将普通二叉树转化为完全二叉树。
有读者会说,满二叉树也可以使用顺序存储。要知道,满二叉树也是完全二叉树,因为它满足完全二叉树的所有特征。

普通二叉树转完全二叉树的方法很简单,只需给二叉树额外添加一些节点,将其"拼凑"成完全二叉树即可。如图 1 所示:

左侧是普通二叉树,右侧是转化后的完全(满)二叉树。
完全二叉树的顺序存储,仅需从根节点开始,按照层次依次将树中节点存储到数组即可



存储由普通二叉树转化来的完全二叉树也是如此,例如上图普通二叉树,可以如此存储:

二叉树的链式存储结构


此为一棵普通的二叉树,若将其采用链式存储,则只需从树的根节点开始,将各个节点及其左右孩子使用链表存储即

遍历二叉树的算法

层次遍历

前面讲过,树是有层次的,拿图 1 来说,该二叉树的层次为 3。通过对树中各层的节点从左到右依次遍历,即可实现对正棵二叉树的遍历,此种方式称为层次遍历。

代码实现:

 public static void leverErgodic(TreeNode root) {if (root == null) return;LinkedList<TreeNode<Integer>> list = new LinkedList<TreeNode<Integer>>();list.add(root);TreeNode currentNode;while (!list.isEmpty()) {currentNode = list.poll();System.out.println(((Integer) currentNode.value).intValue());if (currentNode.leftNode != null) {list.add(currentNode.leftNode);}if (currentNode.rightNode != null) {list.add(currentNode.rightNode);}}}

普通遍历

其实,还有一种更普通的遍历二叉树的思想,即按照 “从上到下,从左到右” 的顺序遍历整棵二叉树。

普通遍历又可以分为:
中序遍历:即左-根-右遍历,对于给定的二叉树根,寻找其左子树;对于其左子树的根,再去寻找其左子树;递归遍历,直到寻找最左边的节点i,其必然为叶子,然后遍历i的父节点,再遍历i的兄弟节点。随着递归的逐渐出栈,最终完成遍历
先序遍历:即根-左-右遍历
后序遍历:即左-右-根遍历

先序遍历:

 //先序遍历public static void preTraver(TreeNode root) {if (null != root) {System.out.print(root.value.toString());preTraver(root.leftNode);preTraver(root.rightNode);}}

中序遍历:

  //中序遍历public static void midTraver(TreeNode root) {if (null != root) {midTraver(root.leftNode);System.out.print(root.value.toString());midTraver(root.rightNode);}}

后序遍历

 //后序遍历public static void lastTraver(TreeNode root) {if (null != root) {lastTraver(root.leftNode);lastTraver(root.rightNode);System.out.print(root.value.toString());}}

二叉查找树-插入

  //二叉 查找树 插入public static void insertNode(TreeNode<Integer> insert, TreeNode<Integer> root) {if (insert.value.intValue() == root.value.intValue()) {return;} else if (insert.value.intValue() > root.value.intValue()) { //大于根结点 ,右侧if (insert.rightNode == null) {insert.rightNode = insert;return;} else {insertNode(insert, root.rightNode);}} else {if (insert.leftNode == null) {insert.leftNode = insert;return;} else {insertNode(insert, root.leftNode);}}}

删除节点存在 3 种情况,分别如下:

  1. 没有左右子节点,可以直接删除
  2. 存在左节点或者右节点,删除后需要对子节点移动
  3. 同时存在左右子节点,不能简单的删除,但是可以通过和后继节点交换后转换为前两种情况
    实现代码如下:
 /*** 获取后继节点** @param node* @return*/public TreeNode getNode(TreeNode node) {if (!node.hR()) {return null; //无后继}TreeNode temp = node.rightNode;while (temp.hL()) {temp = temp.leftNode;}return temp;}TreeNode root;/*** 非相邻节点的替换逻辑(非相邻加粗!)** @param node    被替换节点* @param replace 替换的节点*/public void replaceNode(TreeNode node, TreeNode replace) {if (node.isLeft) {node.fNode.leftNode = replace;} else if (node.isRight) {node.fNode.rightNode = replace;} else {//根结点root = replace;}node.leftNode.fNode = node.rightNode.fNode = replace;replace.fNode = node.fNode;replace.leftNode = node.leftNode;replace.rightNode = node.rightNode;}public void deleteNode(TreeNode node, TreeNode root) {if (node.hL() && !node.hR()) {// 只有左节点if (node.isLeft) {node.fNode.leftNode = node.leftNode;} else if (node.isRight) {node.fNode.rightNode = node.leftNode;} else// 待删除节点是根节点root = node.leftNode;node.leftNode.fNode = node.fNode;} else if (node.hR() && !node.hL()) {// 只有右节点if (node.isLeft) {node.fNode.leftNode = node.rightNode;} else if (node.isRight) {node.fNode.rightNode = node.rightNode;} else// 待删除节点是根节点root = node.rightNode;node.rightNode.fNode = node.rightNode;} else if (node.hL() && node.hR()) {// 有左右子节点TreeNode sNode = getNode(node);if (sNode == node.rightNode) {// 后继节点是右子节点sNode.fNode = node.fNode;if (node.isLeft)node.fNode.leftNode = sNode;else if (node.isRight)node.fNode.rightNode = sNode;else {// 是根节点sNode = root;}sNode.fNode = node.fNode;sNode.leftNode = node.leftNode;node.leftNode.fNode = sNode;} else {// 后继节点是右子节点的最左子节点if (sNode.hR()) {// 左子节点有右子树sNode.fNode.leftNode = sNode.rightNode;sNode.rightNode.fNode = sNode.fNode;replaceNode(node, sNode);} else {// 左子节点没有右子树// 叶节点,直接删除sNode.fNode.leftNode = null;replaceNode(node, sNode);}}} else {// 没有子节点if (node.isLeft) {node.fNode.leftNode = null;} else if (node.isRight) {node.fNode.rightNode = null;}}node = null;}

数据结构树(Tree)详解相关推荐

  1. 数据结构之树结构详解

    树的定义 树是一种很特别的数据结构,树这种数据结构叫做"树"就是因为它长得像一棵树.但是这棵树画成的图长得却是一棵倒着的树,根在上,叶在下. 树是图的一种,树和图的区别就在于:树是 ...

  2. BTree和B+Tree详解结构

    如果是复合索引: 关键字的排序先排左侧字段,在左侧字段相同的情况下,再排序右侧字段. BTree和B+Tree详解 B+树索引是B+树在数据库中的一种实现,是最常见也是数据库中使用最为频繁的一种索引. ...

  3. B-Tree 和 B+Tree详解

    B-Tree 和 B+Tree详解 一.什么是B-Tree 1.B-树插入 2.B-树删除 3.总结 二.什么是B+Tree 1.B+树插入 2.B+树删除 3.总结 一.什么是B-Tree B-Tr ...

  4. 哈夫曼树(霍夫曼树)-详解

    哈夫曼树(霍夫曼树)-详解 哈夫曼树(霍夫曼树)-详解 何为权值?我们看下百度百科的解释. 何为路径? 何为路径长度? 何为树的路径长度? 何为结点的带权路径长度? 何为树的带权路径长度(WPL)? ...

  5. 数据结构与算法详解目录

    数据结构与算法详解是一本以实例和实践为主的图书,主要是经典的数据结构与常见算法案例,来自历年考研.软考等考题,有算法思路和完整的代码,最后提供了C语言调试技术的方法. 后续配套微课视频. 第0章  基 ...

  6. 数据结构--图(Graph)详解(三)

    数据结构–图(Graph)详解(三) 文章目录 数据结构--图(Graph)详解(三) 一.深度优先生成树和广度优先生成树 1.铺垫 2.非连通图的生成森林 3.深度优先生成森林 4.广度优先生成森林 ...

  7. 数据结构--图(Graph)详解(二)

    数据结构–图(Graph)详解(二) 文章目录 数据结构--图(Graph)详解(二) 一.图的存储结构 1.图的顺序存储法 2.图的邻接表存储法 3.图的十字链表存储法 4.图的邻接多重表存储法 二 ...

  8. 数据结构--图(Graph)详解(一)

    数据结构–图(Graph)详解(一) 文章目录 数据结构--图(Graph)详解(一) 一.图的基本概念 1.图的分类 2.弧头和弧尾 3.入度和出度 4.(V1,V2) 和 < V1,V2 & ...

  9. 二叉树合集(二):霍夫曼树(图文详解)

    合集地址 二叉树合集(一):二叉树基础(含四种遍历,图文详解) 二叉树合集(二):霍夫曼树(图文详解) 二叉树合集(三):线索二叉树(图文详解) 二叉树合集(四):对称二叉树(递归和迭代实现) 二叉树 ...

最新文章

  1. redis怎么不让存byte_redis用bitfield存储的问题
  2. 多语言软件gettext解决方案weix_图像编辑软件 Aurora HDR 2019 多语言免费版
  3. 看服务器是不是虚拟机
  4. <马哲>劳动价值论的理论及实践意义
  5. html怎么转换undefined,JavaScript之Undefined详解
  6. 爬虫数据executemany插入_金融数据的获取——一个爬虫的简单例子
  7. mybatis-plus实现自动填充数据如:数据库中createTime和updateTime
  8. Spring MVC应用@Autowired和@Service进行依赖注入
  9. 基于昇腾处理器的目标检测应用(ACL)
  10. C++中的extern C【转】
  11. 《21天学通C语言》总结(2)
  12. cad设计师证书怎么考
  13. 测试用例设计方法大全下——场景法、、错误推测法、正交法
  14. 计算机如何算幂函数,幂函数(乘方)|指数(函数)|对数(函数)|及其运算法则...
  15. MySQL中int(M)和tinyint(M)数值类型中M值的意义
  16. openbsd mysql_在OpenBSD上运行MyDNSNameservers(MySQL / PHP + MyDNS + MyDNSConfig)
  17. scp ? stall ? scp -t ? scp -f ? MTU
  18. vue工作日历考勤记录表
  19. python适合小白学吗_有没有适合零基础小白学习的python课程?
  20. 笔记本电脑开机到登入页面扩展显示器和电脑突然黑屏很久才显示

热门文章

  1. javaWeb邮件发送原理及实现
  2. 云桌面服务器作用,云桌面服务器所需要具备的特点
  3. 关于投影仪显示图像不稳定(抖动)
  4. 鸿蒙系统开放吗,鸿蒙系统开放时间敲定!6月2日正式发布,旗舰手机率先升级...
  5. android textView添加不同颜色的边框
  6. 软文营销,你了解了吗?
  7. 为何世人皆称春酿独好?
  8. matlab读mif文件,关于QuartusII里面调用MATLAB里生成的mif文件的一些问题(转)
  9. 一个元素跟随兄弟元素高度自适应
  10. python CV2 int8 和 float转换