数据结构与算法:树

目录

定义

结构

二叉树

定义

结构

形式

满二叉树

完全二叉树

存储

链式存储结构

数组

孩子节点

父节点

应用

查找

维持相对顺序

遍历

深度优先遍历

前序遍历

中序遍历

后序遍历

广度优先遍历

层序遍历

二叉堆

定义

自我调整

操作

插入加点

删除节点

构建二叉堆

代码实现

优先队列

特点

实现

入队操作

出队操作


        在实际场景中,有许多逻辑关系并不是简单的线性关系,常常存在一对多、多对多的关系;其中树和图就是典型的非线性数据结构。

定义

        树是n(n >=0)个节点的有序集合。当n=0时,称为空树,空树特点:有且仅有一个特定的称为根的节点;当n>1时,其余节点可分为m(m>0)个互不相交的有限集,每一个集合本身又是一个树,并称为根的子树

结构

        节点1是根节点;节点5、6、7、8、9是树的末端,没有“孩子”,被称为叶子节点。图中的虚线部分,是根节点1的其中一个子树。

树的结构从根节点到叶子节点,分为不同的层级。从一个节点的角度来看,它的上下级和同级节点关系如下:

        节点4的上一级节点,是节点4的父节点;从节点4衍生出来的节点,是节点4的孩子节点;和节点4同级,由同一个父节点衍生出来的节点,是节点4的兄弟节点;

        树的最大层级数,称为树的高度或深度。

二叉树

定义

        二叉树是数的一种特殊形式。这种树的每个节点最多有两个孩子节点(最多两个,可以是1个或者0个)。

结构

         二叉树节点的两个孩子节点,一个被称为左孩子,一个被称为右孩子。这两个孩子节点的顺序是固定的,就像人的左手就是左手,右手就是右手,不能够颠倒或混淆。

形式

二叉树还有两种特殊形式,一个叫作满二叉树,另一个叫作完全二叉树

满二叉树

        一个二叉树的所有非叶子节点都存在左右孩子,并且所有叶子节点都在同一层级上,那么这个树就是满二叉树;满二叉树的每一个分支都是满的。

完全二叉树

        对一个有n个节点的二叉树,按层级顺序编号,则所有节点的编号为从1到n。如果这个树所有节点和同样深度的满二叉树的编号为从1到n的节点位置相同,则这个二叉树为完全二叉树

        二叉树编号从1到12的12个节点,和前面满二叉树编号从1到12的节点位置完全对应。因此这个树是完全二叉树。

完全二叉树的条件没有满二叉树那么苛刻:满二叉树要求所有分支都是满的;而完全二叉树只需保证最后一个节点之前的节点都齐全即可

存储

        数据结构可以分为物理结构和逻辑结构。二叉树属于逻辑结构,它可以通过多种物理结构来表达;

二叉树可以用到链式存储结构和数组两种物理存储结构来表达

链式存储结构

链式存储是二叉树最直观的存储方式;

链表是一对一的存储方式,每一个链表节点拥有data变量和一个指向下一节点的next指针;二叉树稍微复杂一些,一个节点最多可以指向左右两个孩子节点,所以二叉树的每一个节点包含3部分:

1.存储数据的data变量

2.指向左孩子的left指针

3.指向右孩子的right指针

数组

        使用数组存储时,会按照层级顺序把二叉树的节点放到数组中对应的位置上。如果某一个节点的左孩子或右孩子空缺,则数组的相应位置也空出来,可以更方便地在数组中定位二叉树的孩子节点和父节点

        使用数组存储时,会按照层级顺序把二叉树的节点放到数组中对应的位置上。如果某一个节点的左孩子或右孩子空缺,则数组的相应位置也空出来。这样可以更方便地在数组中定位二叉树的孩子节点和父节点。

孩子节点

计算:一个父节点的下标是parent,那么它的左孩子节点下标就是2×parent +1;右孩子节点下标就是2×parent + 2。

父节点

计算:一个左孩子节点的下标是leftChild,那么它的父节点下标就是(leftChild-1)/ 2;

对于一个稀疏的二叉树来说,用数组表示法是非常浪费空间的

应用

        二叉树包含了很多的特殊形式,每一种形式都有自己的作用,但是最主要的应用还在于查找操作和维持相对顺序两方面。

查找

二叉树的树形结构使它很适合扮演索引的角色。

特殊的二叉树:二叉查找树,主要的作用就是进行查找操作,在二叉树的基础上增加了条件:

1.如果左子树不为空,则左子树上的所有节点的值均小于根节点的值

2.如果右子树不为空,则右子树上的所有节点的值均大于根节点的值

3.左、右子树也都是二叉查找树

        对于一个节点分布相对均衡的二叉查找树来说,如果节点总数是n,那么搜索节点的时间复杂度就是O(logn),和树的深度是一样的。
        这种依靠比较大小来逐步查找的方式,和二分查找算法非常相似

维持相对顺序

        二叉查找树要求左子树小于父节点,右子树大于父节点,正是这样保证了二叉树的有序性,所以又叫:二叉排序树。

新插入的节点,同样要遵循二叉排序树的原则

插入新元素5,由于5<6,5>3,5>4,所以5最终会插入到节点4的右孩子位置

遍历

在计算机程序中,遍历本身是一个线性操作。所以遍历同样具有线性结构的数组或链表,是一件轻而易举的事情;

反观二叉树,是典型的非线性数据结构,遍历时需要把非线性关联的节点转化成一个线性的序列,以不同的方式来遍历,遍历出的序列顺序也不同

从节点之间位置关系的角度来看,二叉树的遍历分为4种。
1. 前序遍历。
2. 中序遍历。
3. 后序遍历。
4. 层序遍历

从更宏观的角度来看,二叉树的遍历归结为两大类。
1. 深度优先遍历(前序遍历、中序遍历、后序遍历)。
2. 广度优先遍历(层序遍历)

深度优先遍历

        深度优先和广度优先这两个概念不止局限于二叉树,它们更是一种抽象的算法思想,决定了访问某些复杂数据结构的顺序。

        所谓深度优先,顾名思义,就是偏向于纵深,“一头扎到底”的访问方式

前序遍历

二叉树的前序遍历,输出顺序是根节点、左子树、右子树

中序遍历

二叉树的中序遍历,输出顺序是左子树、根节点、右子树

后序遍历

二叉树的后序遍历,输出顺序是左子树、右子树、根节点。

广度优先遍历

层序遍历

        顾名思义,就是二叉树按照从根节点到叶子节点的层次关系,一层一层横向遍历各个节点

        二叉树同一层次的节点之间是没有直接关联的,需要借助一个数据结构来辅助工作,这个数据结构就是队列。


public static void levelOrderTraversal(TreeNode root){Queue<TreeNode> queue = new LinkedList<TreeNode>();queue.offer(root);while(!queue.isEmpty()){TreeNode node = queue.poll();System.out.println(node.data);if(node.leftChild != null){queue.offer(node.leftChild);}if(node.rightChild != null){queue.offer(node.rightChild);}}
}

二叉堆

定义

二叉堆本质是一种完全二叉树,分为两个类型:

1.最大堆:任何一个父节点的值,都大于或等于它左、右孩子节点的值

2.最小堆:最小堆的任何一个父节点的值,都小于或等于它左、右孩子节点的值

二叉堆的根节点叫作堆顶

特点:

        最大堆的堆顶是整个堆中的最大元素;最小堆的堆顶是整个堆中的最小元素

自我调整

        堆的自我调整,就是把一个不符合堆性质的完全二叉树,调整成一个堆;

操作

        操作都基于堆的自我调整,以最小堆为例,看一看二叉堆如何进行自我调整

插入加点

        当二叉堆插入节点时,插入位置是完全二叉树的最后一个位置。例如插入一个新节点,值是 0;与父节点进行比较,如果更大,让新节点“上浮”,和父节点交换位置,持续比较,直到0到达堆顶。

删除节点

二叉堆删除节点的过程和插入节点的过程正好相反,所删除的是处于堆顶的节点。例如删除最小堆的堆顶节点1;

为了继续维持完全二叉树的结构,会把堆的最后一个节点10临时补到原本堆顶的位置;让暂处堆顶位置的节点10和它的左、右孩子进行比较,如果左、右孩子节点中最小的一个(显然是节点2)比节点10小,那么让节点10“下沉”,这样一来,二叉堆重新得到了调整

构建二叉堆

构建二叉堆,也就是把一个无序的完全二叉树调整为二叉堆,本质就是让所有非叶子节点依次“下沉”

从最后一个非叶子节点开始,也就是从节点10开始。如果节点10大于它左、右孩子节点中最小的一个,则节点10“下沉”

经过上述几轮比较和“下沉”操作,最终每一节点都小于它的左、右孩子节点,一个无序的完全二叉树就被构建成了一个最小堆

代码实现

        二叉堆虽然是一个完全二叉树,但它的存储方式并不是链式存储,而是顺序存储。换句话说,二叉堆的所有节点都存储在数组中。

在数组中没有左右指针的情况下,可以依靠数组下标来计算定位一个父节点的左孩子和右孩子:

假设父节点的下标是parent:

        左孩子下标: 2×parent+1

        右孩子下标: 2×parent+2

1. /**
2. * “上浮”调整
3. * @param array 待调整的堆
4. */
5. public static void upAdjust(int[] array) {
6. int childIndex = array.length-1;
7. int parentIndex = (childIndex-1)/2;
8. // temp 保存插入的叶子节点值,用于最后的赋值
9. int temp = array[childIndex];
10. while (childIndex > 0 && temp < array[parentIndex])
11. {
12. //无须真正交换,单向赋值即可
13. array[childIndex] = array[parentIndex];
14. childIndex = parentIndex;
15. parentIndex = (parentIndex-1) / 2;
16. }
17. array[childIndex] = temp;
18. }
19.
20.
21. /**
22. * “下沉”调整
23. * @param array 待调整的堆
24. * @param parentIndex 要“下沉”的父节点
25. * @param length 堆的有效大小
26. */
27. public static void downAdjust(int[] array, int parentIndex,
int length) {
28. // temp 保存父节点值,用于最后的赋值
29. int temp = array[parentIndex];
30. int childIndex = 2 * parentIndex + 1;
31. while (childIndex < length) {
32. // 如果有右孩子,且右孩子小于左孩子的值,则定位到右孩子
33. if (childIndex + 1 < length && array[childIndex + 1] <
array[childIndex]) {
34. childIndex++;
35. }
36. // 如果父节点小于任何一个孩子的值,则直接跳出
37. if (temp <= array[childIndex])
38. break;
39. //无须真正交换,单向赋值即可
40. array[parentIndex] = array[childIndex];
41. parentIndex = childIndex;
42. childIndex = 2 * childIndex + 1;
43. }
44. array[parentIndex] = temp;
45. }
46.
47. /**
48. * 构建堆
49. * @param array 待调整的堆
50. */
51. public static void buildHeap(int[] array) {
52. // 从最后一个非叶子节点开始,依次做“下沉”调整
53. for (int i = (array.length-2)/2; i>=0; i--) {
54. downAdjust(array, i, array.length);
55. }
56. }
57.
58. public static void main(String[] args) {
59. int[] array = new int[] {1,3,2,6,5,7,8,9,10,0};
60. upAdjust(array);
61. System.out.println(Arrays.toString(array));
62.
63. array = new int[] {7,1,3,10,5,2,8,9,6};
64. buildHeap(array);
65. System.out.println(Arrays.toString(array));
66. }

优先队列

队列特点:先进先出、后进后出;入队列,将新元素置于队尾,出队列,队头元素最先被移出

特点

        最大优先队列,无论入队顺序如何,都是当前最大的元素优先出队

        最小优先队列,无论入队顺序如何,都是当前最小的元素优先出队

实现

        最大堆的堆顶是整个堆中的最大元素,可以用最大堆来实现最大优先队列,这样的话,每一次入队操作就是堆的插入操作,每一次出队操作就是删除堆顶节点

入队操作

1.插入新节点

2.新节点上浮到合适位置

出队操作

1.让原堆顶节点10出队

2.把最后一个节点1替换到堆顶位置

3.节点1“下沉”,节点9成为新堆顶

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

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

相关文章

408强化(番外)文件管理

有点看不下去书&#xff0c;408&#xff0c;哎好久没看了&#xff0c;死磕数学时完全不想看其他科目&#xff0c;数学分数也尚未质变。 突然想到一个好点子&#xff0c;只看大纲尝试回忆一下这章的内容。 文件就是为了方便用户使用&#xff0c;按名访问而提出的&#xff0c;从…

OPC是通讯协议吗安全性

目录 1 安全防护 1.1 防火墙 1.2 网闸 2 OPC是通讯协议吗 2.1 什么通讯协议 2.2 那么OPC又是什么&#xff1f; OPC官方说明文档 1 安全防护 本文阐述了控制网络以OPC接口接入信息网络应当采用的安全防护。 1.1 防火墙 防火墙是大家熟知的网络安全产品,并被用作控制网…

分布式事务解决方案之可靠消息最终一致性

分布式事务解决方案之可靠消息最终一致性 什么是可靠消息最终一致性事务 可靠消息最终一致性方案是指当事务发起方执行完成本地事务后并发出一条消息&#xff0c;事务参与方(消息消费者)一定能 够接收消息并处理事务成功&#xff0c;此方案强调的是只要消息发给事务参与方最终…

SpringMVC多文件上传

文章目录 一、文件上传1.1 导入pom依赖1.2 配置文件上传解析器1.3 设置文件上传表单1.4 实现文件上传 二、文件下载三、多文件上传四、JRebel的使用 一、文件上传 1.1 导入pom依赖 <commons-fileupload.version>1.3.3</commons-fileupload.version><dependency…

LeetCode(力扣)435. 无重叠区间Python

LeetCode435. 无重叠区间 题目链接代码 题目链接 https://leetcode.cn/problems/non-overlapping-intervals/ 代码 class Solution:def eraseOverlapIntervals(self, intervals: List[List[int]]) -> int:if not intervals:return 0intervals.sort(keylambda x: x[0])co…

关于content-type的理解

一.content-type的结论 告诉后端传过去的数据是什么类型的数据 二.没有请求体 (1)没有请求体的情况下content-type没有意义。 (2):图示 里面是没有请求体的 (3)有请求体的情况 二.常见的三种方式 (1)application/x-www-form-urlencoded(默认) 参数的表现形式: 传递之前可以…

如何将一个字符串转换为驼峰命名法(camel case)?

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 思路⭐ 示例⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 记得点击上方或者右侧链接订阅本专栏哦 几何带你启航前端之旅 欢迎来到前端入门之旅&#xff01;这个专栏是为那些对Web开发感兴趣、刚刚踏入前端领…

揭秘弹幕游戏制作

最近好多人问弹幕游戏&#xff0c;甚至是招人的也要DOTS做弹幕游戏... 实际上目前的弹幕游戏绝大多数应该和DOTS没有半点关系&#xff0c;别忘了DOTS这项技术渲染问题还没能够被合理解决呢 所以目前用的全都是GPU Instance这项技术&#xff0c;于是乎我决定下场写这篇帖子&am…

2000-2018年各省能源消费和碳排放数据

2000-2018年各省能源消费和碳排放数据 1、时间&#xff1a;2000-2018年 2、范围&#xff1a;30个省市 3、指标&#xff1a;id、year、ENERGY、COAL、碳排放倒数*100 4、来源&#xff1a;能源年鉴 5、指标解释&#xff1a; 2018年碳排放和能源数据为插值法推算得到 碳排放…

【SpringMVC】自定义注解与AOP结合使用

目录 一、SpringMVC之自定义注解 1.1 Java注解简介 1.2 为什么要用注解 1.3 注解的分类 ⭐ 1.3.1 JDK基本注解 1.3.2 JDK元注解 1.3.3 自定义注解 1.4 自定义注解三种使用案例 1.4.1 案例一&#xff08;获取类与方法上的注解值&#xff09; 1.4.2 案例二&#xff0…

Linux中使用Docker安装ElasticSearch7.10.x集群

使用Docker安装ElasticSearch7.10.x单节点请访问这里 一、集群环境说明 服务器IP地址192.168.137.1&#xff0c;192.168.137.2&#xff0c;192.168.137.3 二、前期准备 1. 拉取镜像 docker pull elasticsearch:7.10.12. 首先需要创建一个用于生成秘钥的初始容器&#xff0…

rocketmq

&#x1f353;代码仓库 https://gitee.com/xuhx615/rocket-mqdemo.git &#x1f353;基本概念 ⭐生产者(Producer)&#xff1a;消息发布者⭐主题&#xff08;Topic&#xff09;&#xff1a;topic用于标识同一类业务类型的消息⭐消息队列&#xff08;MessageQueue&#xff09…