Java集合(4)一 红黑树、TreeMap与TreeSet(下)
目录
Java集合(1)一 集合框架
Java集合(2)一 ArrayList 与 LinkList
Java集合(3)一 红黑树、TreeMap与TreeSet(上)
Java集合(4)一 红黑树、TreeMap与TreeSet(下)
Java集合(5)一 HashMap与HashSet
引言
在Java集合(3)一 红黑树、TreeMap与TreeSet(上)中从二叉树的遍历、添加和删除引申到了红黑树的遍历、添加和删除。对二叉树结构有了一定的了解,在这篇文章中将会对红黑树进行详细的说明。
红黑树
二叉树在理想情况下时间复杂度是O(logn),最坏情况下当插入的数据由小到大或者由大到小排列的时候,二叉树就变成了一个链表,而我们知道链表检索的时间复杂度是O(n),效率非常差,所以出现了AVL树和红黑树来改变这种状况。同时由于AVL树的极端平衡特性,导致添加和删除数据后需要过多的旋转操作来保证AVL树平衡的特征,所以TreeMap中会使用红黑树来存储数据。
最好情况下的二叉树:

最差情况下的二叉树:

树旋转
当AVL树在添加或者删除节点时出现不平衡后通过什么操作来保证树的平衡性呢?这种操作就叫做书旋转。红黑树也是如此,不过红黑树更复杂,这点我们后面再说,先来看看AVL树的旋转操作。
树旋转操作是由于二叉树在添加节点时为了避免出现平衡失效的情况而做的一种操作,操作的基本原则是操作后不影响二叉树中序遍历的结果。
这里我们用AVL树来说明这个问题。AVL树是一种高度平衡的二叉树,他的任何两个节点的子树的高度最大差别为1,这样他的查找、插入和删除的时间复杂度都是O(logn),当出现不平衡情况的时候,就需要执行树旋转。
旋转操作
树的旋转操作分为两种,左旋转和右旋转,这两种旋转是相对的。通过右旋或者左旋操作我们可以使一棵树继续保持平衡状态,并不会改变中序遍历的结果,但同时也要付出相应的代价。

//右旋
private void rotateRight(Entry<K,V> p) {
if (p != null) {
Entry<K,V> l = p.left;
p.left = l.right;
if (l.right != null) l.right.parent = p;
l.parent = p.parent;
if (p.parent == null)
root = l;
else if (p.parent.right == p)
p.parent.right = l;
else p.parent.left = l;
l.right = p;
p.parent = l;
}
}
//左旋
private void rotateLeft(Entry<K,V> p) {
if (p != null) {
Entry<K,V> r = p.right;
p.right = r.left;
if (r.left != null)
r.left.parent = p;
r.parent = p.parent;
if (p.parent == null)
root = r;
else if (p.parent.left == p)
p.parent.left = r;
else
p.parent.right = r;
r.left = p;
p.parent = r;
}
}
AVL树旋转的几种情况
当树的任何两个节点的子树的高度差大于1的时候,就需要进行旋转以保证任何两个节点的子树的高度最大差别为1。哪几种情况下需要进行树旋转操作?
1.左左情况,左节点比右节点多两个节点,并且多出的节点都在左子树;

2.右右情况,右节点比左节点多两个节点,并且多出的节点都在右子树;

3.左右情况,左节点或者右节点多出两个节点,多出的第一个节点在左子树,第二个节点在右子树;

4.右左情况,左节点或者右节点多出两个节点,多出的第一个节点在右子树,第二个节点在左子树;

红黑树的特性
明白了AVL树的旋转操作,再来看红黑树就简单多了,红黑树就是一颗满足一定条件的,相对平衡的二叉树,是二叉树和AVL树的一种折中。
红黑树的添加删除操作同二叉树一样,但是当添加删除等操作后使红黑树失去了他的特性后,就需要进行旋转操作来恢复红黑树的特性。
红黑树需要满足以下几点性质:
1.每个节点要么是红色,要么是黑色。
2.根节点永远是黑色的。
3.所有的叶节点都是空节点(即 null),并且是黑色的。
4.每个红色节点的两个子节点都是黑色。(从每个叶子到根的路径上不会有两个连续的红色节点)
5.从任一节点到其子树中每个叶子节点的路径都包含相同数量的黑色节点。
性质1和性质2很好理解。性质3在Java里面指的是空节点,一般不用考虑。性质4保证了从根到叶子节点的最长路径最多只能是最短路径的两倍,根据性质5建立一颗黑色节点为3的红黑树,最短路径为黑-黑-黑,最长路径为黑-红-黑-红-黑-红(因为每个红色节点的两个子节点都是黑色,红色则不可连续)。
下图是一颗标准的红黑树:

红黑树添加后修复
在上一篇文章中的添加操作后调用了fixAfterInsertion(e)方法用来修复被破坏的红黑树性质。
一般默认每个添加的节点都为红色,因为添加的节点如果为黑色,那就一定会破坏性质5,而且很难修复。但如果添加的是红色节点,有可能就不会破坏任何性质,也可能会破坏性质4导致连续的红色节点,可以通过变色和旋转来修复。
在添加红色节点时可能会遇到以下几种情况:
1.新节点为根节点,父节点为空,破坏性质2,修复红色为黑色即可。

2.新节点的父节点为黑色,添加的新节点为红色,不破坏任何性质。

3.新节点的父节点为红色,同时父节点的兄弟节点也为红色(根据性质4,父节点的父节点为黑色),添加的新节点也为红色,破坏了性质4,修复父节点和父节点的兄弟节点为黑色,同时父节点的父节点为红色,保证性质5不被破坏。

4.新节点的父节点为红色,同时父节点的兄弟节点为黑色或为空(空也为黑色)。如果新节点为父节点的左节点,但新节点的父节点为祖父节点的右节点;或者新节点为父节点的右节点,但新节点的父节点为祖父节点的左节点,就需要先右旋或者左旋,然后转换成情况5,再进行一次着色和旋转。

5.新节点的父节点为红色,同时父节点的兄弟节点为黑色或为空(空也为黑色)。如果新节点为父节点的左节点,同时新节点的父节点也为祖父节点的左节点;或者新节点为父节点的右节点,同时新节点的父节点也为祖父节点的右节点。设置新节点的父节点为黑色,设置新节点的祖父节点为红色,然后左旋或者右旋即可。

private void fixAfterInsertion(Entry<K,V> x) {
x.color = RED;
//情况2 x.parent.color == BLACK
while (x != null && x != root && x.parent.color == RED) {
if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
Entry<K,V> y = rightOf(parentOf(parentOf(x)));
//情况3 父节点的兄弟节点也为红色 不需要旋转
if (colorOf(y) == RED) {
setColor(parentOf(x), BLACK);
setColor(y, BLACK);
setColor(parentOf(parentOf(x)), RED);
x = parentOf(parentOf(x));
} else {
//情况4 父节点的兄弟节点为黑色 父节点为祖父节点的左节点 x为父节点的右节点 先左旋变为情况5
if (x == rightOf(parentOf(x))) {
x = parentOf(x);
rotateLeft(x);
}
//情况5 父节点的兄弟节点为黑色 父节点为祖父节点的左节点 x也为父节点的左节点 改变父节点为黑色 祖父节点为红色 然后祖父节点右旋
setColor(parentOf(x), BLACK);
setColor(parentOf(parentOf(x)), RED);
rotateRight(parentOf(parentOf(x)));
}
} else {
Entry<K,V> y = leftOf(parentOf(parentOf(x)));
//情况3
if (colorOf(y) == RED) {
setColor(parentOf(x), BLACK);
setColor(y, BLACK);
setColor(parentOf(parentOf(x)), RED);
x = parentOf(parentOf(x));
} else {
//情况4 父节点的兄弟节点为黑色 父节点为祖父节点的右节点 x为父节点的左节点 先右旋变为情况5
if (x == leftOf(parentOf(x))) {
x = parentOf(x);
rotateRight(x);
}
//情况5 父节点的兄弟节点为黑色 父节点为祖父节点的右节点 x也为父节点的右节点 改变父节点为黑色 祖父节点为红色 然后祖父节点左旋
setColor(parentOf(x), BLACK);
setColor(parentOf(parentOf(x)), RED);
rotateLeft(parentOf(parentOf(x)));
}
}
}
//情况1
root.color = BLACK;
}
红黑树删除后修复
红黑树在删除后调用了fixAfterDeletion(p)用来修复被破坏的红黑树性质。
由于在删除时我们采用后继节点替换的方法,替换之后只需要删除替换的节点即可。这样删除节点的问题就可以转换为删除至多只有1个孩子节点的问题(因为后继节点至多只有右孩子,或者没有孩子)。
删除时有以下几种情况:
1.删除的节点为红色,根据红色节点不可连续,则他的父节点和子节点都为黑色,直接用他的黑色子节点替换即可,删除了红色节点不会破坏性质5。
2.删除的节点为黑色,但是儿子为红色,直接用红色节点替换,替换后变红色节点为黑色即可。
3.删除的节点为黑色,同时儿子也为黑色,这种情况比较复杂,又可以分为几种情况:
将儿子命名为S,儿子替换后的父亲命名为F,儿子替换后的兄弟命名为B,兄弟的左节点命名为BL,兄弟的右节点命名为BR,情况3又可以分为以下几种情况:
4.F为任意色,B为红色,将F左旋转,并交换F和B的颜色,则通过各自路径的黑色节点数目不变,但S现在有了一个红色的父节点F,一个黑色的兄弟节点B,则情况可以变成5、6或者7。

5.F为任意色,B为黑色,BL和BR也为黑色,只需要将B的颜色设置为红色,则通过B的路径少了一个黑色节点和通过S的黑色节点相等了,但通过F的路径少了一个黑色节点,可以重新从第一种情况进行迭代。

6.F为任意色,B为黑色,BL为红色,BR为黑色,将B右旋,这样就变成了情况7。

7.F为任意色,B为黑色,BL为黑色,BR为红色,将F左旋,同时交换F和B和颜色即可。

private void fixAfterDeletion(Entry<K,V> x) {
while (x != root && colorOf(x) == BLACK) {
if (x == leftOf(parentOf(x))) {
Entry<K,V> sib = rightOf(parentOf(x));
//情况4 F为任意色,B为红色 情况可以变成5、6或者7
if (colorOf(sib) == RED) {
setColor(sib, BLACK);
setColor(parentOf(x), RED);
rotateLeft(parentOf(x));
sib = rightOf(parentOf(x));
}
//情况5 F为任意色,B为黑色,BL和BR也为黑色
if (colorOf(leftOf(sib)) == BLACK &&
colorOf(rightOf(sib)) == BLACK) {
setColor(sib, RED);
x = parentOf(x);
} else {
//情况6 F为任意色,B为黑色,BL为红色,BR为黑色
if (colorOf(rightOf(sib)) == BLACK) {
setColor(leftOf(sib), BLACK);
setColor(sib, RED);
rotateRight(sib);
sib = rightOf(parentOf(x));
}
//情况7 F为任意色,B为黑色,BL为黑色,BR为红色
setColor(sib, colorOf(parentOf(x)));
setColor(parentOf(x), BLACK);
setColor(rightOf(sib), BLACK);
rotateLeft(parentOf(x));
x = root;
}
} else { // symmetric
Entry<K,V> sib = leftOf(parentOf(x));
//情况4 F为任意色,B为红色 情况可以变成5、6或者7
if (colorOf(sib) == RED) {
setColor(sib, BLACK);
setColor(parentOf(x), RED);
rotateRight(parentOf(x));
sib = leftOf(parentOf(x));
}
//情况5 F为任意色,B为黑色,BL和BR也为黑色
if (colorOf(rightOf(sib)) == BLACK &&
colorOf(leftOf(sib)) == BLACK) {
setColor(sib, RED);
x = parentOf(x);
} else {
//情况6 F为任意色,B为黑色,BL为红色,BR为黑色 旋转着色后变为情况7
if (colorOf(leftOf(sib)) == BLACK) {
setColor(rightOf(sib), BLACK);
setColor(sib, RED);
rotateLeft(sib);
sib = leftOf(parentOf(x));
}
//情况7 F为任意色,B为黑色,BL为黑色,BR为红色
setColor(sib, colorOf(parentOf(x)));
setColor(parentOf(x), BLACK);
setColor(leftOf(sib), BLACK);
rotateRight(parentOf(x));
x = root;
}
}
}
setColor(x, BLACK);
}
总结
理解了红黑树遍历,删除添加等操作的分析,再理解TreeMap<K,V>实现逻辑就会很容易。TreeMap<K,V>所有的操作都是在红黑树的基础上执行的,红黑树的每一个节点对应为TreeMap<K,V>的一个Entry<K,V>。
TreeSet<E>由于在实现上完全使用了TreeMap<K,V>的key来实现,所以TreeSet<E>的所有操作一样是建立在红黑树的基础上。
Java集合(4)一 红黑树、TreeMap与TreeSet(下)的更多相关文章
- 【深入理解Java集合框架】红黑树讲解(上)
来源:史上最清晰的红黑树讲解(上) - CarpenterLee 作者:CarpenterLee(转载已获得作者许可,如需转载请与原作者联系) 文中所有图片点击之后均可查看大图! 史上最清晰的红黑树讲 ...
- Java集合详解6:TreeMap和红黑树
Java集合详解6:TreeMap和红黑树 初识TreeMap 之前的文章讲解了两种Map,分别是HashMap与LinkedHashMap,它们保证了以O(1)的时间复杂度进行增.删.改.查,从存储 ...
- Java容器汇总【红黑树需要再次学习】
1,概述 2,Collection 2.1,Set[接触比较少] 2.1.1 TreeSet 底层由TreeMap实现 基于红黑树实现,支持有序性操作,例如根据一个范围查找元素的操作.但是查找效率不如 ...
- 转:【Java集合源码剖析】TreeMap源码剖析
前言 本文不打算延续前几篇的风格(对所有的源码加入注释),因为要理解透TreeMap的所有源码,对博主来说,确实需要耗费大量的时间和经历,目前看来不大可能有这么多时间的投入,故这里意在通过于阅读源码对 ...
- java随笔——HashMap与红黑树
前言: hashmap是一种很常用的数据结构,其使用方便快捷,接下来笔者将给大家深入解析这个数据结构,让大家能在用的时候知其然,也知其所以然. 一.Map 首先,从最基本的讲起,我们先来认识一下map ...
- Java面试题之红黑树原理
红黑树原理: 每个节点都只能是红色或黑色的: 根节点是黑色的: 每个叶节点(空节点)是黑色的: 如果一个节点是红色的,那么他的子节点都是黑色的: 从任意一个节点到其每个子节点的路径都有相同数目的黑色节 ...
- Java集合详解7:HashSet,TreeSet与LinkedHashSet
今天我们来探索一下HashSet,TreeSet与LinkedHashSet的基本原理与源码实现,由于这三个set都是基于之前文章的三个map进行实现的,所以推荐大家先看一下前面有关map的文章,结合 ...
- Java集合源码分析(六)TreeSet<E>
TreeSet简介 TreeSet 是一个有序的集合,它的作用是提供有序的Set集合.它继承于AbstractSet抽象类,实现了NavigableSet<E>, Cloneable, j ...
- Java集合源码分析(十)——TreeSet
简介 TreeSet就是一个集合,里面不能有重复的元素,但是元素是有序的. TreeSet其实就是调用了TreeMap实现的,所以,它也不是线程安全的.可以实现自然排序或者根据传入的Comparato ...
- Java集合--概述
目录 Java集合--概述 摘要 图示 正文 Java集合--概述 摘要 本文主要介绍集合的整体概念,并作为接下来Java集合实现类讲解的索引. 图示 这是在网上看到了这样一张图,感觉很清晰, ...
随机推荐
- 20172305 暑假作业 之 TimeCalculate & Save Iron Man
20172305 暑假作业 之 TimeCalculate & Save Iron Man TimeCalculate 项目介绍 项目名称: TimeCalculate 项目简介: 本项目基于 ...
- 团队开发--NABCD
团队成员介绍: 李青:绝对的技术控,团队中扮演“猪”的角色,勤干肯干,是整个团队的主心骨,课上紧跟老师的步伐,下课谨遵老师的指令,课堂效率高,他的编程格言“没有编不出来的程序,只有解决不了的bug”. ...
- TCP系列51—拥塞控制—14、TLP、ER与拥塞控制
一.概述 这里的重点是介绍TLP.ER与拥塞控制并不是介绍TLP和ER本身,因此TLP和ER的详细内容请翻前文. 在TLP与拥塞控制的交互中有几个点需要注意 1.TLP触发的重传后,TCP仍然处于Op ...
- Spring – 缓存注解
Spring缓存抽象概述 Spring框架自身并没有实现缓存解决方案,但是从3.1开始定义了org.springframework.cache.Cache和org.springframework.ca ...
- Docker 安装与常用命令介绍
docker的镜像文件作用就是:提供container运行的文件系统层级关系(基于AUFS实现),所依赖的库文件.已经配置文件等等. 安装docker yum install -y docker 启动 ...
- 结对编程--fault,error,failure的程序设计
一.结对编程内容: 1.不能触发Fault. 2.触发Fault,但是不触发Error. 3.触发Error,但不触发Failure. 二.结对编程人员 1.周浩,周宗耀 2.结对截图: 三.结对项目 ...
- 奇异值分解(SVD)原理详解及推导(转载)
转载请声明出处http://blog.csdn.net/zhongkejingwang/article/details/43053513 在网上看到有很多文章介绍SVD的,讲的也都不错,但是感觉还是有 ...
- 对象库(UI MAP)
目的:能够使用配置文件存储被测页面上元素的定位方式和定位表达式,做到定位数据和程序的分离. 测试程序写好以后,可以方便不具备编码能力的测试人员进行自定义修改和配置 : package dataDriv ...
- 【EF】EF Code-First数据迁移
Code-First数据迁移 首先要通过NuGet将EF升级至最新版本. 新建MVC 4项目MvcMigrationDemo 添加数据模型 Person 和 Department,定义如下: usi ...
- day 05 万恶之源-基本数据类型(dict)
05. 万恶之源-基本数据类型(dict)本节主要内容:1. 字典的简单介绍2. 字典增删改查和其他操作3. 字典的嵌套⼀一. 字典的简单介绍字典(dict)是python中唯⼀一的⼀一个映射类型.他 ...