红黑树详解及代码实战
历史文章回顾:
平衡二叉树
前言
本文主要讲解红黑树的插入实现,从原理到代码实战
一、红黑树特性
1.任何节点非黑即红
2.根节点是黑色,
3.新节点是红色,叶子空节点是黑色
4.红节点不能相连
5.从任何一个节点出发经历的黑色节点数要一样
由此:红黑树非绝对平衡树,但由3、4点特性,可保证最大高度差不会超过两倍(最短全黑,最长红黑相间)
二、插入节点
新插入得节点因默认红色,所以只可能打破特性4,即父节点是红色
这时有几种可能存在:
1.叔节点是红色,这种好解决,将父和叔变黑,祖变红,再以祖为当前节点继续计算即可
2.叔节点是空或黑色,以当前节点、父、祖三个节点的形状的情况
2.1.子、父、祖在左侧成一条线,将父祖颜色互换(因子是红,父肯定是红,祖肯定是黑),即转换成红黑红的结构,以祖为点右旋
2.2.子、父、祖在右侧成一条线,与上述成镜像,变成左旋即可
2.3.子、父、祖不在一条线,这时先以父为点通过左旋或右旋,调整至一条线,再着色旋转即可
总结:在保证其颜色特性的同时,即已维护了红黑树的平衡性
1.叔节点是红色,将红色上移即可,父叔变黑,祖变红
2.叔是黑(空也是黑),通过旋转调整子、父、祖三点成一条线,由红红黑着色成红黑红,再旋转,以黑为父节点即可
三、代码实战
3.1.RBNode节点
package com.zg.demo.arithmetic.binarytree;
import lombok.Data;
/**
* RBNode class
*
* @author zg
* @date 2021/4/20 16:41
*/
@Data
public class RBNode {
private int value;
private RBNode parentNode;
private RBNode leftNode;
private RBNode rightNode;
private boolean red;
public RBNode(int value) {
this.value = value;
this.red = true;
}
public RBNode(int value, boolean red) {
this.value = value;
this.red = red;
}
public void insert(int value) {
insert(this, value);
}
/**
* 1.任何节点非黑即红
* 2.根节点是黑色,
* 3.新节点是红色,叶子空节点是黑色
* 4.红节点不能相连
* 5.从任何一个节点出发经历的黑色节点数要一样
*
* 新插入得节点因默认红色,所以只可能打破特性4,即父节点是红色
* 这时有几种可能存在
* 1.叔节点是红色,这种好解决,将父和叔变黑,祖变红,再以祖为当前节点继续计算即可
* 2.叔节点是空或黑色,以当前节点、父、祖三个节点的形状的情况
* 2.1.子、父、祖在左侧成一条线,将父祖颜色互换(因子是红,父肯定是红,祖肯定是黑),即转换成红黑红的结构,以祖为点右旋
* 2.2.子、父、祖在右侧成一条线,与上述成镜像,变成左旋即可
* 2.3.子、父、祖不在一条线,这时先以父为点通过左旋或右旋,调整至一条线,再着色旋转即可
*
* 总结下:
* 1.叔节点是红色,将红色上移即可,父叔变黑,祖变红
* 2.叔是黑(空也是黑),通过旋转调整子、父、祖三点成一条线,由红红黑着色成红黑红,再旋转,以黑为父节点即可
* @param root root
* @param value value
*/
private void insert(RBNode root, int value) {
RBNode node = new RBNode(value);
RBNode parentNode = root;
// 找到插入节点父节点
while (true) {
if (value < parentNode.value) {
if (parentNode.leftNode != null) {
parentNode = parentNode.leftNode;
continue;
}
parentNode.leftNode = node;
} else {
if (parentNode.rightNode != null) {
parentNode = parentNode.rightNode;
continue;
}
parentNode.rightNode = node;
}
node.parentNode = parentNode;
break;
}
// 判断父节点颜色,是黑色,则插入结束,是红色需要修复
if (parentNode.red) {
insertFixUp(node);
}
}
/**
* 插入数据修正方法
* 1.当有且叔叔节点是红色 将父和叔变黑,祖父变红,相当于在黑上加红,下一步对祖父节点判断修正
* 2.叔叔节点无或者黑色 目标 转换成子 父 祖 左边一条线,将父设为黑,子/祖为红,右旋,下一步对祖父节点判断修正
* @param node 插入节点
*/
private void insertFixUp(RBNode node) {
RBNode parent, grandParent;
while ((parent = node.parentNode) != null && parent.red) {
grandParent = parent.parentNode;
RBNode uncle = getUncleNode(node);
// 1.叔叔节点是红色 将父节点以及叔叔节点都变为黑色,祖父节点变为红色
if (uncle != null && uncle.isRed()) {
parent.red = false;
uncle.red = false;
grandParent.red = true;
// 因祖父节点变红,固需以祖父节点为参考,开始计算修复
node = grandParent;
continue;
}
// 父节点是左孩子
if (parent == grandParent.leftNode) {
// 2.叔叔节点是黑色,当前节点是右孩子,先左旋转换成子 父 祖 一条线
if (parent.rightNode == node) {
leftRotate(parent);
RBNode index = parent;
parent = node;
node = index;
}
// 3.是叔叔节点是黑色,且子 父 祖一条线全部在左边
parent.red = false;
grandParent.red = true;
rightRotate(grandParent);
}
// 父节点是右孩子,与左孩子成镜像
if (parent == grandParent.rightNode) {
if (parent.leftNode == node) {
rightRotate(parent);
RBNode index = parent;
parent = node;
node = index;
}
parent.red = false;
grandParent.red = true;
leftRotate(grandParent);
}
}
}
private RBNode getUncleNode(RBNode node) {
RBNode parent = node.parentNode;
RBNode grandParent = parent.parentNode;
if (grandParent == null) {
return null;
}
if (parent == grandParent.leftNode) {
return grandParent.rightNode;
}
return grandParent.leftNode;
}
/**
* 右旋
* 5 3
* / \ / \
* 3 8 2 5
* / \ / / \
* 2 4 1 4 8
* /
* 1
* @param node node
*/
private void rightRotate(RBNode node) {
RBNode leftNode = node.leftNode;
RBNode parentNode = node.parentNode;
RBNode rightGrandSon = leftNode.rightNode;
if (parentNode != null) {
if (parentNode.leftNode == node) {
parentNode.leftNode = leftNode;
}
if (parentNode.rightNode == node) {
parentNode.rightNode = leftNode;
}
}
leftNode.parentNode = parentNode;
node.parentNode = leftNode;
leftNode.rightNode = node;
node.leftNode = rightGrandSon;
if (rightGrandSon != null) {
rightGrandSon.parentNode = node;
}
}
/**
* 左旋
* 5 7
* / \ / \
* 3 7 5 8
* / \ / \ \
* 6 8 3 6 9
* \
* 9
* @param node node
*/
private void leftRotate(RBNode node) {
RBNode rightNode = node.rightNode;
RBNode parentNode = node.parentNode;
RBNode leftGrandSon = rightNode.leftNode;
if (parentNode != null) {
if (parentNode.leftNode == node) {
parentNode.leftNode = rightNode;
}
if (parentNode.rightNode == node) {
parentNode.rightNode = rightNode;
}
}
rightNode.parentNode = parentNode;
node.parentNode = rightNode;
node.rightNode = leftGrandSon;
rightNode.leftNode = node;
if (leftGrandSon != null) {
leftGrandSon.parentNode = node;
}
}
@Override
public String toString() {
return "RBNode{" +
"value=" + value +
", red=" + red +
'}';
}
}
3.2.RBTree
package com.zg.demo.arithmetic.binarytree;
import lombok.Data;
/**
* RBTree class
*
* @author zg
* @date 2021/4/21 10:25
*/
@Data
public class RBTree {
private RBNode root;
public void insertNode(int value) {
if (root == null) {
root = new RBNode(value, false);
return;
}
root.insert(value);
setRootNode(root);
root.setRed(false);
}
private void setRootNode(RBNode node) {
if (node.getParentNode() == null) {
this.setRoot(node);
return;
}
setRootNode(node.getParentNode());
}
}
3.3.test
public static void main(String[] args) {
//红黑树
RBTree rbTree = new RBTree();
for (int i = 0; i < 12; i++) {
rbTree.insertNode(i);
}
printLevel(rbTree.getRoot());
}
private static void printLevel(RBNode root) {
LinkedBlockingQueue<RBNode> queue = new LinkedBlockingQueue<>();
queue.add(root);
List<List<RBNode>> lists = new ArrayList<>();
lists.add(new ArrayList<>());
while (!queue.isEmpty()) {
RBNode currentNode = queue.poll();
int level = getLevel(currentNode, 0);
if (lists.size() == level) {
lists.add(new ArrayList<>());
}
List<RBNode> list1 = lists.get(level);
list1.add(currentNode);
if (currentNode.getLeftNode() != null) {
queue.add(currentNode.getLeftNode());
}
if (currentNode.getRightNode() != null) {
queue.add(currentNode.getRightNode());
}
}
lists.forEach(nodes -> {
nodes.forEach(node -> {
System.out.print(node.getValue() + "-" + node.isRed());
System.out.print(" ");
});
System.out.println();
});
}
private static int getLevel(RBNode node, Integer level) {
if (node.getParentNode() != null) {
return getLevel(node.getParentNode(), ++level);
}
return level;
}