【Java数据结构】树

Java中的树

一、树基础结构

1.树定义

1.1.一些花里胡哨的名词

·结点

·结点的度、树的度、结点的深度、结点的高度、树的高(深)度

·孩子、双亲(父)、兄弟、祖先、子孙、堂兄弟

1.2.存储方式

1)顺序存储

Alt
在这里插入图片描述
2)链式存储

在这里插入图片描述
在这里插入图片描述

2.二叉树

2.1.性质

·每个结点有0~2个孩子结点

·非空二叉树叶子结点数=双分支结点数+1

·二叉树第n层最多有 2^(n-1)个结点

·高度为n的二叉树最多有2^n-1个结点

·二叉树的结点要么是叶子结点,要么它有两个子结点则称其为满二叉树

在这里插入图片描述

2.2.遍历方式

·前序遍历:根>左>右

·中序遍历:左>根>右

·后序遍历:左>右>根

PS:只有根是存值的,遍历目的就是遍历根的值,但需要决策是先看值还是先往深走

二、树的进阶与Java

1.完全二叉树

1.1.定义

若设二叉树的深度为k,除第 k 层外,其它各层 (1~k-1) 的结点数都达到最大个数,第k 层所有的结点都连续集中在最左边,这就是完全二叉树。
在这里插入图片描述

1.2.性质

从上到下、从左到右依次编号,则编号具有规律:
在这里插入图片描述

1.3.优先级队列中的堆排序

小顶堆:每个结点的值都小于或等于其左右孩子结点的值

在这里插入图片描述

2.二叉查找树与平衡二叉树(小陀螺)

2.1.定义

1)二叉查找树

左子树上所有节点的值均小于根节点的值,而右子树上所有结点的值均大于根节点的值,左小右大

2)平衡二叉树

想象插入的序列越接近有序,生成的二叉搜索树就越像一个链表,查找意义不大。
为了避免二叉搜索树变成“链表”,我们引入了平衡二叉树,即让树的结构看起来尽量“均匀”,左右子树的节点数尽量一样多。
他对二叉树做了改进,在我们每插入一个节点的时候,必须保证每个节点对应的左子树和右子树的树高度差不超过1。
如果超过了就对其进行调平衡(左左、左右、右左、右右)。

3.红黑树

3.1.定义

1)二三树

在二叉查找树基础上增加性质,节点有二节点和三节点之分,
二节点下面有两个子节点,二节点里面可以容纳一个值,而三节点下面有三个子节点,三节点里面可以容纳两个值。
具体大小规则如图

在这里插入图片描述
当有新的值插入时假如新值要进入二节点时就直接进去。但如果正好需要放在三节点中,需要将该节点分裂成两个节点,并将中间的数提到父节点中去,
当然如果将子节点提到父节点的时候导致了父节点里的数超过了两个,就继续向上提,直到满足了为止。
在这里插入图片描述

2)红黑树
和二三树很相像,基本上就是二三树的一个变形。满足以下特征

· 每个结点或者是黑色,或者是红色。

· 根结点是黑色。

· 每个叶子结点(无值结点)是黑色。

· 如果一个结点是红色的,则它的子结点必须是黑色的

· 从一个结点到该结点的子孙结点的所有路径上包含相同数目的黑节点。

在这里插入图片描述
对比看出红黑树中红结点就是二三树的三节点中较小的值。至于不对比二三树看红黑树是怎么调平的,也有左旋、右旋、变色的方式来调平。

3.2.性质

红黑树相比平衡二叉树,在检索的时候效率其实差不多,都是通过平衡来二分查找。但对于插入删除等操作效率提高很多。
红黑树不像平衡二叉树一样追求绝对的平衡,他允许局部很少的不完全平衡,这样对于效率影响不大,但省去了很多没有必要的调平衡操作,
平衡二叉树调平衡有时候代价较大,所以效率不如红黑树。

3.3.HashMap,TreeMap中的红黑树

Java的TreeMap就是红黑树。

而HashMap采用 Hash 算法来决定集合中元素的存储位置。

当系统开始初始化 HashMap 时,系统会创建一个长度为 capacity(默认16) 的 Entry 数组table,

这个数组里可以存储元素的位置被称为“桶(bucket)”,每个 bucket 都有其指定索引,

系统可以根据其索引快速访问该 bucket 里存储的元素。

HashMap 的每个“桶”只存储一个元素(也就是一个 Entry),

由于 Entry 对象可以包含一个引用变量(就是 Entry 构造器的的最后一个参数)用于指向下一个 Entry,

因此可能出现的情况是:HashMap 的 bucket 中只有一个 Entry,

但这个 Entry 指向另一个 Entry ——这就形成了一个 Entry 链。

put和get都首先会调用hashcode方法,去查找相关的key,当有冲突时,再调用equals。

如果一个 Entry 链元素由少增多至大于8个时转为TreeMap即红黑树存储,

当元素个数由多减少至小于6个时转为数组存储。

1)WHY16?

每次放入新元素hashMap会重新计算其index

int index =key.hashCode()&(length-1);

16首先不大不小,既不浪费资源也不会极易触发扩容,其次还有个隐藏秘籍,

因为是将二进制进行按位于,(16-1) 是 1111,末位是1,

这样也能保证计算后的index既可以是奇数也可以是偶数,并且只要传进来的key足够分散,

这样也就减少了hash的碰撞。

2)WHY8?

前提已知,链表,查询成本高,新增成本低。红黑树,查询成本低,新增成本高。

单从平均查找长度n/2和log2(n)来说在n=4时log2(n)开始小于n/2,但根据注释

“TreeNodes占用空间是普通Nodes的两倍,所以只有当bin包含足够多的节点时才会转成TreeNodes”

(Because TreeNodes are about twice the size of regular nodes, we use them only when bins contain enough nodes)

说白了就是trade-off,空间和时间的权衡。

4.B-Tree

4.1.定义

从完全二叉树进化到平衡二叉树提高了查询能力,

从平衡二叉树进化到红黑树提高了插入能力,

但还是没有摆脱典型的二叉查找树结构,查找的时间复杂度O(log2N)且效率与树的深度紧密相关,
这样就有了一个实际问题,就是大规模数据存储中,实现索引查询这样一个实际背景下,
数据一般是在磁盘中存储的,磁盘查找存取的次数往往由树的高度所决定,所以
二叉查找树结构由于树的深度过大而造成磁盘I/O读写过于频繁,进而导致查询效率低下。
多路查找树,一个新的查找树结构。根据平衡二叉树的启发,自然就想到平衡多路查找树结构。
BTree,B即Balanced,平衡的意思(不是Binary…路子多得很)。

磁盘读取数据是以盘块(block)为基本单位的。

读取分为查找、等待、传输,其中等待取决于磁盘转速,传输时间基本固定,最影响效率的就是读取。

查找就是磁头移动找到对应盘块的过程,位于同一盘块中的所有数据都能被一次性全部读取出来,

所以要尽量保证数据都在同一盘块或者同一柱面再不济是相邻柱面上。

对于树,一个结点内的数据必然在一个盘块上,但若获得其子结点就要根据指针再次查找盘块.

小贴士:B-tree就是指的B树

这是一个翻译失误,国内很多人喜欢把B-tree译作B-树。

4.2.性质

一棵m(m>=2)阶的B树(并非m叉树)有以下特点:

· *树中每个结点最多含有m个孩子

· *除根结点和叶子结点外,其它每个结点至少有ceil(m / 2)个孩子

· *若根结点不是叶子结点,则至少有2个孩子

· *所有叶子结点(无值结点)都出现在同一层,叶子结点不包含任何关键字信息

· *每个非终端结点中包含有n个关键字信息ceil(m / 2)-1<= n <= m-1<,且从左至右升序br/>
· 每个非终端结点有关键字Ki,指向子树结点的指针Pi,每个节点都是连续存储的(同一盘块)

· 一棵含有n个总关键字数的m阶的B树的最大高度是log_ceil(m/2)((N+1)/2)+1

如图一个3阶B树(除根节点至少2个分支)

在这里插入图片描述

以一个5阶B树(除根节点至少3个分支,每个结点2~4个数据)介绍操作

插入{1,2,6,7,11,4,8,13,10,5,17,9,16,20,3,12,14,18,19,15}

删除{8,16,15,4}

小贴士:

B树的新值一定落在最底层非叶子节点

如果插入使得超过关键字限制则进行结点拆分

如果删除使得低于关键字限制则向兄弟结点借或和孩子结点交换,必要时也可能进行合并

在这里插入图片描述

在这里插入图片描述

5.B+Tree(还有B*Tree不讲了)

5.1.定义

上面是BTree下面是B+Tree
在这里插入图片描述

所有的非终端结点可以看成是索引部分

小贴士:
有的B+Tree的介绍中会在叶子结点间增加左向右的指针。
这个并不是原本B+Tree定义带的,而是对B+Tree一种优化,
目的是提高遍历效率和范围查找的能力。

5.2.性质

1)B+tree的磁盘读写代价更低

如图B+tree的内部结点并没有指向关键字具体信息的指针。因此其内部结点相对B 树更小。

如果把所有同一内部结点的关键字存放在同一盘块中,那么盘块所能容纳的关键字数量也越多。

一次性读入内存中的需要查找的关键字也就越多。相对来说IO读写次数也就降低了。

2)B+tree的查询效率更加稳定

所有关键字查询的路径长度相同,导致每一个数据的查询效率相当,查询时间复杂度为log_m(N)。

5.3.数据库中的索引

1)Postgre默认BTree

· Hash索引不适用于范围查询。

· Hash索引无法被用来避免数据的排序操作,也无法利用Hash索引来加速任何排序操作。

· 当哈希值大量重复且数据量非常大时,Hash固有机制浪费性能(解决冲突等)。

· 不能用部分索引键来搜索,因为组合索引在计算哈希值的时候是一起计算的。

· 不可避免表扫描。

2)BTree优势的发挥

根据之前的描述,假设树高度为H,B/B+树每次检索最多检索H次。

数据库系统的设计者巧妙利用了磁盘预读原理,

对于BTree将一个结点的大小设为等于一个页,这样每个结点只需要一次I/O就可以完全载入,保证最多IO次数为H。

而B+Tree就更加暴力,大极端情况把所有索引层结点放在一个页,只需2次IO。

在数据库索引中B/B+树的阶M通常很高,来保证H很小。

5.4. HBase中的LSM树

1)B+Tree的缺点

尽管B+Tree在对此盘IO效率的优化上已经做出了很大的突破,任然避免不了自己被随机IO审判的一天。

身为70年代的老怪物,还能活跃在人们面前的主要原因是,CPU和RAM基本一年左右就可以翻一倍的进化,而磁盘寻道技术一年也就提高5%。

但是数据量的增大由不得硬件支配,总有人会想办法解决,既然怎么样都有随机IO,那就把随机编程顺序。

既然要变成顺序,那就要预先准备好写入的东西,比如一坨结点而不是一个。

还有一种主流的解释是:

随着新数据的插入,叶子节点会慢慢分裂,逻辑上连续的叶子节点在物理上往往不连续,甚至分离的很远。

但做范围查询时,会产生大量读随机IO,对于大量的随机写也一样,举一个插入key跨度很大的例子,如3->1000->7->2000 …

新插入的数据存储在磁盘上相隔很远,会产生大量的随机写IO。

2)LSMTree(Log-Structured Merge-Tree)

日志结构合并树,这里的‘日志结构’指的就是数据库追加型的日志,‘合并树’看图:

在这里插入图片描述

LSMTree是把原本一个大树拆成了几个分层的树,每层的容量大概是上一层的10倍,

每当一个分层的容量达到阈值时,就会抽取一部分有序密集的子树,拿到下一层合并,这样做就保证了顺序写。

在HBase中,首先是在内存中有个小树,新插入的数据会先预写在HLog中再记录到内存中,当小树达到一定阈值就触发刷盘、合并。

不难发现其查询时间复杂度为(N/t)log_m(N),t是阈值,这样看是大于B+Tree,易写不易读。

LSM Tree放弃磁盘读性能来换取写的顺序性,用来大幅进步写性能。

3)HBase再升级

bloom filter:

就是个带随即概率的bitmap,可以快速的告诉你,某一个小的有序结构里有没有指定的那个数据的。

于是我就可以不用二分查找,而只需简单的计算几次就能知道数据是否在某个小集合里啦。效率得到了提升,但付出的是空间代价。

compact:

因为hbase一般是部署在hdfs上,hdfs不支持对文件的update操作,

所以hbase是整体内存flush到HRegion磁盘中(DataNode),这样MemStore就变成了DataNode上的磁盘文件StoreFile,

定期HRegionServer对DataNode的数据做merge操作,彻底删除无效空间,多棵小树在这个时机合并成大树,来增强读性能。

compact:

因为hbase一般是部署在hdfs上,hdfs不支持对文件的update操作,

所以hbase是整体内存flush到HRegion磁盘中(DataNode),这样MemStore就变成了DataNode上的磁盘文件StoreFile,

定期HRegionServer对DataNode的数据做merge操作,彻底删除无效空间,多棵小树在这个时机合并成大树,来增强读性能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值