史上最详细的HashMap红黑树解析
请允许我当一回标题党。
好了,言归正传,本篇主要内容便是介绍HashMap的男二号——TreeNode(男一号还是给Node吧,毕竟是TreeNode的爷爷,而且普通节点一般来说也比TreeNode要多),本篇主要从以下几个方面介绍: 1.红黑树介绍 2.TreeNode结构 3.树化的过程 4.红黑树的左旋和右旋 5.TreeNode的左旋和右旋 6.红黑树的插入 7.TreeNode的插入 8.红黑树的删除 9.TreeNode的删除 讲解红黑树的部分算是理论部分,讲解TreeNode的部分则是代码实践部分,配合服用效果更加。 保守估计,仔细食用本篇大约需要半小时,请各位细细品尝。 红黑树介绍 什么是红黑树?嗯,首先,它是一颗树,所谓的树,便是长的像这样的东西
最上面的A是根节点,最下面的D、H、F、G是叶子节点。每一个非根节点有且只有一个父节点;树是具有一层一层的层次结构,这里A位于第一层,B、C位于第二层,依次类推。将左边的B节点部分(包括BDEH)拿出来,则又是一颗树,称为树的子树。 好了,知道树是什么东西了,那么红黑树是什么样的呢? 红黑树,本质上来说是一颗二叉搜索树。嗯,还是先说说这个二叉搜索树吧。二叉代表它的节点最多有两个子节点,而且左右有顺序,不能颠倒,分别叫左孩子和右孩子,这两个节点互为兄弟节点,嗯,其实叫法根现实里的叫法差不多,以下图为例,4、9互为兄弟,7是他们的父亲,9是2的叔叔,8是2的堂兄弟。,很简单吧。说完了称谓,再来说说用途,既然叫做搜索树表示它的用途是为了更快的搜索和查找而设计的,所以这棵树本身满足一定的排序规则,即树中的任何节点的值大于它的左孩子,且小于它的右孩子。 任意节点的左、右子树也分别为二叉查找树。嗯,结合下图意会一下:
红黑树的主要特性: (1)每个节点要么是黑色,要么是红色。(节点非黑即红) (2)根节点是黑色。 (3)每个叶子节点(NIL)是黑色。 (4)如果一个节点是红色的,则它的子节点必须是黑色的。(也就是说父子节点不能同时为红色) (5)从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。(这一点是平衡的关键) 说简单也简单,其实就是一颗比较平衡的又红又黑的二叉树嘛。 TreeNode结构 既然我们已经知道红黑树长什么样了,那么我们再来看看HashMap中的TreeNode代码里是如何表示的: TreeNode继承自LinkedHashMap中的内部类——LinkedHashMap.Entry,而这个内部类又继承自Node,所以算是Node的孙子辈了。我们再来看看它的几个属性,parent用来指向它的父节点,left指向左孩子,right指向右孩子,prev则指向前一个节点(原链表中的前一个节点),注意,这些字段跟Entry,Node中的字段一样,是使用默认访问权限的,所以子类可以直接使用父类的属性。 树化的过程 在前几篇中已经有所介绍,当HashMap桶中的元素个数超过一定数量时,就会树化,也就是将链表转化为红黑树的结构。
这里的逻辑其实不复杂,仅仅是循环遍历当前树,然后找到可以该节点可以插入的位置,依次和遍历节点比较,比它大则跟其右孩子比较,小则与其左孩子比较,依次遍历,直到找到左孩子或者右孩子为null的位置进行插入。 真正复杂一点的地方在于balanceInsertion函数,这个函数中,将红黑树进行插入平衡处理,保证插入节点后仍保持红黑树的性质。这个函数稍后在TreeNode的插入中进行介绍,这里先看看moveRootToFront,这个函数是将root节点移动到桶中的第一个元素,也就是链表的首节点,这样做是因为在判断桶中元素类型的时候会对链表进行遍历,将根节点移动到链表前端可以确保类型判断时不会出现错误。
红黑树的左旋和右旋 左旋和右旋,顾名思义嘛,就是将节点以某个节点为中心向左或者向右进行旋转操作以保持二叉树的平衡,让我们看图说话 图画的有点大。将就着看一下吧,左旋和右旋相当于以要旋转的节点为中心,将子树(以该节点的父节点为根的子树)整体向左旋转,该节点变成子树的根节点,原来的根节点变成了左孩子,如果该节点原来有左孩子,则将其变为该节点左孩子的右孩子。说起来好像有点绕,可以联系图进行形象化的理解,当节点C向左旋转之后,它的左孩子D可以理解为因为重力作用掉到A的右孩子位置,嗯,就是这样。右旋也是类似理解即可。 TreeNode的左旋和右旋 了解了左旋和右旋,让我们看看代码里是怎样实现的:
其实,也很简单嘛。23333 红黑树的插入 现在来看看一个比较麻烦一点的操作,红黑树的插入,首先找到这个节点要插入的位置,即一层一层比较,大的放右边,小的放左边,直到找到为null的节点放入即可,但是如何在插入的过程保持红黑树的特性呢,想想好像比较头疼,但是再仔细想想其实就会发现,其实只有这么几种情况: 1.插入的为根节点,则直接把颜色改成黑色即可。 2.插入的节点的父节点是黑色节点,则不需要调整,因为插入的节点会初始化为红色节点,红色节点是不会影响树的平衡的。 3.插入的节点的祖父节点为null,即插入的节点的父节点是根节点,直接插入即可(因为根节点肯定是黑色)。 4.插入的节点父节点和祖父节点都存在,并且其父节点是祖父节点的左节点。这种情况稍微麻烦一点,又分两种子情况: i.插入节点的叔叔节点是红色,则将父亲节点和叔叔节点都改成黑色,然后祖父节点改成红色即可。 ii.插入节点的叔叔节点是黑色或不存在: a.若插入节点是其父节点的右孩子,则将其父节点左旋, b.若为左孩子,则将其父节点变成黑色节点,将其祖父节点变成红色节点,然后将其祖父节点右旋。 5.插入的节点父节点和祖父节点都存在,并且其父节点是祖父节点的右节点。这种情况跟上面是类似的,分两种子情况: i.插入节点的叔叔节点是红色,则将父亲节点和叔叔节点都改成黑色,然后祖父节点改成红色即可。 ii.插入节点的叔叔节点是黑色或不存在: a.若插入节点是其父节点的左孩子,则将其父节点右旋 b.若为右孩子,则将其父节点变成黑色节点,将其祖父节点变成红色节点,然后将其祖父节点左旋。 然后重复进行上述操作,直到变成1或2情况时则结束变换。说半天,可能还是云里雾里,一图胜千言,让我们从无到有构建一颗红黑树,假设插入的顺序为:10,5,9,3,6,7,19,32,24,17(数字是我拍脑袋瞎想的。)
看图说话是不是就简单明了了,看在我画图这么辛苦的份上,点个关注给个赞可好(滑稽)。 TreeNode的插入 了解了红黑树的删除之后,我们再来看下TreeNode中是怎样用代码实现的:
其实就是一毛一样的,对号入座即可。 红黑树的删除 讲完插入,接下来我们来说说删除,删除的话,比删除还要复杂一点,请各位看官先深呼吸,做好阅读准备。 之前已经说过,红黑树是一颗特殊的二叉搜索树,所以进行删除操作时,其实是先进行二叉搜索树的删除,然后再进行调整。所以,其实这里分为两部分内容:1.二叉搜索树的删除,2.红黑树的删除调整。 二叉搜索树的删除主要有这么几种情景: 情景1:待删除的节点无左右孩子。 情景2:待删除的节点只有左孩子或者右孩子。 情景3:待删除的节点既有左孩子又有右孩子。 对于情景1,直接删除即可,情景2,则直接把该节点的父节点指向它的左孩子或者右孩子即可,情景3稍微复杂一点,需要先找到其右子树的最左孩子(或者左子树的最右孩子),即左(右)子树中序遍历时的第一个节点,然后将其与待删除的节点互换,最后再删除该节点(如果有右子树,则右子树上位)。总之,就是先找到它的替代者,找到之后替换这个要删除的节点,然后再把这个节点真正删除掉。 其实二叉搜索树的删除总体来说还是比较简单的,删除完之后,如果替代者是红色节点,则不需要调整,如果是黑色节点,则会导致左子树和右子树路径中黑色节点数量不一致,需要进行红黑树的调整,跟上面一样,替代节点为其父节点的左孩子与右孩子的情况类似,所以这里只说其为左孩子的情景(PS:上一步的寻找替换节点使用的是右子树的最左节点,所以该节点如果有孩子,只能是右孩子): 情景1:只有右孩子且为红色,直接用右孩子替换该节点然后变成黑色即可。 (D代表替代节点,即要被删除的节点,之前在经过二叉搜索树的删除后,D节点其实已经被删除了,这里为了方便理解这个变化过程,所以把这个节点也画出来了,所以当前的初始状态是待删除节点与其替换节点互换位置与颜色之后的状态) 情景2:只有右孩子且为黑色,那么删除该节点会导致父节点的左子树路径上黑色节点减一,此时只能去借助右子树,从右子树中借一个红色节点过来即可,具体取决于右子树的情况,这里又分成两种: i.兄弟节点是红色,则此时父节点是黑色,且兄弟节点肯定有两个孩子,且兄弟节点的左右子树路径上均有两个黑色节点,此时只需将兄弟节点与父节点颜色互换,然后将父节点左旋,左旋后,兄弟节点的左子树SL挂到了父节点p的右孩子位置,这时会导致p的右子树路径上的黑色节点比左子树多一,此时再SL置为红色即可。 ii.兄弟节点是黑色,那么就只能打它孩子的主意了,这里主要关注远侄子(兄弟节点的右孩子,即SR)的颜色情况,这里分成两种情况: a.远侄子SR是黑色,近侄子任意(白色代表颜色可为任意颜色),则先将S转为红色,然后右旋,再将SL换成P节点颜色,P涂成黑色,S也涂成黑色,再进行左旋即可。其实简单说就是SL上位,替换父节点位置。
emmmm...好像也不是很麻烦嘛(逃)。 TreeNode的删除节点 TreeNode删除节点其实也是两步走,先进行二叉搜索树的删除,然后再进行红黑树的调整,跟之前的情况分析是一致的。
呼。。。终于。。酝酿了好多天的一篇文章总算是写完了,为了尽量确认转换的准确性,找了很多资料进行参考,过程中花了不少时间,曾多次准备放弃。。。不过总算是没有死在娘胎里,也算是完成了一桩心事,开心。 之后还会继续更新,欢迎大家继续关注。也欢迎大家前来打脸。 |
史上最详细的HashMap红黑树解析的更多相关文章
- 【Java入门提高篇】Day25 史上最详细的HashMap红黑树解析
当当当当当当当,好久不见,最近又是换工作,又是换房子,忙的不可开交,断更了一小段时间,最重要的一篇迟迟出不来,每次都犹抱琵琶半遮面,想要把它用通俗易懂的方式进行说明,确实有一定的难度,可愁煞我也,但自 ...
- 史上最全HashMap红黑树解析
HashMap红黑树解析 红黑树介绍 TreeNode结构 树化的过程 红黑树的左旋和右旋 TreeNode的左旋和右旋 红黑树的插入 TreeNode的插入 红黑树的删除 TreeNode的删除节点 ...
- 史上最详细的二叉树、B树,看不懂怨我
今天我们要说的红黑树就是就是一棵非严格均衡的二叉树,均衡二叉树又是在二叉搜索树的基础上增加了自动维持平衡的性质,插入.搜索.删除的效率都比较高.红黑树也是实现 TreeMap 存储结构的基石. 1.二 ...
- 史上最详细“截图”搭建Hexo博客并部署到Github
http://jingyan.baidu.com/article/d8072ac47aca0fec95cefd2d.html 大家也搭建过博客,很多时候,按着教程来做就可以了,但是我当时为了搭建Hex ...
- 史上最详细“截图”搭建Hexo博客——For Windows
http://angelen.me/2015/01/23/2015-01-23-%E5%8F%B2%E4%B8%8A%E6%9C%80%E8%AF%A6%E7%BB%86%E2%80%9C%E6%88 ...
- 史上最详细Windows版本搭建安装React Native环境配置 转载,比官网的靠谱亲测可用
史上最详细Windows版本搭建安装React Native环境配置 2016/01/29 | React Native技术文章 | Sky丶清| 95条评论 | 33530 views ...
- 史上最详细的Android Studio系列教程一--下载和安装
链接地址:http://segmentfault.com/a/1190000002401964#articleHeader4 原文链接:http://stormzhang.com/devtools/2 ...
- 测试思想-测试设计 史上最详细测试用例设计实践总结 Part2
史上最详细测试用例设计实践总结 by:授客 QQ:1033553122 -------------------------接 Part1-------------------------- 方法:这里 ...
- 史上最详细的C语言和Python的插入排序算法
史上最详细的C语言和Python的插入排序算法插入排序原理:所谓插入排序,就像我们在打牌(斗地主)时,整理我们自己手中自己的牌一样,就像是2,1,3,9,J,K,5,4,这四张牌.我们要把它其中的几张 ...
随机推荐
- chrome 中for-in 在遍历对象时的顺序问题
- UICollectionView 数据库元素分组 多种section分开显示
第一遍 复杂方法 : 数据库查询一个表中userID 然后进行分类 中间去重 获得ID个数 放到section 中 显示 #pragma mark - 查询不同的ID 各数 - (void)c ...
- NSStringFromSelector(_cmd)和self
1._cmd是隐藏的参数,代表当前方法的selector,他和self一样都是每个方法调用时都会传入的参数,动态运行时会提及如何传的这两个参数, 你在方法里加入CCLOG(@\"%@, %@ ...
- iOS-----解决Prefix Header出错的问题
我们在使用 Prefix Header 预编译文件时有时会遇到如下的报错 clang: error: no such file or directory: '/Users/linus/Dropbox/ ...
- 如何写一个LaTeX类文件,并设计你自己的简历
2017/8/29 20:26:03 原文地址 https://www.sharelatex.com/blog/2011/03/27/how-to-write-a-latex-class-file-a ...
- Android编程实例-获取当前进程名字
下面代码是根据进程id获取进程名字: /** * 根据Pid获取当前进程的名字,一般就是当前app的包名 * * @param context 上下文 * @param pid 进程的id * @re ...
- 10day2
最多因子数 搜索 [问题描述] 数学家们喜欢各种类型的有奇怪特性的数.例如,他们认为 945 是一个有趣的数,因为它是第一个所有约数之和大于本身的奇数. 为了帮助他们寻找有趣的数,你将写一个程序扫描一 ...
- BZOJ:5092 [Lydsy1711月赛]分割序列(贪心&高维前缀和)
Description 对于一个长度为n的非负整数序列b_1,b_2,...,b_n,定义这个序列的能量为:f(b)=max{i=0,1,...,n}((b_1 xor b _2 xor...xor ...
- HDU1576 A/B
暴力出奇迹,我就知道没取余那么正当,肯定有什么奇淫怪巧,果然5分钟A掉. #include<cstdio> #include<cstdlib> #include<iost ...
- scrapy 伪装代理和fake_userAgent的使用
伪装浏览器代理 在爬取网页是有些服务器对请求过滤的不是很高可以不用ip来伪装请求直接将自己的浏览器信息给伪装也是可以的. 第一中方法: 1.在setting.py文件中加入以下内容,这是一些浏览器的头 ...