数据结构 —— 数组,列表和树的家族

作为一个程序员,在现在的工作中,写业务代码已经不是什么困难的事情了,但是这里有两个来自灵魂的拷问:

你知道你写的代码是如何执行的嘛?你知道你代码中的数据是怎么存储的嘛?

我不知道。。。。。。

那你还不快到碗里来,come on!!!!      今天就先说一下这个数据的存储吧,小板凳搬过来坐好

先上个目录:


目录

1、数据的存储方式有哪些?

2、各种数据存储结构详解

2.1、线性表

2.1.1、顺序存储

2.1.2、链式存储

2.2 Hash表

2.3 树

2.3.1 简介

2.3.2 二叉树

2.3.3  二叉查找树

2.3.4  2-3树(最简单的平衡查找树)

2.3.5 红黑树

2.3.5.1 红黑树的结构

2.3.5.2 红黑树的自旋

2.3.5.3 需要自旋的场景

2.3.5.4 应用


1、数据的存储方式有哪些?

首先,数据的存储方式可以分为线性表,Hash表,树,图四种结构;而线性表又分顺序存储和链式存储的方式。其实说白了Hash表,树,图都是顺序存储和链式存储结合或者单个顺序和链式存储实现的。

如图,红色模块都是数据在我们内存或者磁盘中的存储方式。之所以有这么多存储方式,都是为了更好的契合我们的应用场景。

例如:蓝色框的两种应用场景都是线性表存储,链式和顺序存储两种方式都可以实现。然后根据不同的取值方式可以分为栈(比如JVM内存中的栈)和队列(例如JDK中的BolckingQueue和现在非常火的一些消息中间件)。

这里设计到的JVM的内存结构就先掠过,后续整理JVM代码是怎么执行的再说。

2、各种数据存储结构详解

2.1、线性表

就是字面意思,数据以线性存储,除了首位两个数据,前后都存在相连数据的一组数据。

2.1.1、顺序存储

顺序存储就是内存地址相连的线性表;如图:

典型的例子就是我们的数组,数组的定义是固定长度的,所以定义数组的过程其实就是分配一段固定长度的内存空间。

顺序存储的内存块中只保存了数据本省。因为它是地址相连的,所以我们可以根据起始和偏移量(可以理解为下标)结合来很快读取数据,所以我们的顺序存储的数据查询时间复杂度为O(1);

但是,因为我们的地址是相连并且固定的,所以我们要在数组中插入或者删除数据的时候就会伴随着已经存在数据的移动,所以我们的插入和删除的时间复杂度就是O(n);当然,我们也有插入和删除很迅速的结构,往下看!!!

2.1.2、链式存储

链式存储数据结构和顺序存储完全不同,它是每个数据各自为政,相比于顺序结构每个元素手拉手而言,我们的链式存储就已经有了“电话”了,每个人都留下了上线和下线的电话号码;所以他们并不需要手拉手,各自在各自的地方,有事儿的时候就是一个电话。如图:

其中,b是前一个节点指针(存放上线地址),n是下一个节点指针(存放下线地址)。

基于这种链表存储的方式,链表数据在插入和删除的时候只需要修改旁边两个节点的指针实现。所以时间复杂度是O(1);但是我们在查找的时候就麻烦了,必须从开始或者结尾根据指正去一个一个查找想要的数据,所以时间复杂度为O(n)。

这样我们就发现一个问题,顺序结构和链式结构是可以虽然特性差别很大,但是他们可以互补。很好,那我们就把他们结合起来,所以就有了我们的Hash表。别急,继续往下看!!!

2.2 Hash表

Hash表?什么鬼?      别慌,Hash表是个机灵鬼。你不知道Hash表那肯定知道HashMap吧!HashMap就是这种结构。

先上图吧:

怎么样,是不是简单明了。既然顺序结构和链式结构各自有各自的优势,那我们就得把他们得优势结合起来,就有了我们得Hash表。

那么问题来了,什么是Hash值呢?是怎么来的呢?

Java中Object的所有类的基类,而它有一个方法叫做hashCode(),它就是JAVA默认的对象哈希值。当然也可以自己实现Hash算法。

顺序存储和链式存储是我们计算机存储的基础结构,它们的应用不止于Hash表,包括我们的树和图。往下看:

2.3 树

2.3.1 简介

树是一对多的结构,就是一个节点的可以子节点不止一个,但是父节点除了根节点都是只有一个。

树的种类和应用也是特别多的,典型的就是数据库索引和文件系统。普通树的结构如下图所示:包含树的一些基本术语实意:节点,子节点,深度,度,跟节点,叶子节点

树在我们的计算机中应用非常广泛,而且种类多,各有特点。下面我们就来说一下树的家族。

2.3.2 二叉树

二叉树结构就是度为2的树。简单来说每个节点最多拥有两个节点。这里还有两个特殊的二叉树:满二叉树完全二叉树

满二叉树:除了最后一层叶子节点,所有的节点的度为2。也就是说层数固定,满二叉树的结构就是固定的。

完全二叉树:只缺最右侧N个叶子节点的满二叉树。

有图有真相:

那么问题又来了,为什么只有完全二叉树适合使用顺序存储的方式呢?为什么呢?为什么呢?当然是为了更方便查询啊。看下面:

很明显,左子节点的下标是2i, 右子节点的下标是2i+1,而我们的顺序存储的特点就是直接可以根据下标定位数据,所以可以快速的根据当前下标找到子节点,而普通的二叉树无法做到。这就是为什么只有完全二叉树适合使用顺序存储。

 

还有个重点,那就是二叉树的三种遍历方式,话不多说,直接上图。

2.3.3  二叉查找树

二叉查找树就是对于每个子节点而言,左子节点数据小于本节点数据,右子节点数据大于本节点数据,并且所有节点没有重复数据的二叉树。在二叉查找树中插入或者查询数据时在每一层最多判断1次,所以它的时间复杂度就是整棵树的度。

但是对于每个节点都只有做节点或者只有右节点的二叉树(下图二)就苦了,和线性表一样了,完全体现不出我作为一个二叉查找树的优势啊,所以我们需要在添加数据的时候让树的结构趋于平衡,一个叫做平衡查找树的东西出现了。

下图最优查找树的时间复杂度为lg N(使用二分法原理), 最劣查找树的时间复杂度为N

下面就一起来看看什么是平衡查找树。

2.3.4  2-3树(最简单的平衡查找树)

2-3树不是二叉树,不是二叉树,不是二叉树。重要的事情说三遍,2-3树的所有节点最大可以保存两个数据和三个指针。如下图所示:

  • 2-3树中包含两种节点-----只保存有一个数据两个指正的二节点,保存了两个数据和三个指针的三节点。
  • 2-3树的二节点的左子树也是2-3树并且所有数据都小于改节点数据; 右子树也是2-3树并且所有数据大于该节点数据。
  • 2-3树的三节点有两个数据三个指针,链接左子树,中子树,右子树。同上,三节点的左子树也是2-3树并且所有数据都小于该节点中最小的数据;三节点的中子树也是2-3树并且所有数据都小于该节点中最大的数据大于该节点中最小的数据;三节点的右子树也是2-3树并且所有数据都大于该节点中最大的数据。

2-3平衡查找树的结构清楚了,那么它是怎么保证树的平衡呢?这个就要从数据插入逻辑说起:

在2-3树种插入一个数据分为三步:

  • 第一步:遍历数据,看数据处在哪个位置;
  • 第二步:如果最后找到的地方是一个二节点,则直接插入二节点变为三节点;
  • 第三步:如果最后找到的地方是三节点,则先插入三节点变为4节点,然后把三个数据中间的一个数据提出来作为父节点,两边的数据作为子节点,最后形成一个三个二节点的新的2-3树。

文字看着太累?没关系,那我们来看图:

到了这里应该有人会问为什么这里的4和上面第一个示例中的4不一样呢?

那是因为数据插入顺序不同。具体情节自己想吧。

这样就实现了我们的树一直趋于平衡,降低我们二叉查找树的维护数据平衡成本,2-3树在最差的情况下树的度最大也是lgN, 从最优的是lgN到最差是lgN,高下立判。在数据存储性能方面又走出了很大的一步。

在我们的代码生涯中,还是有一种2-3树的应用,那就是我们的红黑树。

2.3.5 红黑树

2.3.5.1 红黑树的结构

想必大家都被红黑树的自旋平衡化搞得头疼了吧(手动坏笑),没关系,我们一步一步来,先看看它长什么样子。

首先大家可以明确一点,红黑树可以认为是2-3树,但是红黑树本身是一个二叉树,可以说是一个用了2-3树思想的二叉树。重要得事情说三遍。But 它有几个新的特点:

  • 红黑树所说的红黑不是节点是红色黑色,说的是连接---红色连接和黑色连接;
  • 红色连接:就是我们2-3树中的三节点两个数据的连接,只不过这个红色连接是通过左子树的形式出现(也就是红色连接是以三节点最大数据作为根节点,最小数据作为左子节点之间的连接);
  • 黑色连接:就是普通的连接;

还是来看图吧,下图就是红黑树和2-3的暧昧关系。

其实我们最难受的还是红黑树的自旋,但是自旋是为了什么呢?还不是为了让树平衡。那么其实说白了,红黑树自旋就是2-3树的平衡化的一个变种,不要被吓到。世上无难事,只怕有心人。一起来拿下它。Come on!!!!

2.3.5.2 红黑树的自旋

自旋操作:左旋,右旋,颜色反转

左旋逻辑:对于一个“三节点”而言,把红连接从右连接变为左连接的过程。原来的父节点变为左节点,原来的右节点变为父节点;

右旋逻辑:对于一个“三节点”而言,把红连接从左连接变为右连接的过程。原来的父节点变为右节点,原来的左节点变为父节点;

颜色反转:如果一个节点两左右连接都是红色连接(即4节点),则需要提取中间节点为父节点。

2.3.5.3 需要自旋的场景

红黑树是一个二叉树,在二叉树中按照2-3树的逻辑插入新的数据之后会导致树不平衡,出现以下一些场景,需要做一系列的操作使这个树平衡,自旋操作就三种,所以对应的场景也就三种。如图:

  • 如果节点的右子节点为红色,且左子节点位黑色,则进行左旋操作
  • 如果节点的左子节点为红色,并且左子节点的左子节点也为红色,则进行右旋操作
  • 如果节点的左右子节点均为红色,则执行颜色反转操作,提升中间结点。

2.3.5.4 应用

红黑树在我们的代码中有哪些应用场景呢?在Java中,你应该用到过TreeMap和TreeSet吧。它们就是红黑树。

扯一点题外话:

应该有人碰见过使用HashMap的时候数据乱序的场景吧,我们的HashMap使用的是Hash表来进行存储的,它是根据Key的Hash值来进行存储的,所以在我们看来是没有顺序的。这里我们就用到了TreeMap,它是根据key值进行自然排序的有序Map。如果需要按照数据插入顺序来进行排序的话可以用LinkedHashMap。


暂时做一个总结,文章内容也是比较多的,写了好久,不可能一蹴而就, B树和B+树内容请点击这里查看。觉得有疑问或者不正确的地方的,欢迎指正,一起学习,一起进步。有需要上面这些流程图资料的可以留言。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值