-
平衡二叉树之红黑树
红黑树是一种自平衡的二叉查找树,其具有如下性质:
1.所有节点只有红色和黑色; 2.根节点必须为黑色; 3.叶子节点都是黑色(叶子节点指的NIL是黑色); 4.每个红色节点都必须有两个黑色节点; 5.树中任意节点,其到树末端的NIL指针的任何一条路径上都有相同数量的黑色节点(保证平衡)。 *红黑树的高度被维持在logn,红黑树的查找、插入、删除最坏时间复杂度为O(logn)。*
红黑树的核心就是左旋右旋和变色,无论是插入和删除都是将相应节点先删除或插入在左旋右旋和变色的调整,下面分别介绍左旋右旋以及变色的原理及代码:
右旋:如下图可以观察到当做右旋变换时,节点2和节点4、节点1和节点3之间的关系以及连线没有变化;而节点1由节点2的父节点变为节点2的右子节点,节点5由节点2的右子节点变化为节点1的左子节点。
旋转过程:如图以节点1为旋转节点,首先要获取节点1的左子节点2,然后将左子节点2的右子节点5变为节点1的左子节点与节点1相连;然后判断旋转节点1是否有父节点,若有,则将节点1的父节点变为节点2的父节点(查看节点1是父节点的左子节点还是右子节点,相应的将节点2也设置为对应的左或右子节点);最后将旋转节点1设置为节点2的右子节点,节点2设置为节点1的父节点(父子关系和方向调转)。
代码:
private void rightChange(RBNode p){
RBNode l = p.left;
p.left = l.right;
if (l.right != null){
l.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;
}
左旋:如下图可以观察到当做左旋变换时,节点1和节点2、节点3和节点5之间的关系以及连线没有变化;而节点1由节点3的父节点变为节点3的左子节点,节点4由节点3的左子节点变为节点1的右子节点。
旋转过程:如图以节点1为旋转节点,首先要获取节点1的右子节点3,然后将节点3的左子节点4作为节点1的右子节点与节点1相连;然后判断旋转节点1是否有父节点,若有,将节点1的父节点作为节点3的父节点(查看节点1是父节点的左子节点或右子节点,相应的将节点3也设置为对应的左或右子节点);最后将旋转节点1设置为节点3的左子节点,节点3设置为节点1的父节点(父子关系和方向调转)。
代码:
private void LeftChange(RBNode p){
RBNode r = p.right;
p.right = r.left;
//节点连线是双向的,若r.left不是null,还需要让p.right指向r.left
if (r.left != null){
r.left.parent = p;
}
r.parent = p.parent;
if (p.parent !=null){
if(p.parent.left == p){
p.parent.left = r;
}else{
p.parent.right = r;
}
}else {
root = r;
}
r.left = p;
p.parent = r;
}
插入节点:
红黑树插入 :红黑树插入可以先向二叉树一样先插入,然后在进行调整调整。
插入节点默认都是红色节点,如下图,当树为空,此时插入节点就是根节点,直接变为黑色即可,当插入节点的父节点是黑色时,不需要调整直接插入(与二叉树插入一致)。
当插入节点的的父节点是红色节点我们可以得到如下八种情况,分别没有叔叔节点(上面四种)和有叔叔节点(下面四种);对于上面的四种情况(和AVL树的四种旋转情况一样 LL LR RL RR)我们需要旋转+变色,下面的四种情况只需要进行变色即可。
LL型:以插入节点3的爷爷节点节点1右旋,然后将节点2变为黑色(RR型原理与LL型一致,进行左旋)
LR型:先以插入节点3的父节点2为旋转节点进行左旋,然后在以爷爷节点1进行右旋(RL型原理一致,操作相反)。
有叔叔节点型:将父亲和叔叔节点变为黑色,爷爷节点变为红色,此时爷爷节点变为红色后会产生以下情况:
1.爷爷节点就是根节点,那么就要将爷爷节点在设置为黑色,
2.爷爷节点不是根节点(爷爷节点上面还有其他节点),此时就要将爷爷节点看作插入节点,插入到上面的节点进行调整变色(递归处理即可)。
其他三种类型原理与其一致,就不一一列举。
插入节点部分代码:
private void InsertNode(K key,V value){
int cmp;
RBNode p;
RBNode t = this.root;
if (key == null){
throw new NullPointerException();
}
if(t == null){
root = new RBNode(key,value==null ? key:value,null);
root.setColor(BLACK);
return;
}
do {
p = t;
cmp = key.compareTo((K) t.key);
if (cmp < 0){
t = t.left;
}else if (cmp > 0){
t = t.right;
}else{
t.setValue(value==null ? key:value);
return;
}
}while (t !=null);
RBNode e = new RBNode(key,value==null ? key:value,p);
if (cmp < 0){
p.left = e;
}else {
p.right = e;
}
fixRBTreeByNode(e);
}
调整部分代码:
private void fixRBTreeByNode(RBNode e) {
e.setColor(RED);
while (e!=null && e!=root && e.parent.color==RED){
//分为两种情况
//1.插入节点的父节点是爷爷节点的左子节点
if(e.parent==e.parent.parent.left){
RBNode uncle = e.parent.parent.right;
//有叔叔节点
if (uncle.color == RED){
e.parent.setColor(BLACK);
uncle.parent.setColor(RED);
uncle.setColor(BLACK);
//此时爷爷节点不是根节点,那么应该将爷爷节点看作插入节点,递归处理
e = uncle.parent;
}else {
if (e == e.parent.right){
e= e.parent;
LeftChange(e);
}
e.parent.setColor(BLACK);
e.parent.parent.setColor(RED);
RightChange(e.parent.parent);
}
}else {
RBNode uncle = e.parent.parent.left;
//有叔叔节点
if (uncle.color == RED){
e.parent.setColor(BLACK);
uncle.parent.setColor(RED);
uncle.setColor(BLACK);
//此时爷爷节点不是根节点,那么应该将爷爷节点看作插入节点,递归处理
e = uncle.parent;
}else {
if (e == e.parent.left){
e= e.parent;
RightChange(e);
}
e.parent.setColor(BLACK);
e.parent.parent.setColor(RED);
LeftChange(e.parent.parent);
}
}
}
root.setColor(BLACK);
}
二叉树的删除:
1.被删除节点是叶子节点直接删除;
2.被删除节点是具有一个子节点的节点用子节点替换被删除节点;
3.被删除节点是具有两个子节点的节点可以将后继节点的值覆盖被删除节点,然后对该节点的后继进行操作(后继节点肯定不会有两个子节点,所以可以转换为1、2继续处理)。
红黑树的删除:可以将红黑树的删除分为删除和调整。删除可以类比二叉树的删除。
1.对于具有左右子节点的节点可以将其转化为对其前驱或者后继节点的删除(转化为2、3);
2.对于只有一个子节点的节点用其子节点替代被删除节点,然后调整。
3.当被删除节点是叶子节点,对于红黑树来说不能直接删除,要分为这个叶子节点是黑色还是红色,红色直接删除,黑色要先进行调整在删除(直接删除影响平衡)。
前驱:如下图节点4的前驱节点是节点2。
查找某个节点前驱
1.首先判断查找节点的左子节点是否存在:
- 若存在,则在查看左子节点的右子节点是否存在,若存在,则一直向后继续查找右子节点,直到最后一个右子节点就是前驱节点。
- 若不存在,则要判断该节点是否根节点,不是说明他还有长辈,那么就要看是否存在一个长辈使得该节点在这个长辈节点的右侧,若存在这样的长辈节点,那么这个长辈节点就是前驱。
- 举例:如节点4没有左子节点,但是他的前驱节点是3;再如节点1也没有左子节点,同时它也没有前驱节点 。对比节点1和节点4可知当节点没有左子节点时还要长辈节点比较才能判断是否有前驱,这就是上面不存在情况时的例子。
代码:
private RBNode Predecessor(RBNode node){
RBNode r = node.left;
if (node == null){
return null;
}
else if (r !=null){
while (r.right != null){
r = r.right;
}
return r;
}else {
RBNode p = node.parent;
RBNode q = node;
while (p!=null && q == p.left){
q = p;
p = p.parent;
}
return p;
}
}
后继:与前驱同理,方向相反。
private RBNode Successor(RBNode node){
RBNode r = node.left;
if (node == null){
return null;
}
else if (r!=null){
while (r.left != null){
r = r.left;
}
return r;
}else {
RBNode p = node.parent;
RBNode q = node;
while (p!=null && q == p.right){
q = p;
p = p.parent;
}
return p;
}
}
删除代码流程:
1.判断删除节点是否有两个子节点,若有则查找后继节点,对后继节点执行2或者3。
2.判断当前删除节点是否有子节点(要有也只能有一个,因为步骤一已经处理过,将这个子节点称为替代节点),若存在:
(1)当删除节点是根节点,那么将子节点直接替代删除节点成为根节点即可。
(2)删除节点不是根节点,则判断删除节点是其父亲节点的左子节点还是右子节点,然后把删除节点的子节点(替代节点)设置为相应的左或右子节点。
(3)判断删除节点是否为黑色,若是黑色则进行调整(因为删除了黑色节点失去平衡)。
3.若不存在,说明是叶子节点:
(1)先判断这个叶子节点是不是黑色,若是,先调整然后在删除。
(2)是红色直接删除。
private void Remove(RBNode node){
if (node.left!=null && node.right!=null){
RBNode succ = Successor(node);
node.key = succ.key;
node.value = succ.value;
node = succ;
}
RBNode repacement = node.left ==null ?node.right :node.left;
if (repacement != null){
repacement.parent = node.parent;
if (node.parent == null){
root = repacement;
}else if (node == node.parent.left){
node.parent.left= repacement;
}else {
node.parent.right = repacement;
}
node.left= node.right = node.parent=null;
if (node.color == BLACK){
fixRemoveAfter(repacement);
}
}
else {
if (node.color == BLACK){
fixRemoveAfter(node);
}else if (node == node.parent.left){
node.parent.left = null;
}else {
node.parent.right = null;
}
}
}
调整代码流程:
注:删除节点是叶子节点时,调整节点就是删除节点;当删除节点不是叶子节点,调整节点就是替代节点。
1.当被调整节点是红色直接变为黑色即可。
2.当调整节点不是root节点且调整节点是黑色:
当调整节点是父节点的左子节点时:如下图删除节点1在节点2的左侧。
(1)第一步先找正真的兄弟节点,当兄弟节点不是黑色那么此时兄弟就不是真正的兄弟节点。如下图节点1的真正兄弟节点是3,应该转换一下后在找兄弟节点就是真正的。(转换过程就是将删除节点的父节点2变红色,然后将此时的假兄弟节点变黑色,然后把节点2作为旋转节点进行左旋,效果如下图)
(2)找到兄弟节点如果兄弟节两个点子节点都是黑色(空节点也是黑色)且此时若父亲是红色兄弟节点是黑色,那么直接将父节点变为黑色兄弟节点变为红色;若父亲和兄弟节点都是黑色那么将兄弟节点变为红色,若父节点不是根节点还要进行递归处理,因为此时相当于删除节点这一侧(左侧)减少了一个黑色节点而根节点的另一侧就多一个黑色节点不平衡。所以要将删除节点(调整节点)的父节点作为调整节点继续进行调整,若不平衡继续将当前调整节点的父节点作为调整节点,就这样一直递归进行调整。
(3)判断兄弟节点的右子节点是否为空,若为空则说明兄弟节点只有一个左子节点(经过第二步判断说明兄弟一定有一个子节点),那么要先右旋+变色,然后在变色+左旋;若不为空则直接进行变色+左旋。
private void fixRemoveAfter(RBNode node) {
while (node!=root && node.color == BLACK){
if (node == node.parent.left){
RBNode rbNode = node.parent.right;
if (rbNode.color == RED){
node.parent.setColor(RED);
rbNode.setColor(BLACK);
LeftChange(node.parent);
rbNode = node.parent.right;
}
if (rbNode.left.color == BLACK && rbNode.right.color == BLACK){
rbNode.setColor(RED);
node=node.parent;
}
else {
if (rbNode.right == null){
rbNode.setColor(RED);
rbNode.left.setColor(BLACK);
RightChange(rbNode);
rbNode = rbNode.parent;
}
rbNode.setColor(node.parent.color);
node.parent.setColor(BLACK);
rbNode.right.setColor(BLACK);
LeftChange(node.parent);
node =root;
}
}else {
RBNode lbNode = node.parent.left;
if (lbNode.color == RED){
node.parent.setColor(RED);
lbNode.setColor(BLACK);
LeftChange(node.parent);
lbNode = node.parent.left;
}
if (lbNode.right.color == BLACK && lbNode.left.color == BLACK){
lbNode.setColor(RED);
node=node.parent;
}
else {
if (lbNode.left == null){
lbNode.setColor(RED);
lbNode.right.setColor(BLACK);
LeftChange(lbNode);
lbNode = lbNode.parent;
}
lbNode.setColor(node.parent.color);
node.parent.setColor(BLACK);
lbNode.left.setColor(BLACK);
LeftChange(node.parent);
node =root;
}
}
}
node.setColor(BLACK);
}
红黑树的难点是删除,可以通过类比二叉排序树的删除进行思考,红黑树的删除同样可以分为三种节点情况,最后都转换为删除节点只有一个或者没有子节点进行处理,主要难点还是是增加了调整的步骤,那么调整又可以分为调整节点是红色和黑色两类,是红色直接变黑色即可,当是黑色那么就要看真正兄弟节点能不能帮忙(真兄弟还有没有红色子节点),能帮忙就让兄弟帮,不能帮(真兄弟没有红子节点),那么就和兄弟一起变红色,把父亲变为黑色(若父亲本是黑色还需要进行递归调整,原因上面解释过了,原本是红色那么变黑色后就结束了)。
完整代码
public class RBTree<K extends Comparable<K>,V> {
//设置红黑节点标志
private boolean BLACK = true;
private boolean RED = false;
//设置根节点
public RBNode root;
/**
* 左旋
* p pr
* /\ /\
* pl pr ===> p rr
* /\ /\
* rl rr pl rl
* */
private void LeftChange(RBNode p){
RBNode r = p.right;
p.right = r.left;
//节点连线是双向的,若r.left不是null,还需要让p.right指向r.left
if (r.left != null){
r.left.parent = p;
}
r.parent = p.parent;
if (p.parent !=null){
if(p.parent.left == p){
p.parent.left = r;
}else{
p.parent.right = r;
}
}else {
root = r;
}
r.left = p;
p.parent = r;
}
/**
* 右旋*/
private void RightChange(RBNode p){
RBNode 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;
}
/**
*红黑树插入:可分解为两步先插入在调整变色
* 第一步插入与二叉搜索树的插入相同
* @param
* */
void InsertNode(K key){
int cmp;
RBNode p;
RBNode t = this.root;
if (key == null){
throw new NullPointerException();
}
if(t == null){
root = new RBNode(key,null);
root.setColor(BLACK);
return;
}
do {
p = t;
cmp = key.compareTo((K) t.key);
if (cmp < 0){
t = t.left;
}else if (cmp > 0){
t = t.right;
}else{
t.setValue(key);
//t.setValue(value==null ? key:value);
return;
}
}while (t !=null);
RBNode e = new RBNode(key,p);
if (cmp < 0){
p.left = e;
}else {
p.right = e;
}
fixRBTreeByNode(e);
}
private void fixRBTreeByNode(RBNode e) {
e.setColor(RED);
while (e!=null && e!=root && e.parent.color==RED){
//分为两种情况
//1.插入节点的父节点是爷爷节点的左子节点
if(e.parent==e.parent.parent.left){
RBNode uncle = e.parent.parent.right;
//有叔叔节点
if (uncle.color == RED){
e.parent.setColor(BLACK);
uncle.parent.setColor(RED);
uncle.setColor(BLACK);
//此时爷爷节点不是根节点,那么应该将爷爷节点看作插入节点,递归处理
e = uncle.parent;
}else {
if (e == e.parent.right){
e= e.parent;
LeftChange(e);
}
e.parent.setColor(BLACK);
e.parent.parent.setColor(RED);
RightChange(e.parent.parent);
}
}else {
RBNode uncle = e.parent.parent.left;
//有叔叔节点
if (uncle != null){
e.parent.setColor(BLACK);
uncle.parent.setColor(RED);
uncle.setColor(BLACK);
//此时爷爷节点不是根节点,那么应该将爷爷节点看作插入节点,递归处理
e = uncle.parent;
}else {
if (e == e.parent.left){
e= e.parent;
RightChange(e);
}
e.parent.setColor(BLACK);
e.parent.parent.setColor(RED);
LeftChange(e.parent.parent);
}
}
}
root.setColor(BLACK);
}
/*前驱
* */
private RBNode Predecessor(RBNode node){
RBNode r = node.left;
if (node == null){
return null;
}
else if (r !=null){
while (r.right != null){
r = r.right;
}
return r;
}else {
RBNode p = node.parent;
RBNode q = node;
while (p!=null && q == p.left){
q = p;
p = p.parent;
}
return p;
}
}
/*
* 后继*/
private RBNode Successor(RBNode node){
RBNode r = node.right;
if (node == null){
return null;
}
else if (r!=null){
while (r.left != null){
r = r.left;
}
return r;
}else {
RBNode p = node.parent;
RBNode q = node;
while (p!=null && q == p.right){
q = p;
p = p.parent;
}
return p;
}
}
void Remove(RBNode node){
if (node.left!=null && node.right!=null){
RBNode succ = Successor(node);
node.key = succ.key;
node.value = succ.value;
node = succ;
}
RBNode repacement = node.left ==null ?node.right :node.left;
if (repacement != null){
repacement.parent = node.parent;
if (node.parent == null){
root = repacement;
}else if (node == node.parent.left){
node.parent.left= repacement;
}else {
node.parent.right = repacement;
}
node.left= node.right = node.parent=null;
if (node.color == BLACK){
fixRemoveAfter(repacement);
}
}
else {
if (node.color == BLACK){
fixRemoveAfter(node);
}else if (node == node.parent.left){
node.parent.left = null;
}else {
node.parent.right = null;
}
}
}
private void fixRemoveAfter(RBNode node) {
while (node!=root && node.color == BLACK){
if (node == node.parent.left){
RBNode rbNode = node.parent.right;
if (rbNode.color == RED){
node.parent.setColor(RED);
rbNode.setColor(BLACK);
LeftChange(node.parent);
rbNode = node.parent.right;
}
if (rbNode.left.color == BLACK && rbNode.right.color == BLACK){
rbNode.setColor(RED);
node=node.parent;
}
else {
if (rbNode.right == null){
rbNode.setColor(RED);
rbNode.left.setColor(BLACK);
RightChange(rbNode);
rbNode = rbNode.parent;
}
rbNode.setColor(node.parent.color);
node.parent.setColor(BLACK);
rbNode.right.setColor(BLACK);
LeftChange(node.parent);
node =root;
}
}else {
RBNode lbNode = node.parent.left;
if (lbNode.color == RED){
node.parent.setColor(RED);
lbNode.setColor(BLACK);
LeftChange(node.parent);
lbNode = node.parent.left;
}
if (lbNode.right.color == BLACK && lbNode.left.color == BLACK){
lbNode.setColor(RED);
node=node.parent;
}
else {
if (lbNode.left == null){
lbNode.setColor(RED);
lbNode.right.setColor(BLACK);
LeftChange(lbNode);
lbNode = lbNode.parent;
}
lbNode.setColor(node.parent.color);
node.parent.setColor(BLACK);
lbNode.left.setColor(BLACK);
LeftChange(node.parent);
node =root;
}
}
}
node.setColor(BLACK);
}
//前序遍历
public void preOrder(RBNode root)
{
if(root ==null) return;
System.out.println(root.key+" color "+root.color);
preOrder(root.left);
preOrder(root.right);
}
//创建节点类
static class RBNode<K extends Comparable<K>,V>{
private RBNode left;
private RBNode right;
private RBNode parent;
private boolean color;
private K key;
private V value;
public RBNode(RBNode left, RBNode right, RBNode parent, boolean color, K key, V value) {
this.left = left;
this.right = right;
this.parent = parent;
this.color = color;
this.key = key;
this.value = value;
}
public RBNode( K key, RBNode parent) {
this.parent = parent;
this.key = key;
}
public RBNode() {
}
public RBNode getLeft() {
return left;
}
public RBNode getRight() {
return right;
}
public RBNode getParent() {
return parent;
}
public boolean getColor() {
return color;
}
public K getKey() {
return key;
}
public V getValue() {
return value;
}
public void setLeft(RBNode left) {
this.left = left;
}
public void setRight(RBNode right) {
this.right = right;
}
public void setParent(RBNode parent) {
this.parent = parent;
}
public void setColor(boolean color) {
this.color = color;
}
public void setKey(K key) {
this.key = key;
}
public void setValue(V value) {
this.value = value;
}
}
}