红黑树的原理及实现
- 红黑树介绍
- 节点的旋转
- 左旋
- 右旋
- 插入节点
- 删除节点
- 代码实现
红黑树介绍
红黑树(Red Black Tree) 是一种自平衡二叉查找树,是在计算机科学中用到的一种数据结构。
红黑树在进行插入和删除操作时通过特定操作保持二叉查找树的平衡,从而获得较高的查找性能。
红黑树的查找,插入和删除的时间复杂度都能够在O(log n)以内,这里的n 是树中元素的数目。
Java中的HashMap,TreeMap,ConcurrentHashMap,C++ 的STL,Linux内核中很多地方都用到了红黑树。
五大性质:
1:节点是红色或者黑色。
2:根节点是黑色。
3:每个红色节点父节点和孩子节点都是黑色(不能有连续的两个红色节点)。
4:从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。
5:所有的叶子节点都是为Null的黑色节点(也叫外部节点,目的是让红黑树变为真二叉树)。
从根节点到叶子节点的最长路径不多于最短路径的两倍长。
性质3导致了路径不能有两个相连的红色节点。最短的可能路径都是黑色节点,最长的可能路径有交替的红色和黑色节点。
性质4从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。这就表明了没有路径能多于任何其他路径的两倍长。
(假设最长路径都是红黑交替,最短路径都是黑节点,这时最长路径是最短路径的两倍)
以下就是一颗红黑树:
节点的旋转
左旋
对节点A左旋,P为A的父节点,L和R为左节点和右节点,L_L和L_R为L的左节点和右节点,R_L和R_R为R的左节点和右节点。
左旋进行的操作:
- 如果A的父节点为null,则说明A是根节点,则把根节点变为R;
如果A的父节点存在,判断A是其父节点的左孩子还是右孩子(图中A是其父节点的左孩子),若是左孩子,则将P的left变为R(右孩子则将P的Right变为R),将R的父节点变为P - 将A的父节点变为R,将R的左孩子变为A
- 将R之前的左孩子R_L的父节点变为A,将A的右孩子变为R_L
// 左旋
private void leftRotate (Node node) {
// node的右节点存在且不是null的叶子节点
if (node.getRight() != null && !node.getRight().isLeafNode()) {
// 父节点为null,node为根节点 对根节点左旋
if (node.getParent() == null) {
this.root = node.getRight();
this.root.setParent(null);
} else {
if (((Comparable)node.getObject()).compareTo(node.getParent().getObject()) < 0) {
// 当前节点为父节点的左节点
node.getParent().setLeft(node.getRight());
} else {
// 当前节点为父节点的右节点
node.getParent().setRight(node.getRight());
}
node.getRight().setParent(node.getParent());
}
node.setParent(node.getRight());
Node grandSon = node.getRight().getLeft();
if (grandSon != null ) {
grandSon.setParent(node);
}
node.getRight().setLeft(node);
node.setRight(grandSon);
}
}
右旋
右旋和左旋同理,只是方向改变一下
右旋进行的操作:
- 如果A的父节点为null,则说明A是根节点,则把根节点变为;
如果A的父节点存在,判断A是其父节点的左孩子还是右孩子(图中A是其父节点的左孩子),若是左孩子,则将P的left变为L(右孩子则将P的Right变为L),将L的父节点变为P - 将A的父节点变为L,将L的右孩子变为A
- 将L之前的右孩子L_R的父节点变为A,将A的左孩子变为L_R
// 右旋
private void rightRotate (Node node) {
// node的左节点存在且不是null的叶子节点
if (node.getLeft() != null && !node.getLeft().isLeafNode()) {
// 父节点为null,node为根节点 对根节点右旋
if (node.getParent() == null) {
this.root = node.getLeft();
this.root.setParent(null);
} else {
if (((Comparable)node.getObject()).compareTo(node.getParent().getObject()) < 0) {
// 当前节点为父节点的左节点
node.getParent().setLeft(node.getLeft());
} else {
// 当前节点为父节点的右节点
node.getParent().setRight(node.getLeft());
}
node.getLeft().setParent(node.getParent());
}
node.setParent(node.getLeft());
Node grandSon = node.getLeft().getRight();
if (grandSon != null ) {
grandSon.setParent(node);
}
node.getLeft().setRight(node);
node.setLeft(grandSon);
}
}
插入节点
由于左右节点的对称性,插入操作和删除操作都只讨论一种情况。
对红黑树插入节点时,一般会将插入的节点默认设置为红色,这样只可能破坏性质2和性质3,(因为性质4的平衡过程会比性质3复杂)。
如果插入的节点是根节点,性质2会被破坏,如果插入节点的父节点是红色,则会破坏性质3。
下面对插入操作分情况讨论:
情况1:
- 插入的是根节点,即原来的树是空树。这时会违反性质2。
解决办法:把节点颜色变为黑色
情况2:
- 插入节点的父节点是黑色,不会违反红黑树的性质,直接插入就行。
- 解决办法:不用做任何处理
情况3:
- 插入节点的父节点是红色,且叔叔节点也是红色(爷爷节点的颜色一定为黑色)
解决办法:把父节点和叔叔节点的颜色变为黑色,把爷爷节点的颜色变为红色,再对爷爷节点进行平衡操作。(爷爷节点也可能是情况3,则再对爷爷节点的爷爷节点进行平衡操作,最后总会变为其他的四种情况)
如下例子:11是插入的节点,把父节点和叔叔节点变为黑色,爷爷节点变为红色,再对爷爷节点进行平衡操作。进行情况3的操作后变成了情况4
情况4:
- 插入节点的父节点是红色,叔叔节点是黑色,且插入节点是其父节点的右孩子
解决办法:令父节点为当前节点,对当前节点进行左旋,再对当前节点进行平衡操作(将情况4变为情况5)
令父节点10为当前节点,对当前节点进行左旋,就变成了情况5
情况5:
- 插入节点的父节点是红色,叔叔节点是黑色,且插入节点是其父节点的左孩子
解决办法:将父节点变为黑色,爷爷节点变为红色,对爷爷节点进行右旋。
当前节点为10,将父节点15变为黑色,将爷爷节点20变为红色,对爷爷节点进行右旋,即完成红黑树的平衡
删除节点
红黑树删除节点和搜索二叉树删除节点一样,只是删除后需要做平衡操作维持红黑树的性质。
节点删除分成四种情况:
- 删除节点无孩子节点,则直接删除
- 删除节点只有左孩子,删除后将左孩子顶替上来
- 删除节点只有右孩子,删除后将右孩子顶替上来
- 删除节点有左右两个孩子节点,用其左子树的最大节点(或者右子树的最小结点)代替待删除结点的数据,然后删除左子树的最大节点(或者右子树的最小结点)。即将第四种情况转化为第二种或第三种情况。
删除节点后的修正操作:
当删除节点为红色节点,不会破坏红黑树的性质。
当删除节点为黑色节点时,当前子树少了一个黑色节点,破坏了性质4,需要进行修正。
删除黑色节点后,当前子树少了一个黑色节点,有两种方法:(敲黑板:重点! 可能不好理解,把下面的几种情况看完就能理解了)
- 若有红色的侄子节点(兄弟节点的子节点),则向兄弟树借一个黑色节点
- 若没有红色的侄子节点,则将黑色的兄弟节点变为红色,让兄弟树也减少一个黑色节点,再以父节点为当前节点进行平衡操作,如果到了根节点,则整棵树的黑高度-1
情况1:
- 删除节点为红色节点,不会破坏红黑树的性质,直接删除。
情况2:
- 删除节点为黑色节点,顶替的孩子节点为红色
解决办法:直接将顶替上来的节点变成黑色
情况3:
- 删除节点为黑色节点,顶替的孩子节点为黑色,且兄弟节点为红色(则父节点和侄子节点都为黑色)
解决办法:将兄弟节点变为黑色,将父节点变为红色,对父节点进行旋转操作 ==> (当前节点的兄弟节点变为黑色,变为下面几种情况,再进行correctTree修正操作)
如下:删除17后,黑色18节点顶替上来,兄弟节点10为红色,将兄弟节点10变为黑色,将父节点15变为红色,当前节点18为右节点,则对父节点15进行右旋(若为左节点则对父节点进行左旋),右旋后,18的兄弟节点变为黑色,变为下面的几种情况。
情况4:
- 删除节点为黑色节点,顶替的孩子节点为黑色,且兄弟节点为黑色,侄子节点都为黑色(不能向兄弟树借黑色节点,这时是上面的第二种解决方法)
解决办法:将兄弟节点变为红色,对父节点进行correctTree修正操作)
如下:删除16后,18顶替上来,将兄弟节点13变为红色,对父节点15进行平衡操作(此时父节点为红,直接变成黑色就行,可能父节点为黑色,则需要继续判断)
情况5:
- 删除节点为黑色节点,顶替的孩子节点为黑色,且兄弟节点为黑色,内侧的侄子节点为红色,最外侧的侄子节点为黑色(需要先将最外侧的侄子节点变为红色,变为情况6,再进行修正操作)
解决办法:将兄弟节点变为红色,将内侧的侄子节点变为黑色,若当前节点为父节点的右节点则对兄弟节点左旋(若当前节点为父节点的左节点则对兄弟节点右旋),变为情况6,再进行correctTree修正操作
如下:删除22后,25顶替上来,将兄弟节点13变为红色,将内侧侄子节点16变为黑色,对兄弟节点13左旋,则最外侧的侄子节点变为红色,变为情况6
情况6:
- 删除节点为黑色节点,顶替的孩子节点为黑色,且兄弟节点为黑色,最外侧的侄子节点为红色,内侧的侄子节点的颜色红黑都可以
解决办法:将父节点和brother的颜色交换(brother为黑色,父节点颜色未知),将最外侧的侄子节点变为黑色,若当前节点为父节点的右孩子则对父节点进行右旋操作(若当前节点为父节点的左孩子则对父节点进行左旋操作)
如下:删除22后,25顶替上来,将兄弟节点16和父节点20的颜色交换,将最外侧的侄子节点13变为黑色,对父节点20进行右旋操作,这样树就平衡了
代码实现
- Colore用枚举定义:
package com.zlin.redBlackTree;
public enum Color {
RED, BLACK
}
- 节点Node:
Object表示存储的对象值,需要实现Comparable接口,重写compareTo方法,因为插入和删除时对象的比较就是通过compareTo方法。
package com.zlin.redBlackTree;
public class Node {
private Object object; // 存储的对象
private Node parent; // 父节点
private Node left; // 左孩子
private Node right; // 右孩子
private Color color; // 颜色
private boolean leafNode; // 是不是叶子节点
public Node() {
}
public Node(Object object, Node parent, Node left, Node right, Color color) {
this.object = object;
this.parent = parent;
this.left = left;
this.right = right;
this.color = color;
this.leafNode = false;
}
public Node(Object object, Node parent, Node left, Node right, Color color, boolean leafNode) {
this.object = object;
this.parent = parent;
this.left = left;
this.right = right;
this.color = color;
this.leafNode = leafNode;
}
public Object getObject() {
return object;
}
public void setObject(Object object) {
this.object = object;
}
public Node getParent() {
return parent;
}
public void setParent(Node parent) {
this.parent = parent;
}
public Node getLeft() {
return left;
}
public void setLeft(Node left) {
this.left = left;
}
public Node getRight() {
return right;
}
public void setRight(Node right) {
this.right = right;
}
public Color getColor() {
return color;
}
public void setColor(Color color) {
this.color = color;
}
public boolean isLeafNode() {
return leafNode;
}
public void setLeafNode(boolean leafNode) {
this.leafNode = leafNode;
}
}
- 添加节点
public void setRoot(Object object) {
Node root = new Node(object, null, null, null, Color.BLACK);
Node leftBlackLeafNode = new Node(null, null, null, null, Color.BLACK, true);
Node rightBlackLeafNode = new Node(null, null, null, null, Color.BLACK, true);
this.root = root;
this.size = 1;
this.root.setRight(leftBlackLeafNode);
this.root.setLeft(rightBlackLeafNode);
}
public boolean add (Object object) {
Node node = new Node(object, null, null, null, Color.RED);
boolean addSuccess = false;
Node parent = null;
if (this.root == null) {
// 根节点为空
setRoot(node.getObject());
addSuccess = true;
} else if ((parent = getParentNode(node, root)) != null) {
if (parent.getColor() == Color.BLACK) {
// 父节点是黑色 直接插入
if (((Comparable)node.getObject()).compareTo(parent.getObject()) < 0) {
Node leftBlackLeafNode = parent.getLeft();
Node rightBlackLeafNode = new Node(null, null, null, null, Color.BLACK, true);
parent.setLeft(node);
node.setParent(parent);
node.setLeft(leftBlackLeafNode);
node.setRight(rightBlackLeafNode);
} else {
Node leftBlackLeafNode = new Node(null, null, null, null, Color.BLACK, true);
Node rightBlackLeafNode = parent.getRight();
parent.setRight(node);
node.setParent(parent);
node.setLeft(leftBlackLeafNode);
node.setRight(rightBlackLeafNode);
}
} else {
if (((Comparable)node.getObject()).compareTo(parent.getObject()) < 0) {
Node leftBlackLeafNode = parent.getLeft();
Node rightBlackLeafNode = new Node(null, null, null, null, Color.BLACK, true);
parent.setLeft(node);
node.setParent(parent);
node.setLeft(leftBlackLeafNode);
node.setRight(rightBlackLeafNode);
} else {
Node leftBlackLeafNode = new Node(null, null, null, null, Color.BLACK, true);
Node rightBlackLeafNode = parent.getRight();
parent.setRight(node);
node.setParent(parent);
node.setLeft(leftBlackLeafNode);
node.setRight(rightBlackLeafNode);
}
// 父节点是红色,两个红色节点相连,需要修正颜色
adjustColor(node);
}
this.size++;
addSuccess = true;
}
return addSuccess;
}
/**
* 调整颜色
* param node : 当前节点
*/
private void adjustColor(Node node){
if (((Comparable)node.getObject()).compareTo(this.getRoot().getObject()) == 0) {
// 一直调整直到根节点
node.setColor(Color.BLACK);
} else if (node.getParent().getColor() == Color.RED) {
if ((((Comparable)node.getParent().getParent().getObject()).compareTo(node.getParent().getObject()) < 0 ?
node.getParent().getParent().getLeft() : node.getParent().getParent().getRight()).getColor() == Color.RED) {
// 父节点为红 (爷爷节点必定为黑) uncle叔叔节点为红
// 将父节点和叔叔节点变为黑色,爷爷节点变为红色, 再对爷爷节点进行adjust操作
node.getParent().setColor(Color.BLACK);
// uncle叔叔节点
(((Comparable)node.getParent().getParent().getObject()).compareTo(node.getParent().getObject()) < 0 ? node.getParent().getParent().getLeft() : node.getParent().getParent().getRight()).setColor(Color.BLACK);
node.getParent().getParent().setColor(Color.RED);
adjustColor(node.getParent().getParent());
} else if ((((Comparable)node.getParent().getParent().getObject()).compareTo(node.getParent().getObject()) < 0 ?
node.getParent().getParent().getLeft() : node.getParent().getParent().getRight()).getColor() == Color.BLACK) {
// 父节点为红 (爷爷节点必定为黑) uncle叔叔节点为黑
if (((Comparable)node.getObject()).compareTo(node.getParent().getObject()) < 0) {
// 父节点设置为黑,爷爷节点设置为红色, 再对爷爷节点进行右旋操作
node.getParent().setColor(Color.BLACK);
node.getParent().getParent().setColor(Color.RED);
if (((Comparable)node.getParent().getObject()).compareTo(node.getParent().getParent().getObject()) < 0) {
rightRotate(node.getParent().getParent());
} else {
leftRotate(node.getParent().getParent());
}
} else {
// 当前节点为父节点的右节点
// 对当前节点的父节点进行左旋操作,再对当前节点的父节点进行adjust操作
leftRotate(node.getParent());
adjustColor(node.getLeft());
}
}
}
}
- 删除节点
public boolean remove (Object object) {
boolean removeSucces = false;
Node node = searchNode(object, this.root);
if (node != null) {
doRemove(node);
this.size--;
removeSucces = true;
}
return removeSucces;
}
// 删除节点
private void doRemove (Node node) {
if (node.getLeft().isLeafNode() && node.getRight().isLeafNode()) {
// 删除节点没有叶子节点,直接删除
Node parentNode = node.getParent();
if (((Comparable)node.getObject()).compareTo(node.getParent().getObject()) < 0) {
node.getParent().setLeft(node.getLeft());
node.setParent(null);
} else {
node.getParent().setRight(node.getRight());
node.setParent(null);
}
// 删除节点为红色则直接删除,为黑色则需要修正红黑树
if (node.getColor() == Color.BLACK) {
correctTree(parentNode);
}
} else if (node.getLeft().isLeafNode()) {
// 右节点存在,删除node,右节点顶替上来
if (((Comparable)node.getObject()).compareTo(node.getParent().getObject()) < 0) {
node.getParent().setLeft(node.getRight());
node.getRight().setParent(node.getParent());
} else {
node.getParent().setRight(node.getRight());
node.getRight().setParent(node.getParent());
}
// 删除节点为红色则直接删除,为黑色则需要修正红黑树
if (node.getColor() == Color.BLACK) {
correctTree(node.getRight());
}
node.setParent(null);
node.setRight(null);
} else if (node.getRight().isLeafNode()) {
// 左节点存在,删除node,左节点顶替上来
if (((Comparable)node.getObject()).compareTo(node.getParent().getObject()) < 0) {
node.getParent().setLeft(node.getLeft());
node.getLeft().setParent(node.getParent());
} else {
node.getParent().setRight(node.getLeft());
node.getLeft().setParent(node.getParent());
}
// 删除节点为红色则直接删除,为黑色则需要修正红黑树
if (node.getColor() == Color.BLACK) {
correctTree(node.getLeft());
}
node.setParent(null);
node.setLeft(null);
} else {
// 左节点和右节点都存在,找到左子树的最大节点或者右子树的最小节点node1(temp要么没有叶子节点,要么只有一个叶子节点,符合上面的情况)
// 替换要删除节点和node1的值,再删除node1
// 把删除节点和node1交换后,比较的结果会反过来
// node.getValue() < node.getParent().getValue() 所以用isReplaceNode记录是否为交换的节点,如果是则左右交换
// 找到左子树的最大值
Node maxNodeInLeftTree = searchMaxNodeInLeftTree(node);
Object object = node.getObject();
node.setObject(maxNodeInLeftTree.getObject());
maxNodeInLeftTree.setObject(object);
doRemove(maxNodeInLeftTree);
}
}
// 删除黑色节点后,对红黑树进行调整
// 删除黑色节点后 当前子树少了一个黑节点
// 两种方案: 向兄弟子树借一个黑色节点 交给父节点来处理(直到根节点,则整个树的根节点到所有叶子节点的黑节点的数量-1)
private void correctTree (Node node) {
if (((Comparable)node.getObject()).compareTo(this.root.getObject()) == 0) {
// 是根节点 整个树的根节点到所有叶子节点的黑节点的数量-1
this.root.setColor(Color.BLACK);
} else if (node.getColor() == Color.RED) {
// 顶替的节点为红色 直接变为黑色
node.setColor(Color.BLACK);
} else {
// 顶替的节点为黑色
if ((((Comparable)node.getParent().getObject()).compareTo(node.getObject()) < 0 ?
node.getParent().getLeft() : node.getParent().getRight()).getColor() == Color.RED) {
// 兄弟节点为红色 则父节点和兄弟节点的子节点都为黑色
// 操作 : 将兄弟节点变为黑色 将父节点变为红色 对父节点进行旋转操作 ==> 当前节点的兄弟节点变为黑色(变为下面几种情况,再进行correctTree修正操作)
node.getParent().setColor(Color.RED);
if (((Comparable)node.getParent().getObject()).compareTo(node.getObject()) < 0) {
// 当前节点node为父节点的右节点
node.getLeft().setColor(Color.BLACK);
// 对父节点进行右旋
rightRotate(node.getParent());
} else {
node.getRight().setColor(Color.BLACK);
// 对父节点进行左旋
leftRotate(node.getParent());
}
// 现在node的兄弟节点变为黑色 (变为下面的情况) 再对node进行correctTree修正操作
correctTree(node);
} else {
// 兄弟节点为黑色
Node brother = (((Comparable)node.getParent().getObject()).compareTo(node.getObject()) < 0 ?
node.getParent().getLeft() : node.getParent().getRight());
if (brother.getLeft().getColor() == Color.BLACK
&& brother.getRight().getColor() == Color.BLACK) {
// 兄弟节点的子节点都为黑色
// 兄弟节点的子节点都为黑色,不能向兄弟子树借一个黑色节点(下面三种情况则是向兄弟节点借一个黑色节点)
// 需要交给父节点来处理(即父节点所在的子树都少一个黑色节点,对父节点进行correctTree修正操作)
// 操作:将兄弟节点变为红色 对父节点进行correctTree修正
brother.setColor(Color.RED);
correctTree(node.getParent());
} else if (brother.getLeft().getColor() == Color.RED
&& brother.getRight().getColor() == Color.BLACK) {
// 左节点为红 右节点为黑
if (((Comparable)node.getParent().getObject()).compareTo(node.getObject()) < 0) {
// 当前节点是父节点的右节点
// 最外侧的侄子节点为红色 , 需要将最外侧的侄子节点变为黑色 然后从兄弟子树拿一个黑色节点
// 操作:1.将父节点和brother的颜色交换(brother为黑色,父节点颜色未知)
// 2.将brother的左节点变为黑色
// 3.对父节点进行右旋操作
brother.setColor(node.getParent().getColor());
node.getParent().setColor(Color.BLACK);
brother.getLeft().setColor(Color.BLACK);
rightRotate(node.getParent());
} else {
// 当前节点是父节点的左节点
// 最外侧的侄子节点为黑色 需要先将最外侧的侄子节点变为红色 再correctTree修正
// 操作:将brother的左节点设置为黑色 将brother设置为红色 对brother进行右旋操作 右旋后再对node进行correctTree修正
brother.getLeft().setColor(Color.BLACK);
brother.setColor(Color.RED);
rightRotate(brother);
correctTree(node);
}
} else if (brother.getLeft().getColor() == Color.BLACK
&& brother.getRight().getColor() == Color.RED) {
// 左节点为黑 右节点为红
if (((Comparable)node.getParent().getObject()).compareTo(node.getObject()) < 0) {
// 当前节点是父节点的右节点
// 最外侧的侄子节点为黑色 需要先将最外侧的侄子节点变为红色 再correctTree修正
// 操作:将brother的右节点设置为黑色 将brother设置为红色 对brother进行左旋操作 右旋后再对node进行correctTree修正
brother.getRight().setColor(Color.BLACK);
brother.setColor(Color.RED);
leftRotate(brother);
correctTree(node);
} else {
// 当前节点是父节点的左节点
// 最外侧的侄子节点为红色, 需要将最外侧的侄子节点变为黑色 然后从兄弟子树拿一个黑色节点
// 操作:1.将父节点和brother的颜色交换(brother为黑色,父节点颜色未知)
// 2.将brother的右节点变为黑色
// 3.对父节点进行左旋操作
brother.setColor(node.getParent().getColor());
node.getParent().setColor(Color.BLACK);
brother.getRight().setColor(Color.BLACK);
leftRotate(node.getParent());
}
} else {
// 兄弟节点的左右子节点都为红
if (((Comparable)node.getParent().getObject()).compareTo(node.getObject()) < 0) {
// 当前节点是父节点的右节点
brother.setColor(node.getParent().getColor());
node.getParent().setColor(Color.BLACK);
brother.getLeft().setColor(Color.BLACK);
rightRotate(node.getParent());
} else {
// 当前节点是父节点的左节点
// 最外侧的侄子节点为红色, 需要将最外侧的侄子节点变为黑色 然后从兄弟子树拿一个黑色节点
// 操作:1.将父节点和brother的颜色交换(brother为黑色,父节点颜色未知)
// 2.将brother的右节点变为黑色
// 3.对父节点进行左旋操作
brother.setColor(node.getParent().getColor());
node.getParent().setColor(Color.BLACK);
brother.getRight().setColor(Color.BLACK);
leftRotate(node.getParent());
}
}
}
}
}
代码的实现只是按照上面的各种情况进行讨论,把各种情况计算进去,当前节点为父节点的左孩子和右孩子进行的旋转操作可能不一样(左旋/右旋),上面的各种情况分析才是重点。