目录
上期帖子我讲述了二叉树的基本原理和相关知识点,这期我来讲述一个新的更加重要,非常重要的知识点,它就是红黑树。
1. 引言
大家都知道,HashMap的底层原理是面试官经常会问到的一个问题,那么大家知道在JDK1.8之后,HashMap 的底层是有什么改变吗?
在JDK1.8之前,HashMap底层是由数组+链表组成的。
在JDK1.8之后,HashMap底层是由数组+链表+红黑树构成的。添加了红黑树这种数据结构,优化了 HashMap,提高了数据的检索效率。这里只作简单介绍,如果各位想要深入了解 HashMap 的底层原理,可以去看我的另一篇文章
2. 红黑树的简单介绍
第一:红黑树它本身也是一个自平衡的二叉搜索树,但不是高度平衡的,和平衡二叉树略有区别,是一种特殊的二叉搜索树,在原本的二叉树节点的基础上,多了一个存储改节点颜色的数据。
第二:1972年时被称为平衡二叉B树,在1978年改名为红黑树。上期帖子我讲述了平衡二叉树,想必各位也知道,平衡二叉树需要经常的进行旋转,这样会非常浪费资源和时间。因此,在平衡二叉树的基础上对它做了进一步的优化,就得到了如今的红黑树。
第三:红黑树必须满足特有的红黑规则。
3. 红黑规则
相信说到了这里,一定有很多人好奇,这个红黑规则到底是什么呢?接下来就让我给你一一道来。
红黑树规则一定有以下五点,满足所有以下五点,即可被称为红黑树。
(1)每个节点要么是红色,要么是黑色。
(2)根节点必须是黑色,插入效率更高,后面会说到。
(3)如果一个节点没有子节点或者父节点,则该节点相应的指针属性为Nil,这些Nil视为叶节点,每个叶节点(Nil)是黑色的。
(4)如果某一个节点是红色的,那么它的子节点必须是黑色的(不能出现两个红色节点相连的情况)。
(5)对每一个节点,从该节点到其所有后代叶子节点的简单路径上,均包含相同数目的黑色节点。
如下图,就是一个简单的红黑树:
可以看到,根节点13即为黑色,所有叶子节点Nil也为黑色。
有一点要注意,虽然不能出现两个红色节点相连的情况,但是允许出现两个黑色节点相连。
还有一点也要注意,“简单路径” 就是指从根节点向下寻找,不可以回头;叶子节点就是指一个结点最下面的那一层,即Nil。
此外,我们都知道,二叉树的每个节点中,既保存了父结点的地址,左子节点的地址,右子节点的地址,又保存了自身数据。
但在红黑树中,每个节点除了保存上述四个数据之外,还要多一个位置来保存该节点的颜色。
如下图所示:
说完红黑树的基本概念,我们再来了解一下红黑树添加节点的规则。
4. 红黑树中默认插入节点是什么颜色效率更高?
这里要记住,红黑树中,添加的节点默认颜色均为红色,因为这样效率高。
可能有些小伙伴就会提出疑问了,为什么默认添加红色效率高呢?这里我举一个例子你就明白了。
假设有以下三个节点,20,18,23。
现在将它们插入到红黑树中
第一种情况,假设添加的节点默认颜色为黑色
1. 添加节点20时,因为根节点要求黑色,所以不需要改变颜色,添加20后得到的结果如下:
2. 添加节点18时,根据红黑规则,节点18应为红色,添加后如下所示:
3. 同理,添加节点23时,根据红黑规则,节点23应为红色,添加后如下所示:
总结上述三步,我们发现,节点18和节点23都修改了颜色,需要改变两次。
那我们再来看第二种情况,假设添加的节点默认颜色均为红色,会是什么情况呢?
我们来演示一遍,
1. 先添加节点20,因为根节点要求黑色,而现在默认为红色,所以需要进行修改
添加后结果如下:
2. 再添加节点18,默认为红色,添加之后如下,发现没有违反红黑规则,所以不需要进行修改
3. 同理,再添加节点23,默认为红色,添加之后如下,发现没有违反红黑规则,所以不需要进行修改
总结上述三步,我们发现,若默认插入节点颜色为黑色,需要修改两次;若插入节点默认为红色,需要修改一次,所以默认红色时效率更高。感兴趣的小伙伴可以再换一个红黑树试一下,你会发现永远都是默认红色时,插入效率最高。
5. 红黑树中插入节点的规则
其实,除了添加节点默认为红色之外,还有一些别的规则我们需要注意,如下图所示:
OK,我们来试着做一道题,感受一下上述图中的插入规则,看如下图片,将图片中的各个节点插入到红黑树中,我们来试着做一下吧!
第一步:先插入节点20,因为默认为红色,20为第一个节点,所以修改颜色为红色,如下:
第二步:插入节点18,18与20作比较,比20小,插入到20的左边作为左子节点,不需要修改颜色,插入后如下所示:
第三步:插入节点23,23与20作比较,比20大,插入到20的右边作为右子节点,不需要修改颜色,插入后结果如下所示:
第四步:插入节点22,22与20作比较,比20大,插入到20的右边,再与23作比较,插入到23的左边,默认为红色,插入后为如下
可以看出,已经违反了红黑规则,所以需要做出修改,我们看上述插入规则,非根,父节点红色,叔叔节点红色,所以进行四步操作;
(1)将父设为黑色,将叔叔设为黑色。得到如下
(2)将祖父设为红色。得到如下
(3)若祖父为根节点,再将根变为黑色。
(4)如果祖父为非根,将祖父设为当前节点在进行其他判断。此时判断,发现已经满足了红黑树规则,所以完成了插入操作。
经过一系列操作之后,得到的结果如下:
第五步:插入节点17,先判断,应插入在18的左侧。我们可以看到,若插入的节点非根且父节点为黑色,则不需要任何操作。插入后如下图所示:
第六步:插入节点24,经判断应插入在23的右侧。可以看到,若插入的节点非根且父节点为黑色,则不需要任何操作。
第七步:插入节点19,经判断应插入在18的右侧。可以看到,若插入的节点非根且父节点为黑色,则不需要任何操作。
第六第七步一并操作,得到的结果如下:
OK,到这里上面7个数据就全部插入完成了,各位是不是觉得也不过如此啊,别急,还没有到难点的地方。下面我再做补充,假设我要在上述红黑树中再插入节点15和节点14,该怎么做呢?
我们来试试看
先插入15,进行判断,应该在17的左侧,插入后得到结果如下:
发现违反了红黑规则,我们对其进行调整,可以看到,节点15的父节点17为红色,叔叔节点19也为红色,我们还要进行上面的四步操作:
(1)将父设为黑色,将叔叔设为黑色。得到如下
(2)将祖父设为红色。得到如下
(3)若祖父为根节点,再将根变为黑色。这里祖父18不为根节点,无需变色。
(4)如果祖父为非根,将祖父设为当前节点在进行其他判断。这里祖父18为红色,父节点为20黑色,不需要做任何修改,此时插入即完成,最终得到的结果如下:
再插入节点14,先判断,插入在节点15的左边,插入结果如下:
可以看出,违反了红黑规则,所以对其进行判断修改,得出父节点15为红色,叔叔节点Nil为黑色,所以需要进行下面三步:
(1)将“父”设为黑色,修改后如下
(2)将“祖父”设为红色,修改后如下
(3)以祖父为支点进行右旋,这里旋转时不需要考虑Nil的,所以如下
经右旋后,得到的结果如下:
此时再进行判断,已经满足了红黑树的性质,插入完成!
各位觉得到这里就完了吗?我来提一个问题,如果我插入的不是14节点,而是16节点,又该怎么做呢?我先把插入前的图片重新粘贴在这里
我们来试一下吧!
先判断,16要插入到15的右侧
插入后得到结果为
来进行判断,16的父节点15为红色,叔叔节点Nil为黑色,这是我们看规则,要求以父节点进行右旋,如下
得到的旋转后的结果为
这是我们再进行判断,此时判断要站在旋转支点15的基础上进行判断,15的父节点16为红色,叔叔节点为Nil黑色,此时各位就会发现,已经和上述插入节点14时的情况相同了,仍然是进行三步操作
(1)将“父”设为黑色,修改后如下
(2)将“祖父”设为红色,修改后如下
(3)以祖父为支点进行右旋,这里旋转时不需要考虑Nil的,如下所示:
旋转后再将Nil进行补充得到最终结果
此时我们再来判断,发现已经满足了红黑规则,插入完成。
OK,到这里关于红黑树的添加节点的规则就全部讲解完毕了。如果各位小伙伴哪里不懂的,可以多看几遍,仔细阅读我所描述的文字和所配的图片,相信一定能攻克困难,拿下红黑树。