目录

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(下)的更多相关文章

  1. 【深入理解Java集合框架】红黑树讲解(上)

    来源:史上最清晰的红黑树讲解(上) - CarpenterLee 作者:CarpenterLee(转载已获得作者许可,如需转载请与原作者联系) 文中所有图片点击之后均可查看大图! 史上最清晰的红黑树讲 ...

  2. Java集合详解6:TreeMap和红黑树

    Java集合详解6:TreeMap和红黑树 初识TreeMap 之前的文章讲解了两种Map,分别是HashMap与LinkedHashMap,它们保证了以O(1)的时间复杂度进行增.删.改.查,从存储 ...

  3. Java容器汇总【红黑树需要再次学习】

    1,概述 2,Collection 2.1,Set[接触比较少] 2.1.1 TreeSet 底层由TreeMap实现 基于红黑树实现,支持有序性操作,例如根据一个范围查找元素的操作.但是查找效率不如 ...

  4. 转:【Java集合源码剖析】TreeMap源码剖析

    前言 本文不打算延续前几篇的风格(对所有的源码加入注释),因为要理解透TreeMap的所有源码,对博主来说,确实需要耗费大量的时间和经历,目前看来不大可能有这么多时间的投入,故这里意在通过于阅读源码对 ...

  5. java随笔——HashMap与红黑树

    前言: hashmap是一种很常用的数据结构,其使用方便快捷,接下来笔者将给大家深入解析这个数据结构,让大家能在用的时候知其然,也知其所以然. 一.Map 首先,从最基本的讲起,我们先来认识一下map ...

  6. Java面试题之红黑树原理

    红黑树原理: 每个节点都只能是红色或黑色的: 根节点是黑色的: 每个叶节点(空节点)是黑色的: 如果一个节点是红色的,那么他的子节点都是黑色的: 从任意一个节点到其每个子节点的路径都有相同数目的黑色节 ...

  7. Java集合详解7:HashSet,TreeSet与LinkedHashSet

    今天我们来探索一下HashSet,TreeSet与LinkedHashSet的基本原理与源码实现,由于这三个set都是基于之前文章的三个map进行实现的,所以推荐大家先看一下前面有关map的文章,结合 ...

  8. Java集合源码分析(六)TreeSet<E>

    TreeSet简介 TreeSet 是一个有序的集合,它的作用是提供有序的Set集合.它继承于AbstractSet抽象类,实现了NavigableSet<E>, Cloneable, j ...

  9. Java集合源码分析(十)——TreeSet

    简介 TreeSet就是一个集合,里面不能有重复的元素,但是元素是有序的. TreeSet其实就是调用了TreeMap实现的,所以,它也不是线程安全的.可以实现自然排序或者根据传入的Comparato ...

  10. Java集合--概述

    目录 Java集合--概述 摘要 图示 正文 Java集合--概述 摘要 ​ 本文主要介绍集合的整体概念,并作为接下来Java集合实现类讲解的索引. 图示 ​ 这是在网上看到了这样一张图,感觉很清晰, ...

随机推荐

  1. 谈谈javascript中的变量提升还有函数提升

    在很多面试题中,经常会看到关于变量提升,还有函数提升的题目,所以我就写一篇自己理解之后的随笔,方便之后的查阅和复习. 首先举个例子 foo();//undefined function foo(){ ...

  2. Scrum立会报告+燃尽图(Beta阶段第三次)

    此作业要求参见:https://edu.cnblogs.com/campus/nenu/2018fall/homework/2385 项目地址:https://coding.net/u/wuyy694 ...

  3. Beta周王者荣耀交流协会第六次会议

    1.立会照片 成员王超,高远博,冉华,王磊,王玉玲,任思佳,袁玥全部到齐. master:袁玥 2. 时间跨度 2017年11月15日 19:00 — 19:10 ,总计10分钟. 3. 地点 一食堂 ...

  4. RIGHT-BICEP测试第二次

    1.Right-结果是否正确? 正确 2.B-是否所有的边界条件都是正确的? 正确 3.P-是否满足性能要求? 部分满足 4.是否满足有无括号? 无 5.数字个数是否不超过十? 只是双目运算 6.能否 ...

  5. AOP:jdk的动态代理

    1.文件结构 2.建立接口 package com.wangcf.manager; public interface IUserManager { public void add(); public ...

  6. 关于双系统下Ubuntu不能访问Windows中某个盘的问题

    1.问题描述   在Ubuntu系统下访问Windows系统中磁盘时出现无法访问的情况,具体如下显示:   该问题为磁盘挂载错误,需要进行修复. 2.解决办法   (1)打开终端:如果没有安装ntfs ...

  7. Windows Forms编程实战学习:第一章 初识Windows Forms

    初识Windows Forms 1,用C#编程 using System.Windows.Forms;   [assembly: System.Reflection.AssemblyVersion(& ...

  8. int 和Integer

    Java是一个近乎纯洁的面向对象编程语言,但是为了编程的方便还是引入不是对象的基本数据类型,但是为了能够将这些基本数据类型当成对象操作,Java为每一个基本数据类型都引入了对应的包装类型(wrappe ...

  9. PAT L1 - 046 整除光棍

    https://pintia.cn/problem-sets/994805046380707840/problems/994805084284633088 这里所谓的“光棍”,并不是指单身汪啦~ 说的 ...

  10. 这些天php面试的总结

    面试总结 记录一些本人在面试中遇到的觉得有些掌握不好的面试题,下面的答案都是本人回答的,如果哪里不对的话,希望各位能够指出. 1.Git fetch和git pull的区别 Git fetch相当于从 ...