JDK1.7的时候 HashMap是由数组+链表实现 -> JDK1.8时推出的红黑树,用于解决出现哈希冲突的情况。这是红黑树在面试时常被问到的点。下面就从二叉搜索树来一步一步介绍理解红黑树。
红黑树(Red Black Tree) 是一种自平衡二叉查找树,在1972年由Rudolf Bayer发明的,当时被称为平衡二叉B树(symmetric binary B-trees)。后来,在1978年被 Leo J. Guibas 和 Robert Sedgewick 修改为如今的“红黑树”。
要想理解红黑树,先介绍其最基本的结构二叉排序树,也叫二叉搜索树:二叉树排序是指树中节点的度不大于2的有序树,它有如下性质:
1.若任意结点的左子树不空,则左子树上所有结点的值均不大于它的根结点的值。
2. 若任意结点的右子树不空,则右子树上所有结点的值均不小于它的根结点的值。
3.任意结点的左、右子树也分别为二叉排序树。
可以见到,二叉搜索树有其优良的性质,可以算是二分算法的数据结构版,若用它来存储数据,那么查找数据时的时间复杂度一般为logn。
但是,二叉搜索树可能会出现一种问题:退化成一个链表;
可以见到,此时的二叉搜索树的性能就降低到了n。那么该如何构建二叉搜索树而防止这种问题呢?
1.用AVL树:完全平衡二叉树(追求极致的平衡,转成极致的情况,一般情况下很难用到)
2.用红黑树。红黑树就是一种特殊的二叉搜索树,也是一种自平衡二叉查找树。
在HashMap中为何舍弃AVL树而采取红黑树:
AVL树更加严格平衡,因此可以提供更快的查找效果,因此,对于密集型查找任务,使用AVL树,而对于插入密集型任务,应该使用红黑树。一方面,AVL树比红黑树保持更加严格的平衡。AVL树中从根到最深叶的路径最多为~1.44 lg(n + 2),而在红黑树中最多为~2 lg(n + 1),因此,在AVL树中查找通常更快。另一方面,在插入和删除时,AVL树速度较慢:因为需要更高的旋转次数才能在修改时正确地重新平衡数据结构。
对于通用实现(即先验并不清楚查找是否是操作的主要部分),RedBlack树是首选:它们更容易实现,并且在常见情况下更快 - 无论数据结构如何经常被搜索修改。
在CurrentHashMap中是加锁了的,实际上是读写锁,如果写冲突就会等待,如果插入时间过长必然等待时间更长,而红黑树相对AVL树他的插入更快!
下面介绍红黑树的性质:
- 每个结点不是红色就是黑色,根节点都是黑色root
- 不可能有连载一起的红色结点
- 根结点到所有的【空结点】的路径上都具有相同数量的黑色结点
- 根据第三条性质可以推出,红黑树中每个节点到【所在子树中的空节点】的路径上都具有相同数量的黑色节点
- 如果节点是红色节点,则这个节点要么没有子节点,要么有两个子节点,并且都是黑色节点,因为需要满足第二条和第三条性质
- 如果节点是黑色节点,并且有一个黑色子节点,则另一个子节点一定存在,为红色节点或者黑色节点,因为需要满足第三条性质
红黑树有三种常用操作:
- 改变颜色
- 左旋 右子为轴,当前结点左旋 轴结点的左孩子变成待旋转结点的右孩子
-
右旋
什么时候要变色左旋右旋?
- 破坏了红黑树规则的情况下要旋转变色。
旋转和颜色变换规则:所有插入的点默认为红色(因为如果结点都是黑色的话就直接满足了红黑树所有的性质,变换不了)
- 变颜色的情况:当前结点的父亲是红色,且它的祖父结点的另一个子节点也是红色(叔叔结点):
- 把父节点变为黑色
- 把叔叔结点也变为黑色
- 把祖父结点设为红色
- 把指针定义到祖父结点设为当前要操作的
- 左旋:当前父节点是红色,叔叔是黑色的时候,且当前的结点是右子树,左旋,以父节点作为左旋
- 右旋:当前父节点是红色,叔叔是黑色的时候,且当前的结点是左子树,右旋
- 把父结点变为黑色
- 把祖父结点变为红色
- 以祖父结点旋转
这里,加了一个6之后,进行了颜色变换,将操作结点放到了12上,此时满足左结点旋转情况,然后进行左旋。
到这,红黑树的基本性质和操作介绍完毕。下面是附带红黑树的java源码
package cs0416;
import com.sun.source.tree.Tree;
import javax.swing.tree.TreeNode;
//这是红黑树的第一种实现(等价于2-3-4查找树、4阶B树)
public class RBT_1 {
private TreeNode root;
private TreeNode sentry; //定义哨兵结点,其它结点的空指针都指向哨兵节点 应该定义final 但是为什么会出错
private int size;
private int height ; //定义高度
private static class TreeNode{
public Comparable comparable;
public TreeNode parent ;
public TreeNode left ;
public TreeNode right;
public boolean color;
public TreeNode(Comparable comparable , boolean color){
this.comparable = comparable;
this.color = color;
this.parent = null;
this.left = null;
this.right = null;
}
}
public RBT_1(){
this.sentry = new TreeNode(null , false);//哨兵结点为黑色
this.root = sentry;
this.size = 0;
this.height = 0;
}
//基本操作 查找某棵子树的最左结点和最右结点
private TreeNode getMin(TreeNode node){
while(node.left != sentry)
node = node.left;
return node;
}
private TreeNode getMax(TreeNode node){
while(node.right != sentry)
node = node.right;
return node;
}
//旋转操作
//左旋 将结点与右结点交换角色与颜色
private void rotateLeft(TreeNode node){
TreeNode temp = node.right;
//调整结点与其右子结点的左子结点的关系
node.right = temp.left;
//调整结点的父节点与其右子结点的关系
temp.parent = node.parent;
if(node == root)
root = temp;
else if(node == node.parent.left)
node.parent.left = temp;
else
node.parent.right = temp;
//调整结点与其右子结点关系,交换颜色
temp.left = node;
node.parent = temp;
boolean flag = node.color;
node.color = temp.color;
temp.color = flag;
return;
}
//右旋
private void rotateRight(TreeNode node){
TreeNode temp = node.left;
node.left = temp.right;
temp.parent = node.parent;
if(node == root)
root =temp;
else if(node == node.parent.left)
node.parent.left = temp;
else
node.parent.right = temp;
temp.right = node;
node.parent = temp;
boolean flag = node.color;
node.color = temp.color;
temp.color = flag;
return;
}
//变色操作:子节点都变为黑色 , 结点变为红色
private static void convert(TreeNode node){
node.color = true;
node.left.color = false;
node.right.color = false;
return;
}
//查找操作
private TreeNode search(Comparable comparable){
TreeNode node = root;
while(node != sentry){
if(comparable.compareTo(node.comparable) == 0)
break;
else if(comparable.compareTo(node.comparable) < 0)
node = node.left;
else
node = node.right;
}
return node;
}
//交换操作 交换结点保存的元素
private void swap(TreeNode one , TreeNode other){
Comparable comparable = one.comparable;
one.comparable = other.comparable;
other.comparable = comparable;
return;
}
//替换结点
private void replace(TreeNode source, TreeNode target) {
if (target == root)
root = source;
else if (target == target.parent.left)
target.parent.left = source;
else
target.parent.right = source;
source.parent = target.parent;
return;
}
//添加元素
public void insert(Comparable comparable) {
TreeNode node = root;
TreeNode temp = sentry;//新节点的父节点
//查找添加的位置
while (node != sentry) {
temp = node;
if (comparable.compareTo(node.comparable) < 0)
node = node.left;
else
node = node.right;
}
node = new TreeNode(comparable, true);//新节点都为红色
node.parent = temp;
if (temp == sentry)//添加第一个元素
root = node;
else if (comparable.compareTo(temp.comparable) < 0)
temp.left = node;
else
temp.right = node;
node.left = sentry;
node.right = sentry;
//自底向上调整红黑树
correct_insert(node);
size++;
return;
}
//添加元素后修正节点,保持红黑树的性质
private void correct_insert(TreeNode node) {
TreeNode temp;
while (node.parent.color) {//节点和父节点都为红色,并且父节点不是根节点
if (node.parent == node.parent.parent.left) {//父节点是左子节点
temp = node.parent.parent.right;
if (!temp.color) {//父节点的兄弟节点为黑色,进行旋转,旋转之后,修正结束
if (node == node.parent.right) {
node = node.parent;
rotateLeft(node);
}
rotateRight(node.parent.parent);
}else {//父节点的兄弟节点为红色,进行变色,变色之后,向上继续修正
node = node.parent.parent;
convert(node);
}
}else {//父节点是右子节点
temp = node.parent.parent.left;
if (!temp.color) {//父节点的兄弟节点为黑色,进行旋转,旋转之后,修正结束
if (node == node.parent.left) {
node = node.parent;
rotateRight(node);
}
rotateLeft(node.parent.parent);
}else {//父节点的兄弟节点为红色,进行变色,变色之后,向上继续修正
node = node.parent.parent;
convert(node);
}
}
}
if (node == root) {//当前节点为根节点,则根节点变为红色,红黑树的黑色高度将加一
root.color = false;
height++;
}
return;
}
//传入被删节点,查找替代节点,使用替代节点的元素替代被删节点的元素,保留被删节点的颜色,删除替代节点
private Comparable delete(TreeNode node) {
TreeNode substitute;//替代节点
TreeNode start;//调整红黑树的起始节点
//优先使用前驱节点作为替代节点
if (node.left != sentry) {//存在左子节点,以前驱节点替代被删节点
substitute = getMax(node.left);
start = substitute.left;
}else if (node.right != sentry) {//存在右子节点,以后继节点替代被删节点
substitute = getMin(node.right);
start = substitute.right;
}else {//不存在子节点,被删节点作为替代节点
substitute = node;
start = sentry;
}
swap(node, substitute);//替代元素
replace(start, substitute);//起始节点取代替代节点的位置
if (!substitute.color)//替代节点的颜色,即删除的颜色是黑色
correct_delete(start);
size--;
return substitute.comparable;
}
//删除元素后修正节点,保持红黑树的性质
private void correct_delete(TreeNode node) {
TreeNode temp;
while (node != root && !node.color) {//当前节点为黑色节点,并且不为根节点
if (node == node.parent.left) {//当前节点是左子节点
temp = node.parent.right;
if (temp.color) {//兄弟节点为红色
rotateLeft(node.parent);
}else if (!temp.left.color && !temp.right.color) {//兄弟节点为黑色,左右子节点都为黑色
temp.color = true;
node = node.parent;
}else {//兄弟节点为黑色,左右子节点中存在红色
if (!temp.right.color) {//兄弟节点为黑色,其右子节点为黑色,则左子节点为红色
rotateRight(temp);
}else {//兄弟节点为黑色,其右子节点为红色
temp.right.color = false;
rotateLeft(node.parent);
break;
}
}
}else {//当前节点为右子节点
temp = node.parent.left;
if (temp.color) {//兄弟节点为红色
rotateRight(node.parent);
}else if (!temp.left.color && !temp.right.color) {//兄弟节点为黑色,左右子节点都为黑色
temp.color = true;
node = node.parent;
}else {//兄弟节点为黑色,左右子节点中存在红色
if (!temp.left.color) {//兄弟节点为黑色,其左子节点为黑色,则右子节点为红色
rotateLeft(temp);
}else {//兄弟节点为黑色,其左子节点为红色
temp.left.color = false;
rotateRight(node.parent);
break;
}
}
}
}
if (node == root)//当前节点为根节点,则红黑树的黑色高度将减一
height--;
else
node.color = false;
return;
}
}