TreeMap
首先给个网站,自己手动增加和删除红黑树节点网站
BST
说红黑树先从BST二叉查找树进行说起
二叉树:每个子节点只有两个节点的树
二叉查找树(二叉搜索树):
就是一颗二叉树,他的左节点比父节点要小,
右节点比父节点要大。他的高度决定的查找效率。
BST操作
查找(红黑树通用):查找每个节点我们从根节点开始查找
查找值比当前值大,则搜索右子树
查找值等于当前值,停止查找,返回当前节点
查找值比当前值小,则搜索左子树
插入:
要插入节点,必须先找到插入节点位置。
依然是从根节点开始比较,小于根节点的话就和左子树
比较,反之与右子树比较,直到左子树为空或者右子树为空,
则插入到相应为空的位置。
遍历(红黑树通用):
根据一定顺序来访问整个树,
常见的有前序遍历,中序遍历(用的较多),后序遍历
前序遍历:根节点-》左子树-》右子树
中序遍历:左子树-》根节点-》右子树
后续遍历:左子树-》右子树-》根节点
查找最小值(红黑树通用):
沿着根节点的左子树一路查找,直到最后一个不为空的节点,
该节点就是当前这个树的最小节点
查找最大值(红黑树通用):
沿着根节点的右子树一路查找,直到最后一个不为空的节点,
该节点就是当前这个树的最大节点
查找前驱节点(红黑树通用):小于当前节点的最大值
查找后继节点(红黑树通用):大于当前节点的最小值
删除:
本质上是找前驱或者后继节点来替代
叶子节点直接删除(没有前驱或后继节点)
只有一个子节点的用子节点替代
(本质上就是找的前驱节点或者是后继节点,
左节点就是前驱节点,右节点就是后继节点)
有两个子节点的,需要找到替代节点(替代节点就是前驱节点或者后继节点)
删除操作和红黑树一样,只不过红黑树多了着色和旋转过程
BST的问题
BST存在的问题是,树在插入的时候会导致倾斜,
不同的插入顺序会导致数的高度不一样,而树的高度
直接影响了树的查找效率。
最坏的情况所有的节点都在一条斜线上,这样树的高度为N。
基于BST存在的问题,平衡查找二叉树(Balanced BST)产生了。
平衡树的插入和删除的时候,会通过旋转操作将高度保持在LogN。
其中两款具有代表性的平衡术分别为AVL树(高度平衡树,具备二叉搜索
树的全部特性,而且左右子树高度差不超过1)和红黑树。
面试题:有了AVL树为什么还要红黑树呢??
AVL树由于实现比较复杂,而且插入和删除性能差,在实际环境下的应用不如红黑树。
红黑树的实际应用非常广泛,
如Java中的HashMap和TreeSet,Java 8中HashMap的实现因为用
RBTree代替链表(链表长度>8时),性能有所提升。
2-3-4树
说完BST,还要说一下2-3-4树,因为红黑树和它两个关系渊源
2-3-4树是四阶的 B树(Balance Tree),
他属于一种多路查找树,它的结构有以下限制:
所有叶子节点都拥有相同的深度。(因为所有节点都是从叶节点进行插入的)
节点只能是 2-节点、3-节点、4-节点之一。
2-节点:包含 1 个元素的节点,有 2 个子节点;
3-节点:包含 2 个元素的节点,有 3 个子节点;
4-节点:包含 3 个元素的节点,有 4 个子节点;
所有节点必须至少包含1个元素
元素始终保持排序顺序,整体上保持二叉查找树的性质,
即父结点大于左子结点,小于右子结点;
而且结点有多个元素时,每个元素必须大于它左边的和它的左子树中元素。
2-3-4树的查询操作像普通的二叉搜索树一样,非常简单,
但由于其结点元素数不确定,在一些编程语言中实现起来并不方便,
实现一般使用它的等同——红黑树。
2-3-4树和红黑树对应起来
2-3-4树的每一个结点都对应红黑树的一种结构,
所以每一棵 2-3-4树也都对应一棵红黑树,
下图是 2-3-4树不同结点与红黑树子树的对应。
插入操作
2-3-4树中结点添加需要遵守以下规则:
插入都是向最下面一层插入;
升元:将插入结点由 2-结点升级成 3-结点,或由 3-结点升级成 4-结点;
向 4-结点插入元素后,需要将中间元素提到父结点升元,原结点变成两个 2-结点,再把元素插入
2-结点中,如果父结点也是 4-结点,则递归向上层升元,至到根结点后将树高加1;
而将这些规则对应到红黑树里,就是:
新插入的结点颜色为 红色 ,这样才可能不会对红黑树的高度产生影响。
2-结点对应红黑树中的单个黑色结点,插入时直接成功(对应 2-结点升元)。
3-结点对应红黑树中的 黑+红 子树,插入后将其修复成 红+黑+红 子树(对应 3-结点升元);
4-结点对应红黑树中的 红+黑+红 子树,插入后将其修复成 红色祖父+黑色父叔+红色孩子 子树,然后
再把祖父结点当成新插入的红色结点递归向上层修复,直至修复成功或遇到 root 结点;
红黑树
红黑树是一种结点带有颜色属性的二叉查找树,
但它在二叉查找树之外,还有以下5大性质:
1. 节点是红色或黑色。
2. 根是黑色。
3. 所有叶子都是黑色(叶子是NIL节点)。
4. 每个红色节点必须有两个黑色的子节点。
(从每个叶子到根的所有路径上不能有两个连续的红色节点。)
5. 从任一节点到其每个叶子的所有简单路径
都包含相同数目的黑色节点(黑色平衡)。
这些性质都可以从2-3-4树和红黑树的等价关系里面递推过来
比如性质一就是2-3-4树只有三种节点的树,
对应的等价关系的红黑树的节点要么红要么黑
性质二也是2-3-4树的三种节点对应的红黑树的根节点都是黑色的
性质三,没什么说的,源代码如此
性质四,从对应关系来说,因为对应的都是上黑下红,所以红节点下面
对应的肯定是黑色,因为无论怎么等价,黑色都是在上面,
还有因为性质三,叶节点也是黑色的,所以就算最后一个是红色节点
他的两个孩子是无的,但是默认两个没有的孩子是黑色的
操作
变色:节点的颜色由黑变红或者由红变黑
左旋:以某个节点作为旋转点,其右子节点变为旋转节点的父节点,
右子节点的左子节点变为旋转节点的右子节点,左子节点保持不变。
右旋:以某个节点作为旋转点,其左子节点变为旋转节点的父节点,
左子节点的右子节点变为旋转节点的左子节点,右子节点保持不变。
新增
分情况讨论,主要是要找到插入位置,
然后自平衡(左旋或者右旋)且插入节点是红色
(如果是黑色的话,那么当前分支上就会多出一个黑色节点出来,
从而破坏了黑色平衡),以下分析全部以左子树为例子,右子树的情况则相反。
如果插入的是第一个节点(根节点),红色变黑色
如果父节点为黑色,则直接插入,不需要变色
如果父节点为红色,叔叔节点也是红色(此种情况爷爷节点一定是黑色),
则父节点和叔叔节点变黑色,爷爷节点变红色(如果爷爷节点是根节点,
则再变成黑色),爷爷节点此时需要递归
(把爷爷节点当做新插入的节点再次进行比较)
如果父节点是红色,没有叔叔节点或者叔叔节点是黑色
(此时只能是NIL节点),则以爷爷节点为支点右旋,
旋转之后原来的爷爷节点变红色,原来的父节点变黑色。
删除
总结来说:
自己能搞定的自己搞定;搞不定的找兄弟和父亲帮忙;
父亲和兄弟都帮不了那有福同享,有难同当(父亲和兄弟自损)
自己能搞定的自己搞定
如果删除的节点对应于2-3-4树的3节点或者4节点,则直接删除,
不用跟兄弟和父亲借. 如果删除的是红色节点,则直接删;
如果删除的是黑色节点,则红色节点上来替代,变黑即可
搞不定的找兄弟和父亲帮忙
前提是找到“真正“的兄弟节点
兄弟节点有的借(此时兄弟节点一定是黑色,
如果是红色那说明这个节点不是真正的兄弟节点,需要回到上一步找真正的兄弟节点)
兄弟节点有两个子节点的情况(2个子节点肯定是红色,
如果是黑色的话相当于此时兄弟节点对应2-3-4树是2节点,
不可能有多余的元素可以借),此时需要旋转变色
兄弟节点只有一个子节点的情况,此时需要旋转变色
兄弟和父亲节点帮不了忙,于是开始递归自损
前提是找到“真正”的兄弟节点
兄弟节点没有多余的元素可借(此时兄弟节点一定为黑色2节点),
此时兄弟节点所在分支也要自损一个黑色节点以此达到黑色平衡,
最快的方式就是兄弟节点直接变红(相当于就是减少一个黑色节点),
此时一父节点为root的子树又达到了平衡(两边都比之前少一个黑色)。
但是以祖父节点为root的树依然是不平衡的,此时需要递归处理。
删除操作:
1、删除叶子节点,直接删除
2、删除的节点有一个子节点,那么用子节点来替代
3、如果删除的节点有2个子节点,此时需要找到前驱节点或者后继节点来替代
删除节点方案:
1、找到前驱节点,复制前驱节点值覆盖预备删除的节点的值,然后删除前驱节点
2、找到后继节点,复制后继节点的值覆盖预备删除的节点的值,然后删除后继节点
被删除的前驱节点或者后继节点只有2种情况:
1、被删节点是叶子节点
2、被删节点只有一个孩子
//情况一、替代节点是红色,则直接染红,
//补偿删除的黑色节点,这样红黑树依然保持平衡
setColor(x,BLACK);
//x是左孩子的情况
if (x == leftOf(parentOf(x)))
{
//兄弟节点
RBNode rnode = rightOf(parentOf(x));
//判断此时兄弟节点是否是真正的兄弟节点
if (colorOf(rnode) == RED)
{
setColor(rnode, BLACK);
setColor(parentOf(x), RED);
leftRotate(parentOf(x));
//找到真正的兄弟节点
rnode = rightOf(parentOf(x));
}
//情况三,找兄弟借,兄弟没得借
if (colorOf(leftOf(rnode)) == BLACK && colorOf(rightOf(rnode)) == BLACK)
{
//情况复杂
setColor(rnode, RED);
x = parentOf(x);
}
//情况二,找兄弟借,兄弟有的借
else
{
//分2种小情况:兄弟节点本来是3节点或者是4节点的情况
if (colorOf(rightOf(rnode)) == BLACK)
{
setColor(leftOf(rnode), BLACK);
setColor(rnode, RED);
rightRotate(rnode);
rnode = rightOf(parentOf(x));
}
setColor(rnode, colorOf(parentOf(x)));
setColor(parentOf(x), BLACK);
setColor(rightOf(rnode), BLACK);
leftRotate(parentOf(x));
x = root;
}
}
代码
package com.jane.redBlackTree;
public class RBTree<K extends Comparable<K>, V>
{
private static final boolean RED = false;
private static final boolean BLACK = true;
private RBNode root;
public RBNode getRoot()
{
return root;
}
public void setRoot(RBNode root)
{
this.root = root;
}
/**
* 围绕p左旋
* pf pf
* / /
* p pr(r)
* / \ ==> / \
* pl pr(r) p rr
* / \ / \
* rl rr pl rl
*
* @param p
*/
private void leftRotate(RBNode p)
{
if (p != null)
{
RBNode 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;
}
}
/**
* 右旋
* pf pf
* \ \
* p (l)pl
* / \ => / \
*(l)pl pr ll p
* / \ / \
* ll lr lr pr
*
* @param p
*/
private void rightRotate(RBNode p)
{
if (p != null)
{
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;
}
}
/**
* 找到指定节点的前驱节点,即找小于node节点的最大值
* @param node
*/
private RBNode predecessor(RBNode node)
{
if (node == null)
{
return null;
} else if (node.left != null)
{
RBNode p = node.left;
while (p.right != null)
{
p = p.right;
}
return p;
}
/**
* 这种情况在删除节点的时候是不会遇到的,但是我们需要写完整的找前驱节点
* 这个就是当前节点没有子节点的情况,所以就一直向父节点来找
* 因为当前节点一直是父节点的左孩子,所以当当前节点是父节点的右孩子时
* 就终止寻找,这个父节点就是要找的节点
*/
else
{
RBNode p = node.parent;
RBNode ch = node;
while (p != null && ch == p.left)
{
ch = p;
p = p.parent;
}
return p;
}
}
/**
* 找后继节点,即大于节点的最小值
* @param node
* @return
*/
private RBNode successor(RBNode node)
{
if (node == null)
{
return null;
} else if (node.right != null)
{
RBNode p = node.right;
while (p.left != null)
{
p = p.left;
}
return p;
} else
{
RBNode p = node.parent;
RBNode ch = node;
while (p != null && ch == p.right)
{
ch = p;
p = p.parent;
}
return p;
}
}
private boolean colorOf(RBNode node)
{
return node == null ? BLACK : node.color;
}
private RBNode parentOf(RBNode node)
{
return node != null ? node.parent : null;
}
private RBNode leftOf(RBNode node)
{
return node != null ? node.left : null;
}
private RBNode rightOf(RBNode node)
{
return node != null ? node.right : null;
}
private void setColor(RBNode node, boolean color)
{
if (node != null)
{
node.color = color;
}
}
public void put(K key, V value)
{
RBNode t = this.root;
//如果是根节点
if (t == null)
{
root = new RBNode<>(key, value == null ? key : value, null);
return;
}
int cmp;
//寻找插入位置
//定义一个双亲指针
RBNode parent;
if (key == null)
{
throw new NullPointerException();
}
//沿着跟节点寻找插入位置
do
{
parent = 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<K, Object> e = new RBNode<>(key, value == null ? key : value, parent);
//如果比较最终落在左子树,则直接将父节点左指针指向e
if (cmp < 0)
{
parent.left = e;
}
//如果比较最终落在右子树,则直接将父节点右指针指向e
else
{
parent.right = e;
}
//调整
fixAfterPut(e);
}
/**
* 1、2-3-4树:新增元素+2节点合并(节点中只有1个元素)=3节点(节点中有2个元素)
* 红黑树:新增一个红色节点+黑色父亲节点=上黑下红(2节点)--------------------不要调整
*
* 2、2-3-4树:新增元素+3节点合并(节点中有2个元素)=4节点(节点中有3个元素)
* 这里有4种小情况(左3,右3,还有2个左中右不需要调整)------左3,右3需要调整,其余2个不需要调整
* 红黑树:新增红色节点+上黑下红=排序后中间节点是黑色,两边节点都是红色(3节点)
*
* 3、2-3-4树:新增一个元素+4节点合并=原来的4节点分裂,中间元素升级为父节点,新增元素与剩下的其中一个合并
* 红黑树:新增红色节点+爷爷节点黑,父节点和叔叔节点都是红色=爷爷节点变红,父亲和叔叔变黑,如果爷爷是根节点,则再变黑
*
*
* @param x
*/
private void fixAfterPut(RBNode x) {
x.color = RED;
//本质上就是父节点是黑色就不需要调整,对应情况就是2,3
while (x != null && x != root && x.parent.color == RED)
{
//1、x的父节点是爷爷的左孩子(左3)
if (parentOf(x) == leftOf(parentOf(parentOf(x))))
{
//叔叔节点
RBNode 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));
}
//第2种情况
else
{
if (x == rightOf(parentOf(x)))
{
x = parentOf(x);
leftRotate(x);
}
//父亲变黑
setColor(parentOf(x), BLACK);
//爷爷变红
setColor(parentOf(parentOf(x)), RED);
//根据爷爷节点右旋转
rightRotate(parentOf(parentOf(x)));
}
}
//2、跟第一种情况相反操作
else
{
//右3
//叔叔节点
RBNode 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));
}
//第2种情况
else
{
if (x == leftOf(parentOf(x)))
{
x = parentOf(x);
rightRotate(x);
}
//父亲变黑
setColor(parentOf(x), BLACK);
//爷爷变红
setColor(parentOf(parentOf(x)), RED);
//根据爷爷节点右旋转
leftRotate(parentOf(parentOf(x)));
}
}
}
root.color = BLACK;
}
public V remove(K key)
{
RBNode node = getNode(key);
if (node == null)
{
return null;
}
V oldValue = (V) node.value;
deleteNode(node);
return oldValue;
}
/**
* 删除操作:
* 1、删除叶子节点,直接删除
* 2、删除的节点有一个子节点,那么用子节点来替代
* 3、如果删除的节点有2个子节点,此时需要找到前驱节点或者后继节点来替代
* @param node
*/
private void deleteNode(RBNode node)
{
//3、node节点有2个孩子
if (node.left != null && node.right != null)
{
//后继节点替代
RBNode rep = successor(node);
//前驱节点替代
// RBNode rep= predecessor(node);
node.key = rep.key;
node.value = rep.value;
node = rep;
}
RBNode replacement = node.left != null ? node.left : node.right;
//2、替代节点不为空
if (replacement != null)
{
//替代者的父指针指向的原来node的父亲
replacement.parent = node.parent;
//node是根节点
if (node.parent == null)
{
root = replacement;
}
//node是左孩子,所以替代者依然是左孩子
else if (node == node.parent.left)
{
node.parent.left = replacement;
}
//node是右孩子,所以替代者依然是右孩子
else
{
node.parent.right = replacement;
}
//将node的左右孩子指针和父指针都指向null(此时node处于游离状态,等待垃圾回收)
node.left = node.right = node.parent = null;
//替换完之后需要调整平衡
if (node.color == BLACK)
{
//需要调整,这种情况一定是红色(替代节点一定是红色,此时只要变色)
fixAfterRemove(replacement);
}
}
//删除节点就是根节点
else if (node.parent == null)
{
root = null;
}
//1、node节点是叶子节点,replacement为null
else
{
//先调整
if (node.color == BLACK)
{
fixAfterRemove(node);
}
//再删除
if (node.parent != null)
{
if (node == node.parent.left)
{
node.parent.left = null;
} else if (node == node.parent.right)
{
node.parent.right = null;
}
node.parent = null;
}
}
}
/**
* 删除后调整
* @param x
*/
private void fixAfterRemove(RBNode x)
{
while (x != root && colorOf(x) == BLACK)
{
//x是左孩子的情况
if (x == leftOf(parentOf(x)))
{
//兄弟节点
RBNode rnode = rightOf(parentOf(x));
//判断此时兄弟节点是否是真正的兄弟节点
if (colorOf(rnode) == RED)
{
setColor(rnode, BLACK);
setColor(parentOf(x), RED);
leftRotate(parentOf(x));
//找到真正的兄弟节点
rnode = rightOf(parentOf(x));
}
//情况三,找兄弟借,兄弟没得借
if (colorOf(leftOf(rnode)) == BLACK && colorOf(rightOf(rnode)) == BLACK)
{
//情况复杂
setColor(rnode, RED);
x = parentOf(x);
}
//情况二,找兄弟借,兄弟有的借
else
{
//分2种小情况:兄弟节点本来是3节点或者是4节点的情况
if (colorOf(rightOf(rnode)) == BLACK)
{
setColor(leftOf(rnode), BLACK);
setColor(rnode, RED);
rightRotate(rnode);
rnode = rightOf(parentOf(x));
}
setColor(rnode, colorOf(parentOf(x)));
setColor(parentOf(x), BLACK);
setColor(rightOf(rnode), BLACK);
leftRotate(parentOf(x));
x = root;
}
}
//x是右孩子的情况
else
{
//兄弟节点
RBNode rnode = leftOf(parentOf(x));
//判断此时兄弟节点是否是真正的兄弟节点
if (colorOf(rnode) == RED)
{
setColor(rnode, BLACK);
setColor(parentOf(x), RED);
rightRotate(parentOf(x));
//找到真正的兄弟节点
rnode = leftOf(parentOf(x));
}
//情况三,找兄弟借,兄弟没得借
if (colorOf(rightOf(rnode)) == BLACK && colorOf(leftOf(rnode)) == BLACK)
{
//情况复杂
setColor(rnode, RED);
x = parentOf(x);
}
//情况二,找兄弟借,兄弟有的借
else
{
//分2种小情况:兄弟节点本来是3节点或者是4节点的情况
if (colorOf(leftOf(rnode)) == BLACK)
{
setColor(rightOf(rnode), BLACK);
setColor(rnode, RED);
leftRotate(rnode);
rnode = leftOf(parentOf(x));
}
setColor(rnode, colorOf(parentOf(x)));
setColor(parentOf(x), BLACK);
setColor(leftOf(rnode), BLACK);
rightRotate(parentOf(x));
x = root;
}
}
}
//情况一、替代节点是红色,则直接染黑,补偿删除的黑色节点,这样红黑树依然保持平衡
setColor(x, BLACK);
}
private RBNode getNode(K key)
{
RBNode node = this.root;
while (node != null)
{
int cmp = key.compareTo((K) node.key);
if (cmp < 0)
{
node = node.left;
} else if (cmp > 0)
{
node = node.right;
} else
return node;
}
return null;
}
static class RBNode<K extends Comparable<K>, V>
{
private RBNode parent;
private RBNode left;
private RBNode right;
private boolean color;
private K key;
private V value;
public RBNode()
{
}
public RBNode(K key, V value, RBNode parent)
{
this.parent = parent;
this.color = BLACK;
this.key = key;
this.value = value;
}
public RBNode(RBNode parent, RBNode left, RBNode right, boolean color, K key, V value)
{
this.parent = parent;
this.left = left;
this.right = right;
this.color = color;
this.key = key;
this.value = value;
}
public RBNode getParent()
{
return parent;
}
public void setParent(RBNode parent)
{
this.parent = parent;
}
public RBNode getLeft()
{
return left;
}
public void setLeft(RBNode left)
{
this.left = left;
}
public RBNode getRight()
{
return right;
}
public void setRight(RBNode right)
{
this.right = right;
}
public boolean isColor()
{
return color;
}
public void setColor(boolean color)
{
this.color = color;
}
public K getKey()
{
return key;
}
public void setKey(K key)
{
this.key = key;
}
public V getValue()
{
return value;
}
public void setValue(V value)
{
this.value = value;
}
}
}