1. 红黑树
1. 红黑树是二叉搜索树的优化版,应为如果二叉搜索树的节点大部分全集中在左子树或右子树上,就会导致树的高度非常高,继而导致在进行集合操作时与链表差距不大。红黑树是一种平衡二叉树结构,其保证了在最坏情况下的集合操作时间复杂度为O(lgn),保证了红黑树中任意一条从根节点到叶子节点的路径长度不会超过其他路径的2倍。
2. 树中的每个节点包含5个属性:key(该值是整数,用来进行比较搜索)、value(节点存储的数据对象)、color(节点颜色)、right(右子节点)、left(左子节点)以及p(父节点)。用color表示节点颜色,只能是红色或黑色。如果某个节点没有左子节点或右子节点(子节点为null),那么就将指向该左子节点或右子节点的指针值置为null,null(或者说NIL)可以看做二叉树的外部节点,也是叶子节点,而带有value的节点是为树的内部节点。实现代码如下
private static final class Node{
private int key;
private Object value;
private boolean red;//true为红色,false为黑色
private Node left;//左子节点
private Node right;//右子节点
private Node parent;//父节点
}
3. 红黑树性质:
(1)每个节点都是黑色或者红色的;
(2)根节点都是黑色的;
(3)每个叶子节点(NIL)都是黑色的;
(4)如果一个节点是红色的,则它的两个子节点都是黑色的;
(5)从根节点到其所有后代的叶子节点(NIL)的简单路径上,均包含相同数量的黑色节点;
(6)从性质5又可以推出:如果一个结点存在黑子结点,那么该结点肯定有两个子结点;
4. 红黑树中的左旋与右旋操作:
(1)左旋:左旋指的是将当前节点X的右子节点Y提升至当前节点X的位置,并且将X节点与Y节点的左子树进行交换,将Y节点的原左子树转移到X节点的柚子树上
(2)右旋:右旋与左旋刚好相反,将首先转移Y节点父节点的连接到X节点上,然后将Y节点为根节点的子树连接到X节点的右子树上,并且让X节点原来的右子树转移到Y节点的左子树上
(3)实现代码:
//左自旋方法
private Node rotateLeft(Node node, Node parent) {
Node temp = parent.parent;
if (temp == null) {
parent.right = node.left;
node.left.parent = parent;
node.left = parent;
parent.parent = node;
node.parent = null;
root = node;
return node;
}
if (temp.left == parent) {
temp.left = node;
node.parent = temp;
parent.right = node.left;
node.left.parent = parent;
node.left = parent;
parent.parent = node;
} else {
temp.right = node;
node.parent = temp;
parent.right = node.left;
node.left.parent = parent;
node.left = parent;
parent.parent = node;
}
return node;
}
//右自旋方法
private Node rotateRight(Node node, Node parent) {
Node temp = parent.parent;
if (temp == null) {
parent.left = node.right;
node.right.parent = parent;
node.right = parent;
parent.parent = node;
node.parent = null;
root = node;
return node;
}
if (temp.left == parent) {
temp.left = node;
node.parent = temp;
parent.left = node.right;
node.right.parent = parent;
node.right = parent;
parent.parent = node;
} else {
temp.right = node;
node.parent = temp;
parent.left = node.right;
node.right.parent = parent;
node.right = parent;
parent.parent = node;
}
return node;
}
2. 红黑树的插入操作实现
与普通二叉树的增长不同,普通搜索二叉树的增长是自顶向下增长,而红黑树是自顶向下增长,但同时要自底向上平衡,在看下面的实现思路时要考虑这句话的意思。
1. 插入操作包括两部分工作:一查找插入的位置;二插入后自平衡。查找插入节点的父结点很简单,跟查找操作区别不大
实现代码
//寻找要被插入的节点在红黑树中应在的位置,并插入
Node n = root;
while (true) {
if (node.key > n.key) {
if (n.right == null) {
n.right = node;
node.parent = n;
break;
} else {
n = n.right;
}
} else if (node.key == n.key) {
n.value = node.value;
break;
} else {
if (n.left == null) {
n.left = node;
node.parent = n;
break;
} else {
n = n.left;
}
}
}
2. 平衡二叉树:这一步是核心步骤,首先插入的节点要将其颜色设置为红色,因为红色在父结点(如果存在)为黑色结点时,红黑树的黑色平衡没被破坏,不需要做自平衡操作。但如果插入结点是黑色,那么插入位置所在的子树黑色结点总是多1,必须做自平衡。然后我们需要对插入节点所面临的的各种情况进行分析
(1)情景1:如果红黑树为空树,那么就直接将插入节点作为根节点。
(2)情景2:如果插入节点的父节点为黑色,那么就直接插入,无需进行二叉树平衡操作。
(3)情景3:如果插入节点的key已经存在,那么就替换该节点下的对象元素并且不更换原节点的颜色,只需要将原节点的值进行更新即可。(因为红黑树基本都是用来进行搜索的,每个节点除了要带一个存储的对象,还需要依据对象生成一个整数key来标示,key用来找到插入元素在红黑树中的位置,有些红黑树实现策略可能并不是将节点存储的对象进行更新,而是将新节点插入到key相等的父节点的左侧或右侧,不过要保证存储的对象实现了equals方法用来进行比较是否相等,不相等则插入,相等则不插入。)。
(4)情景4:当前被插入节点的叔叔节点存在,且父节点与叔叔节点都为红色;此情景下可以确定父节点的父节点(祖父节点)一定是黑色的,处理时比较简单,只需要将父节点与叔叔节点置为黑色,但此时会打破红黑树性质第5条,所以还需要将祖父节点置为红色即可。
置为红色节点后,祖父节点还有可能与其父节点冲突(都为红色),所以要将祖父节点视为新插入的节点再次进行平衡。
(5)叔叔结点不存在或为黑结点,并且插入结点的父亲结点是祖父结点的左子结点,而且插入节点是父节点的左子节点;该情况下需要将插入节点的父节点置为黑色,祖父节点置为红色,然后对父节点和祖父节点进行右旋处理。处理完成后就发现红黑树重新处于平衡状态
(6)叔叔结点不存在或为黑结点,并且插入结点的父亲结点是祖父结点的左子结点,而且插入节点是父节点的右子节点;该情况下需要先将插入节点与父节点进行一次左旋操作,转为情景(5),然后按照情景5进行处理即可。(此时顶层节点置为黑色,其与上层节点一定不会冲突,所以红黑树插入操作最多进行两次旋转操作)
(7)叔叔结点不存在或为黑结点,并且插入结点的父亲结点是祖父结点的右子结点,而且插入节点是父节点的右子节点;与情景5正好相反,该情况下需要将插入节点的父节点置为黑色,祖父节点置为红色,然后对父节点和祖父节点进行左旋处理。处理完成后就发现红黑树重新处于平衡状态
(8)叔叔结点不存在或为黑结点,并且插入结点的父亲结点是祖父结点的右子结点,而且插入节点是父节点的左子节点;将插入节点与父节点进行右旋操作,就会变为情景7,在执行情景7操作即可。处理完成后红黑树重新处于平衡状态
3. 平衡红黑树逻辑实现代码:
//平衡红黑树方法,传入新插入的节点作为参数
protected Node balanceTree(Node node) {
//如果插入节点的父节点为黑色节点,则表示插入完成无需平衡
if (!node.parent.red) {
return node;
}
Node pr = node.parent.parent.right;//祖父节点的右子节点
Node pl = node.parent.parent.left;//祖父节点的左子节点
Node gf = node.parent.parent;//祖父节点
//如果被插入节点的父节点是红色的,而且其兄弟节点也是红色的
if (pr != null && pl != null && pr.red && pl.red) {
//将左右子节点颜色置为黑色,祖父节点置为红色,并将新插入节点指针指向祖父节点
pr.red = false;
pl.red = false;
pl.parent.red = true;
node = pl.parent;
//将祖父节点作为新插入节点平衡操作
return balanceTree(node);
}
//如果被插入节点的父节点是红色的,而且兄弟节点为黑色或null,那么就对新插入节点的父节点与祖父节点进行左旋或右旋处理
//如果父节点为上一级节点的左子节点
if (node.parent == pl && (pr == null || !pr.red)) {
//当前插入节点为父节点的左子节点
if (node == pl.left) {
//右旋处理前需要将父节点与祖父节点的颜色变换
pl.red = false;
pl.parent.red = true;
//右旋处理父节点与祖父节点
rotateRight(pl, pl.parent);
return node;
}
//当前插入节点为父节点的右子节点
if (node == pr.right) {
//先左旋,将新插入节点与父节交换位置
rotateLeft(node, node.parent);
//右旋处理前需要将上面左旋处理后的新插入节点与父节点的颜色变换
node.red = false;
node.parent.red = true;
//然后右旋新节点与新父节点
rotateRight(node, node.parent);
return node;
}
}
//如果父节点为上一级节点的右子节点
if (node.parent == pr && (pl == null || !pl.red)) {
//当前插入节点为父节点的右子节点
if (node == pr.right) {
//左旋处理前需要将父节点与祖父节点的颜色变换
pr.red = false;
pr.parent.red = true;
//左旋处理
rotateLeft(pr, pr.parent);
return node;
}
//当前插入节点为父节点的左子节点
if (node == pr.left) {
//先右旋,将当前节点与父节点位置交换
rotateRight(node, node.parent);
//左旋处理前需要将上面右旋后的新插入节点与父节点的颜色变换
node.red = false;
node.parent.red = true;
//然后左旋
rotateLeft(node, node.parent);
return node;
}
}
return node;
}
4. 插入方法的实现:
public class RBTree {
private Node root=null;
public Node insert(Node node){
node.red = true;
if (root == null) {
root = node;
root.red = false;
return root;
}
//寻找要被插入的节点在红黑树中应在的位置,并插入
Node n = root;
while (true) {
if (node.key > n.key) {
if (n.right == null) {
n.right = node;
node.parent = n;
break;
} else {
n = n.right;
}
} else if (node.key == n.key) {
n.value = node.value;
return node;
} else {
if (n.left == null) {
n.left = node;
node.parent = n;
break;
} else {
n = n.left;
}
}
}
//插入后进行平衡
balanceTree(node);
return node;
}
//左自旋方法
private Node rotateLeft(Node node, Node parent) {
Node temp = parent.parent;
if (temp == null) {
parent.right = node.left;
node.left.parent = parent;
node.left = parent;
parent.parent = node;
node.parent = null;
root = node;
return node;
}
if (temp.left == parent) {
temp.left = node;
node.parent = temp;
parent.right = node.left;
node.left.parent = parent;
node.left = parent;
parent.parent = node;
} else {
temp.right = node;
node.parent = temp;
parent.right = node.left;
node.left.parent = parent;
node.left = parent;
parent.parent = node;
}
return node;
}
//右自旋方法
private Node rotateRight(Node node, Node parent) {
Node temp = parent.parent;
if (temp == null) {
parent.left = node.right;
node.right.parent = parent;
node.right = parent;
parent.parent = node;
node.parent = null;
root = node;
return node;
}
if (temp.left == parent) {
temp.left = node;
node.parent = temp;
parent.left = node.right;
node.right.parent = parent;
node.right = parent;
parent.parent = node;
} else {
temp.right = node;
node.parent = temp;
parent.left = node.right;
node.right.parent = parent;
node.right = parent;
parent.parent = node;
}
return node;
}
//平衡红黑树方法,传入新插入的节点作为参数
protected Node balanceTree(Node node) {
//如果插入节点的父节点为黑色节点,则表示插入完成无需平衡
if (!node.parent.red) {
return node;
}
Node pr = node.parent.parent.right;//祖父节点的右子节点
Node pl = node.parent.parent.left;//祖父节点的左子节点
//如果被插入节点的父节点是红色的,而且其兄弟节点也是红色的
if (pr != null && pl != null && pr.red && pl.red) {
//将左右子节点颜色置为黑色,祖父节点置为红色,并将新插入节点指针指向祖父节点
pr.red = false;
pl.red = false;
pl.parent.red = true;
node = pl.parent;
//将祖父节点作为新插入节点平衡操作
return balanceTree(node);
}
//如果被插入节点的父节点是红色的,而且兄弟节点为黑色或null,那么就对新插入节点的父节点与祖父节点进行左旋或右旋处理
//如果父节点为上一级节点的左子节点
if (node.parent == pl && (pr == null || !pr.red)) {
//当前插入节点为父节点的左子节点
if (node == pl.left) {
//右旋处理前需要将父节点与祖父节点的颜色变换
pl.red = false;
pl.parent.red = true;
//右旋处理父节点与祖父节点
rotateRight(pl, pl.parent);
return node;
}
//当前插入节点为父节点的右子节点
if (node == pr.right) {
//先左旋,将新插入节点与父节交换位置
rotateLeft(node, node.parent);
//右旋处理前需要将上面左旋处理后的新插入节点与父节点的颜色变换
node.red = false;
node.parent.red = true;
//然后右旋新节点与新父节点
rotateRight(node, node.parent);
return node;
}
}
//如果父节点为上一级节点的右子节点
if (node.parent == pr && (pl == null || !pl.red)) {
//当前插入节点为父节点的右子节点
if (node == pr.right) {
//左旋处理前需要将父节点与祖父节点的颜色变换
pr.red = false;
pr.parent.red = true;
//左旋处理
rotateLeft(pr, pr.parent);
return node;
}
//当前插入节点为父节点的左子节点
if (node == pr.left) {
//先右旋,将当前节点与父节点位置交换
rotateRight(node, node.parent);
//左旋处理前需要将上面右旋后的新插入节点与父节点的颜色变换
node.red = false;
node.parent.red = true;
//然后左旋
rotateLeft(node, node.parent);
return node;
}
}
return node;
}
private static final class Node{
private int key;
private Object value;
private boolean red;//true为红色,false为黑色
private Node left;//左子节点
private Node right;//右子节点
private Node parent;//父节点
}
}