红黑树的演变过程:
二叉排序树-----2-3-4树-----2-3树------红黑树
二叉排序树BST
二叉排序树是一颗二叉树,对于每个节点的值有 左节点 < 根节点 < 右节点
为了便于理解,这里使用递归来实现其基本操作方法
查找方法
private Value get(Node x, Key key){
if(x == null) return null;
int cmp = key.compareTo(x.key);
if(cmp>0)
return get(x.right, key);
else if(cmp<0)
return get(x.left, key);
else
return x.value;
}
插入方法
private Node put(Node x, Key key, Value val) {
if(x == null)
return new Node(key, val, 1);
int cmp = key.compareTo(x.key);
if(cmp>0)
x.right = put(x.right, key, val);
else if(cmp<0)
x.left = put(x.left, key, val);
else
x.value = val;
x.N = size(x.left) + size(x.right) + 1;
return x;
}
删除方法 基本思想用待删除节点的右子树的最小值来代替该节点
private Node delete(Node x, Key key){
if(x==null) return null;
int cmp = key.compareTo(x.key);
if(cmp<0) x.left = delete(x.left, key);
if(cmp>0) x.right = delete(x.right, key);
if(x.left==null) return x.right;
if(x.right==null) return x.left;
Node t = x;
x = min(t.right);
x.right = deleteMin(t.right);
x.left = t.left;
x.N = size(x.left) + size(x.right) + 1;
return x;
}
二叉排序树当输入序列有序时,树的高度会急剧增加,从而使得基本操作时间复杂度为O(N)。
2-3-4树以及LLRB
由二叉树出发,进一步定义2-3-4树,每个节点有三种类型2节点,3节点以及4节点。
定义2-3树,每个节点有三种类型2节点,3节点。
2-3-4树的插入算法
查找到待插入节点合适位置,如果节点为2,3节点则直接插入即可
如果节点为4节点,则不能直接插入,此时应该将4节点分解,然后执行插入,由于4节点的插入可能导致4节点的父节点结构破坏,因此需要依次向上判断处理父节点是否满足基本性质。
对于4节点的分解过程,有两种思路:
由上到下,在向下寻找带插入位置时,遇见4节点即进行变换;
由下到上,找到待插入位置,如果是4节点则进行分解,插入新节点,依次向上检查父节点是否满足性质,否则继续分解父节点
4节点的分解
对于4节点的分解,分为两种情况:
4节点的父节点为2节点: 将4节点中间值放到父节点中
4节点的父节点为3节点: 将4节点中间值提取到父节点 其余分解为2个2节点
利用由上到下思想,对于搜索路径的每一个4节点使用上述变换,易得最终插入位置必为非4节点,则直接插入即可。
红黑树
进一步演化为红黑树,使用节点颜色,连接颜色来等价性表示2-3-4树。
左倾红黑树LLRB:将2-3-4树中的3节点,4节点使用红连接来表示。
从红连接角度定义红黑树:
每个节点颜色由指向该节点的连接颜色决定,红色或者黑色;
所有红连接均为左连接
一个节点不能同时与两个红连接相连
完美黑色平衡:空连接到根节点的路径上黑连接数目相同
红黑树的基本变换
1.左旋转
将红连接左旋转 不会破坏完美黑色平衡
// 左旋转h的右连接
private Node rotateLeft(Node h) {
// System.out.println("rotateleft before " + h);
// 修改连接
Node x = h.right;
h.right = x.left;
x.left = h;
// 修改颜色
x.color = h.color;
h.color = RED;
// 修改数量
x.N = h.N;
h.N = size(h.left) + size(h.right) + 1;
// System.out.println("rotateleft after " + x);
return x;
}
2.右旋转
// 右旋转h的左连接
private Node rotateRight(Node h) {
Node x = h.left;
h.left = x.right;
x.right = h;
x.color = h.color;
h.color = RED;
x.N = h.N;
h.N = size(h.left) + size(h.right) + 1;
return x;
}
3.颜色变换 分解4节点
// 分解4节点
private void flipColors(Node h) {
h.color = RED;
h.left.color = BLACK;
h.right.color = BLACK;
}
4.颜色变换 合并生成4节点 分解4节点的逆过程
// 生成4节点
private void flipColorsD(Node h) {
h.color = BLACK;
h.left.color = RED;
h.right.color = RED;
}
5.由下到上调整,保持红连接均为左连接 分解4节点
private Node fixUp(Node h) {
if (isRed(h.right))
h = rotateLeft(h);
if (isRed(h.left) && isRed(h.left.left))
h = rotateRight(h);
if (isRed(h.left) && isRed(h.right))
flipColors(h);
return h;
}
6.将某节点的右子2节点转换为非2节点 此时可以直接删除右子节点
private Node moveRedRight(Node h) {
// 与兄弟节点合并
flipColorsD(h);
// 从兄弟节点借一个值
if (isRed(h.left.left)) {
h = rotateRight(h);
flipColors(h);
}
return h;
}
7.将某节点的左子2节点转换为非2节点 此时可以直接删除左子节点
private Node moveRedLeft(Node h) {
flipColorsD(h);
if (isRed(h.right.left)) {
h.right = rotateRight(h.right);
h = rotateLeft(h);
flipColors(h);
}
return h;
}
红黑树插入算法实现
插入算法基本思路两种:
2-3树 由下到上 插入新节点 左旋 右旋 分解四节点
2-3-4树 由上到下 分解4节点 插入新节点 左旋 右旋
利用BST插入算法思想,执行节点插入,将新节点设定为红色,利用红连接与父节点进行相连,需要进行调整使其满足红黑树的定义。
该方式可以认为是由下到上方式,先插入新节点,然后进行调整。
插入新节点 左旋 右旋 颜色变换分解4节点 其实此时红黑树等价于一颗2-3树,只有2节点或者3节点
private Node put(Node h, Key key, Value val) {
if (h == null)
return new Node(key, val, 1, RED);
int cmp = key.compareTo(h.key);
if (cmp > 0)
h.right = put(h.right, key, val);
else if (cmp < 0)
h.left = put(h.left, key, val);
else
h.val = val;
if (!isRed(h.left) && isRed(h.right))
h = rotateLeft(h);
if (isRed(h.left) && isRed(h.left.left))
h = rotateRight(h);
if (isRed(h.left) && isRed(h.right))
flipColors(h);
h.N = size(h.left) + size(h.right) + 1;
return h;
}
private boolean isRed(Node x) {
if (x == null)
return false;
return x.color;
}
也可以先进行分解4节点,然后进行节点插入,进行基本调整。
4节点是如何分解的
当父节点是2节点以及3节点时,具体变换规则如下所示,颜色变换分解4节点,左旋,右旋即可。此时红黑树等价于一颗2-3-4树,有2,3,4节点类型。
下面是由上到下思路在2-3-4树中插入新节点代码:
红黑树的删除算法实现
删除算法基本思路:
从根节点出发,寻找待删除节点位置;
如果遇见2节点,则利用基本变换将2节点转换为非2节点;
找到待删除节点,将待删除节点右子树节点替换为该节点,然后删除右子树的最小值节点;
删除最小值
删除最小值 往左子树中寻找最小值,当遇见2节点时,将2节点转换为非2节点,从父节点借一个节点或者从兄弟节点中借一个节点
public void deleteMin() {
root = deleteMin(root);
root.color = BLACK;
}
private Node deleteMin(Node h) {
if (h.left == null)
return null;
if (!isRed(h.left) && !isRed(h.left.left))
h = moveRedLeft(h);
h.left = deleteMin(h.left);
return fixUp(h);
}
删除最大值
删除最大值 往右子树寻找 如果遇见2节点,从父节点中借一个节点或者从兄弟节点中借一个节点
private void deleteMax() {
root = deleteMax(root);
root.color = BLACK;
}
private Node deleteMax(Node h) {
// 父节点为非2节点 从父节点借一个值
if (isRed(h.left))
h = rotateRight(h);
if (h.right == null)
return null;
// 从兄弟节点处理,借一个值或者直接与兄弟节点进行合并
if (!isRed(h.right) && !isRed(h.right.left))
h = moveRedRight(h);
h.right = deleteMax(h.right);
return fixUp(h);
}
删除任意值
public void delete(Key key) {
root = delete(root, key);
root.color = BLACK;
}
private Node delete(Node h, Key key) {
int cmp = key.compareTo(h.key);
//往左子树寻找待删除节点
if (cmp < 0) {
//如果该节点为2节点,将2节点转换为非2节点,从父节点借或者从兄弟节点中借
if (!isRed(h.left) && !isRed(h.left.left))
h = moveRedLeft(h);
h.left = delete(h.left, key);
} else {
//在右子树中 如果出现2节点转换为非2节点
if (isRed(h.left))
h = rotateRight(h);
//上述右旋转会改变h指针,因此需要重新判断
if (key.compareTo(h.key) == 0 && h.right == null)
return null;
if (!isRed(h.right) && !isRed(h.right.left))
h = moveRedRight(h);
//如果找到待删除节点,则将待删除节点的右子树最小值替换为该节点,然后删除右子树的最小值
//上述2节点转换操作会改变h指针 因此需要重新判断
if (key.compareTo(h.key) == 0) {
h.key = min(h.right).key;
h.val = get(h.right, h.key);
h.right = deleteMin(h.right);
} else {
h.right = delete(h.right, key);
}
}
return fixUp(h);
}
红黑树的性能
1.一颗大小为N的红黑树高度不会超过2lgN
2.一颗大小为N的红黑树,根节点到任意节点平均路径长度为 1.00lgN
3.在红黑树中,以下操作都是对数级别的:查找 插入 删除
红黑树的图形化展示
参考之前BST图形化显示:https://blog.csdn.net/u014106644/article/details/89670061
将红黑树转换为完全二叉树,计算相对坐标,利用SWT来绘制红黑树
红黑树层序遍历转换为完全二叉树
public List<List<Node>> levelOrder2(){
if(root == null) return null;
List<List<Node>> ll = new ArrayList<>();
Queue<Node> q = new LinkedList<>();
q.add(root);
int h = height();
int depth = 1;
while(q.size()>0){
int s = q.size();
List<Node> lk = new ArrayList<>();
while(s>0){
Node tmp = q.poll();
lk.add(tmp);
if(tmp.left!=null){
q.add(tmp.left);
}else{
q.add(new Node((Key)specialChar, (Value)specialChar, 1));
}
if(tmp.right!=null){
q.add(tmp.right);
}else{
q.add(new Node((Key)specialChar, (Value)specialChar, 1));
}
s--;
}
ll.add(lk);
if(depth>h-1)
break;
depth++;
}
return ll;
}
计算红黑树每一层的起始坐标以及每一层节点间隔距离
public List<List<Node>> calculateNodeCoordinate2(){
int h = height();
List<List<Node>> ll = levelOrder2();
for(int i=1; i<=h; i++){
int lineStart = pow2(h-i+1)-2;
int offset = pow2(h-i+2);
int t = lineStart;
for(int j=0; j<ll.get(i-1).size(); j++){
Node n = ll.get(i-1).get(j);
n.x = t;
n.y = i;
t = t + offset;
}
}
return ll;
}
利用SWT来绘制红黑树节点以及链接
private void showRBTNode(Group group){
rbtll = rbt.calculateNodeCoordinate2();
rbth = rbt.height();
//绘制节点
for(int i=1; i<=rbth; i++){
for(int j=0; j<rbtll.get(i-1).size(); j++){
RBTCoordinate<Integer, String>.Node nc = rbtll.get(i-1).get(j);
if(String.valueOf(nc.key).equals(RBTCoordinate.specialChar))
continue;
Label lname = new Label(group, SWT.NONE|SWT.CENTER);
lname.setBounds(getNodeLabelStartX(nc.x), getNodeLableStartY(nc.y), labelW, labelH);
lname.setText(String.valueOf(nc.key));
//System.out.println("i = " + i + " x = " + nc.getX() + " y = " + nc.getY());
}
}
}
private void showRBTLink(Group group) {
// group.redraw();
// 绘制连接
gc = new GC(group);
gc.setLineWidth(2);
for (int i = 1; i <= rbth; i++) {
for (int j = 0; j < rbtll.get(i - 1).size(); j++) {
RBTCoordinate<Integer, String>.Node nc = rbtll.get(i - 1).get(j);
if (nc.left != null) {
setGCColorByNode(nc.left);
gc.drawLine(getNodeLabelStartX(nc.x) + labelW / 2,
getNodeLableStartY(nc.y) + labelH,
getNodeLabelStartX(nc.left.x) + labelW / 2,
getNodeLableStartY(nc.left.y));
}
if (nc.right != null) {
setGCColorByNode(nc.right);
gc.drawLine(getNodeLabelStartX(nc.x) + labelW / 2,
getNodeLableStartY(nc.y) + labelH,
getNodeLabelStartX(nc.right.x) + labelW / 2,
getNodeLableStartY(nc.right.y));
}
} // end for
}
}
private void setGCColorByNode(RBTCoordinate<Integer, String>.Node nc){
if(nc.color)
gc.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_RED));
else
gc.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_BLACK));
}
最终显示效果如下:
数组初始化 增加节点 删除节点
理解红黑树插入以及删除的变换操作,可以从2-3-4树的插入以及删除操作来入手理解,核心是插入时如何分解4节点,删除时如何构造4节点。