红黑树的性质(重点):
- 每个结点不是红色就是黑色
- 不可能有连在一起的红色结点(黑色的就可以),每个叶子节点都是黑色的空节点(NIL),也就是说,叶子节点不存储数据
- 根结点一定是黑色
- 每个节点,从该节点到达其可达叶子节点的所有路径,都包含相同数目的黑色节点
红黑树的性能
插入 | 查找 | 删除 |
---|---|---|
近似:nlogn | logn | 近似logn |
红黑树的应用:
- HashMap
- TreeMap
- Windows底层:查找
- Linux进程调度,nginx等
为了满足红黑树的性质,因此出现了旋转:3种变换
1、改变颜色 2、左旋 3.、右旋
条件:当前结点的父亲是红色,叔叔结点也是红色:
private boolean colorChange(MyTreeNode node) {
MyTreeNode uncleNode = node.uncleNode(); // 叔叔节点
// 父红叔红
if (node.parent.color == R && uncleNode != nil && uncleNode.color == R) {
// 把父结点设为黑色
node.parent.color = B;
// 把叔叔结点也设为黑色
uncleNode.color = B;
// 爷爷节点设置为红色,如果爷爷为根颜色为黑
if (node.parent.parent.parent != nil){
node.parent.parent.color = R;
}
// 把指针定义到爷爷结点结点设为当前要操作的结点
node = node.parent.parent;
return Boolean.TRUE;
}
return Boolean.FALSE;
}
if (node.parent.color == R && node.uncleNode().color == B ) { // 父红叔黑
if (node.parent.isLeftNode() && node.isRightNode()) { // 父左子右
node = node.parent; // 自底向上
rotateLeft(node); // 左旋
}
if (node.parent.isRightNode() && node == node.parent.right) { // 父右子右
node.parent.color = B; // 父亲设置为黑
node.parent.parent.color = R; // 爷爷设置为红
rotateLeft(node.parent.parent); // 以爷爷结点进行左边旋
}
}
if (node.parent.color == R && node.uncleNode().color == B ) { // 父红叔黑
if (node.parent.isLeftNode() && node.isLeftNode()) { // 父左子左
node.parent.color = B; // 把父结点设为黑色
node.parent.parent.color = R; // 把爷爷结点设为红色
rotateRight(node.parent.parent); // 以爷爷结点进行右旋
}
if (node.parent.isRightNode() && node == node.parent.right) { // 父右子左
node = node.parent;
rotateRight(node); //右旋
}
}
package datastructure.tree;
import datastructure.queue.LinkedListQueue;
/**
* 节点
*
* @author zw
* @create 2023-04-03 16:12
*/
public class MyTreeNode<T extends Comparable<T>> {
public int color; // 颜色:红黑树属性
public int weight; //频率(权重):哈夫曼树属性
public T data;
public MyTreeNode<T> left;
public MyTreeNode<T> right;
public MyTreeNode<T> parent;
@Override
public String toString() {
return "Node [color=" + color + ", left=" + left.data + ", right=" + right.data + ", parent="
+ parent.data + "]" + "\r\n";
}
public MyTreeNode(T data, MyTreeNode<T> left, MyTreeNode<T> right) {
this.data = data;
this.left = left;
this.right = right;
}
public MyTreeNode(T data) {
this.data = data;
}
public MyTreeNode(T data, int weight) {
this.data = data;
this.weight = weight;
}
// 用于节点的层数
public int getNodeDepth(BinaryNode root) {
return root.equals(this) ? 0 : (1 + Math.max(getNodeDepth(root.left), getNodeDepth(root.right)));
}
/**
* 查找后继节点
*
* @return
*/
public MyTreeNode findSuccessorsNode() { // 查找node的后继节点
if (this.right == null) { // 表示没有右边 那就没有后继
return this;
}
MyTreeNode cur = this.right;
MyTreeNode pre = this.right; // 开一个额外的空间 用来返回后继节点,因为我们要找到为空的时候,那么其实返回的是上一个节点
while (cur != null) {
pre = cur;
cur = cur.left; // 注意后继节点是要往左边找,因为右边的肯定比左边的大,我们要找的是第一个比根节点小的,所以只能往左边
}
return pre; // 因为cur会变成null,实际我们是要cur的上一个点,所以就是pre来代替
}
/**
* 前序遍历:根(输出) 左 右 时间复杂度?O(n) N^2 O(2*n)=>O(n);
*/
public void pre(MyTreeNode root) {
System.out.print(root.data); // 根
if (root.left != null) pre(root.left); // 左
if (root.right != null) pre(root.right); // 右
}
public void pre() {
pre(this);
}
/**
* 中序遍历:左 根(输出) 右
*/
public void in(MyTreeNode root) {
if (root.left != null) in(root.left); // 左
System.out.print(root); // 根
if (root.right != null) in(root.right); // 右
}
public void in() {
in(this);
}
/**
* 后序遍历:左 右 根(输出)
*/
public void post(MyTreeNode root) {
if (root.left != null) post(root.left); // 左
if (root.right != null) post(root.right); // 右
System.out.print(root.data); // 根
}
public void post() {
post(this);
}
/**
* 广度优先遍历:层次遍历
* 算法思想:
* (1)我们定义一个队列,先将根结点入队;
* (2)当前结点是队头结点,将其出队并访问;
* (3)若当前结点的左结点不为空将左结点入队;若当前结点的右结点不为空将其入队即可。
*/
private void bfs(MyTreeNode root) {
LinkedListQueue<MyTreeNode> queue = new LinkedListQueue<MyTreeNode>(100);
// 添加元素到队尾
queue.add(root);
while (!queue.isEmpty()) {
// 取出队头元素
MyTreeNode head = queue.poll();
System.out.print(head.data);
// 将左节点添加到队列
if (head.left != null) queue.add(head.left);
// 将右边节点添加队列
if (head.right != null) queue.add(head.right);
}
}
public void bfs() {
bfs(this);
}
/**
* 兄弟节点
*
* @return
*/
public MyTreeNode brotherNode() {
if (this.isLeftNode()) return this.parent.right;
if (this.isRightNode()) return this.parent.left;
return null;
}
/**
* 叔叔节点
*
* @return
*/
public MyTreeNode uncleNode() {
if (this.parent == null) return null;
else if (this.parent.isLeftNode()) return this.parent.parent.right;
else if (this.parent.isRightNode()) return this.parent.parent.left;
return null;
}
/**
* 大于
*
* @param data
* @return
*/
public boolean gt(T data) {
return this.data.compareTo(data) > 0;
}
public boolean gt(MyTreeNode<T> node) {
return this.data.compareTo(node.data) > 0;
}
/**
* 等于
*
* @param data
* @return
*/
public boolean equals(T data) {
return this.data.compareTo(data) == 0;
}
/**
* 小于
*
* @param data
* @return
*/
public boolean lt(T data) {
return this.data.compareTo(data) < 0;
}
public boolean lt(MyTreeNode<T> node) {
return this.data.compareTo(node.data) < 0;
}
public boolean isRootNode() {
return this.parent == null;
}
public boolean isLeftNode() {
return this.equals(this.parent.left);
}
public boolean isRightNode() {
return this.equals(this.parent.right);
}
public boolean isLeaf() {
return this.left == null && this.right == null;
}
public boolean isLeftLeaf() {
return (this.left == null && this.right == null)
&& this.equals(this.parent.left);
}
public boolean isRightLeaf() {
return (this.left == null && this.right == null)
&& this.equals(this.parent.right);
}
/**
* 存在2个子节点
*
* @return
*/
public boolean have2ChildNode() {
return this.left != null && this.right != null;
}
/**
* 只存在左子节点
*
* @return
*/
public boolean onlyLeftChildNode() {
return this.left != null && this.right == null;
}
/**
* 只存在右子节点
*
* @return
*/
public boolean onlyRightChildNode() {
return this.left == null && this.right != null;
}
/**
* 打印当前节点树
*/
public void show() {
// 得到树的深度
int treeDepth = getTreeDepth(this);
// 最后一行的宽度为2的(n - 1)次方乘3,再加1
// 作为整个二维数组的宽度
int arrayHeight = treeDepth * 2 - 1;
int arrayWidth = (2 << (treeDepth - 2)) * 3 + 1;
// 用一个字符串数组来存储每个位置应显示的元素
String[][] res = new String[arrayHeight][arrayWidth];
// 对数组进行初始化,默认为一个空格
for (int i = 0; i < arrayHeight; i++) {
for (int j = 0; j < arrayWidth; j++) {
res[i][j] = " ";
}
}
// 从根节点开始,递归处理整个树
writeArray(this, 0, arrayWidth / 2, res, treeDepth);
// 此时,已经将所有需要显示的元素储存到了二维数组中,将其拼接并打印即可
for (String[] line : res) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < line.length; i++) {
sb.append(line[i]);
if (line[i].length() > 1 && i <= line.length - 1) {
i += line[i].length() > 4 ? 2 : line[i].length() - 1;
}
}
System.out.println(sb.toString());
}
}
private void writeArray(MyTreeNode<T> currNode, int rowIndex, int columnIndex, String[][] res, int treeDepth) {
// 保证输入的树不为空
if (currNode == null) return;
// 先将当前节点保存到二维数组中
res[rowIndex][columnIndex] = String.valueOf(currNode.data)+"|" + String.valueOf(currNode.color);
// 计算当前位于树的第几层
int currLevel = ((rowIndex + 1) / 2);
// 若到了最后一层,则返回
if (currLevel == treeDepth)
return;
// 计算当前行到下一行,每个元素之间的间隔(下一行的列索引与当前元素的列索引之间的间隔)
int gap = treeDepth - currLevel - 1;
// 对左儿子进行判断,若有左儿子,则记录相应的"/"与左儿子的值
if (currNode.left != null) {
res[rowIndex + 1][columnIndex - gap] = "/";
writeArray(currNode.left, rowIndex + 2, columnIndex - gap * 2, res, treeDepth);
}
// 对右儿子进行判断,若有右儿子,则记录相应的"\"与右儿子的值
if (currNode.right != null) {
res[rowIndex + 1][columnIndex + gap] = "\\";
writeArray(currNode.right, rowIndex + 2, columnIndex + gap * 2, res, treeDepth);
}
}
/**
* 用于获得树的层数
*
* @return
*/
public int getTreeDepth(MyTreeNode<T> currNode) {
return currNode == null ? 0 : (1 + Math.max(getTreeDepth(currNode.left), getTreeDepth(currNode.right)));
}
}
package datastructure.tree;
import java.util.Arrays;
/**
* 红黑树
* 特性:
* 1) 每个结点不是红色就是黑色
* 2) 不可能有连在一起的红色结点(黑色的就可以)
* 3) 每个叶子节点都是黑色的空节点(NIL),也就是说,叶子节点不存储数据
* 4) 根结点一定都是黑色
* 5) 每个节点,从该节点到达其可达叶子节点的所有路径,都包含相同数目的黑色节点
*
* @author zw
* @create 2023-04-09 18:00
*/
public class MyRedBlackTree<T extends Comparable<T>> {
// nil表示的是叶子结点
private final MyTreeNode nil = new MyTreeNode(-1);
private MyTreeNode root = nil;
private final int R = 0; // 红色
private final int B = 1; // 黑色
/**
* 插入
*
* @param node
*/
private void insert(MyTreeNode<T> node) {
node.left = nil;
node.right = nil;
MyTreeNode temp = root;
// 插入结点染色情况
//1.如果插入前是空树,那么新插入的元素就会成为根节点,根据特征,需要将根节点染成黑色。
if (root == nil) {
root = node;
node.color = B;
node.parent = nil;
}
//2.如果红黑树非空,那么在红黑树中插入新的结点时,所有的点都默认是红色结点
else {
node.color = R; // 插入节点默认为红
//System.out.println(String.format("插入%s:修正前打印树",node.data));
//root.show();
insert(root, node);
fixTree(node); // 修正树
System.out.println(String.format("插入%s:修正后打印树", node.data));
root.show();
}
}
/**
* 二叉搜索树的插入
*
* @param node
* @param insertNode
*/
public void insert(MyTreeNode node, MyTreeNode insertNode) {
if (node.lt(insertNode.data)) {
if (node.right != nil) {
insert(node.right, insertNode);
} else {
insertNode.parent = node;
node.right = insertNode;
}
} else {
if (node.left != nil) {
insert(node.left, insertNode);
} else {
insertNode.parent = node;
node.left = insertNode;
}
}
}
/**
* 插入结点后调整和平衡过程
* @param node
*/
private void fixTree(MyTreeNode node) {
// 1.变颜色的情况: 当前结点的父亲是红色,且它的祖父结点的另一个结点(也就是叔叔结点)也是红色:
while (node.parent.color == R) { // 父节点为红色
// 颜色变化
if (colorChange(node)) continue; // 叔叔节点为黑色才不会执行continue
// 左旋
rotateLeftFixTree(node);
// 右旋
rotateRightFixTree(node);
}
}
/**
* 右旋转修复树的情况
*
* @param node
*/
private void rotateRightFixTree(MyTreeNode node) {
if (node.parent.color == R && node.uncleNode().color == B) { // 父红叔黑
if (node.parent.isLeftNode() && node.isLeftNode()) { // 父左子左
node.parent.color = B; // 把父结点设为黑色
node.parent.parent.color = R; // 把爷爷结点设为红色
rotateRight(node.parent.parent); // 以爷爷结点进行右旋
}
if (node.parent.isRightNode() && node == node.parent.right) { // 父右子左
node = node.parent;
rotateRight(node); //右旋
}
}
}
/**
* 左旋转修复树的情况
*
* @param node
*/
private void rotateLeftFixTree(MyTreeNode node) {
if (node.parent.color == R && node.uncleNode().color == B) { // 父红叔黑
if (node.parent.isLeftNode() && node.isRightNode()) { // 父左子右
node = node.parent; // 自底向上
rotateLeft(node); // 左旋
}
if (node.parent.isRightNode() && node == node.parent.right) { // 父右子右
node.parent.color = B; // 父亲设置为黑
node.parent.parent.color = R; // 爷爷设置为红
rotateLeft(node.parent.parent); // 以爷爷结点进行左边旋
}
}
}
/**
* 变色
*
* @param node
* @return
*/
private boolean colorChange(MyTreeNode node) {
MyTreeNode uncleNode = node.uncleNode(); // 叔叔节点
// 叔叔节点也为红色的情况:
if (node.parent.color == R &&
uncleNode != nil &&
uncleNode.color == R) {
// 把父结点设为黑色
node.parent.color = B;
// 把叔叔结点也设为黑色
uncleNode.color = B;
// 爷爷节点设置为红色,如果爷爷为根颜色为黑
if (node.parent.parent.parent != nil) {
node.parent.parent.color = R;
}
// 把指针定义到爷爷结点结点设为当前要操作的结点
node = node.parent.parent;
return Boolean.TRUE;
}
return Boolean.FALSE;
}
/**
* 向左旋转,两种情况
* 1、左旋转为根节点
* 2、左旋转为有父节点
*
* @param node
*/
void rotateLeft(MyTreeNode node) {
// 1、左旋转为根节点
// node
// left right
// rl
if (node.parent == null) {
MyTreeNode oldRightLeft = node.right.left;
// 1.1 node右节点上浮,node下沉
root = node.right;
node.right.parent = null;
root.left = node;
node.parent = root;
// 1.2 将左旋节点node右子树的左子树,变为node的右子树
if (oldRightLeft != nil) {
oldRightLeft.parent = node;
}
node.right = oldRightLeft;
oldRightLeft.parent = node;
}
// 2、左旋转为有父节点
else {
MyTreeNode oldParent = node.parent;
// 2.1 当前节点为左节点
// oldParent
// node
// left right
// rl
if (oldParent.left == node) {
oldParent.left = node.right; // 上浮
node.right.parent = oldParent;
oldParent.left.left = node; // node下沉
node.parent = oldParent.left;
}
// 2.1 当前节点为右节点
// oldParent
// node
// left right
// rl
else {
oldParent.right = node.left; // 上浮
node.left.parent = oldParent;
oldParent.right.left = node; // node下沉
node.parent = oldParent.right;
}
// 2.3 node右侧左子树,挂成node右子树
if (node.right.left != nil) {
node.right.left.parent = node;
}
node.right = node.right.left;
node.parent.left = node;
}
}
/**
* 向右旋转
* 1、左旋转为根节点
* 2、左旋转为有父节点
*
* @param node
*/
void rotateRight(MyTreeNode node) {
MyTreeNode oldLeftRight = node.left.right;
// 1、右旋转节点为根节点
// node
// left right
// lr
if (node.parent == null) {
// nodo 左侧上浮,右侧下沉
root = node.left;
node.left.parent = null;
root.right = node;
node.parent = root;
// lr
if (oldLeftRight != nil) {
oldLeftRight.parent = node;
}
node.left = oldLeftRight;
oldLeftRight.parent = node;
}
// 2、右旋转节点不为根节点
else {
MyTreeNode oldParent = node.parent;
MyTreeNode rotationNode = node;
// 2.1 当前节点为左节点
// oldParent
// node
// left right
// lr
if (node.parent.left == node) {
oldParent.left = node.left; // 上浮
node.left.parent = oldParent;
oldParent.left.right = node; // node下沉
node.parent = oldParent.right;
}
// 2.1 当前节点为右节点
// oldParent
// node
// left right
// lr
else {
oldParent.right = node.left; // 上浮
node.left.parent = oldParent;
oldParent.right.right = node; // node下沉
node.parent = oldParent.right;
}
}
// lr变为node左子节点
if (oldLeftRight != nil) {
oldLeftRight.parent = node;
}
node.left = oldLeftRight;
}
}
public void printTree(MyTreeNode node) {
if (node == nil) {
return;
}
printTree(node.left);
System.out.print(node.toString());
printTree(node.right);
}
public static void main(String[] args) {
MyRedBlackTree<Integer> redBlackTree = new MyRedBlackTree<>();
int data[] = {53, 34, 80, 18, 46, 74, 88, 17, 33, 50, 72};
System.out.println(Arrays.toString(data));
for (int i = 0; i < data.length; i++) {
MyTreeNode node = new MyTreeNode<Integer>(data[i]);
redBlackTree.insert(node);
}
redBlackTree.printTree(redBlackTree.root);
}
打印结果中|后跟节点颜色,0红1黑
[53, 34, 80, 18, 46, 74, 88, 17, 33, 50, 72]
插入34:修正后打印树
53|1
/ \
34|0 -1|0
/ \
-1|0-1|0
插入80:修正后打印树
53|1
/ \
34|0 80|0
/ \ / \
-1|0-1|0-1|0-1|0
插入18:修正后打印树
53|1
/ \
34|1 80|1
/ \ / \
18|0 -1|0-1|0 -1|0
/ \
-1|0-1|0
插入46:修正后打印树
53|1
/ \
34|1 80|1
/ \ / \
18|0 46|0-1|0 -1|0
/ \ / \
-1|0-1|0-1|0-1|0
插入74:修正后打印树
53|1
/ \
34|1 80|1
/ \ / \
18|0 46|074|0 -1|0
/ \ / \ / \
-1|0-1|0-1|0-1|0-1|0
插入88:修正后打印树
53|1
/ \
34|1 80|1
/ \ / \
18|0 46|074|0 88|0
/ \ / \ / \ / \
-1|0-1|0-1|0-1|0-1|0-1|0-1|0
插入17:修正后打印树
53|1
/ \
34|0 80|1
/ \ / \
18|1 46|174|0 88|0
/ \ / / \ / \
17|0 -1|0-1|0-1|0-1|0-1|0-1|0 -1|0
/ \
-1|0-1|0
插入33:修正后打印树
53|1
/ \
34|0 80|1
/ \ / \
18|1 46|174|0 88|0
/ \ / / \ / \
17|0 33|0-1|0-1|0-1|0-1|0-1|0 -1|0
/ \ / \
-1|0-1|0-1|0-1|0
插入50:修正后打印树
53|1
/ \
34|0 80|1
/ \ / \
18|1 46|174|0 88|0
/ \ / / \ / \
17|0 33|0-1|0-1|050|0-1|0-1|0 -1|0
/ \ / \ / \
-1|0-1|0-1|0-1|0 -1|0-1|0
插入72:修正后打印树
53|1
/ \
34|0 80|0
/ \ / \
18|1 46|174|1 88|1
/ \ / / \ / \
17|0 33|0-1|072|050|0-1|0-1|0 -1|0
/ \ / \ / \ / \
-1|0-1|0-1|0-1|0-1|0-1|0-1|0
Node [color=0, left=-1, right=-1, parent=18]
Node [color=1, left=17, right=33, parent=34]
Node [color=0, left=-1, right=-1, parent=18]
Node [color=0, left=18, right=46, parent=53]
Node [color=1, left=-1, right=50, parent=34]
Node [color=0, left=-1, right=-1, parent=46]
Node [color=1, left=34, right=80, parent=-1]
Node [color=0, left=-1, right=-1, parent=74]
Node [color=1, left=72, right=-1, parent=80]
Node [color=0, left=74, right=88, parent=53]
Node [color=1, left=-1, right=-1, parent=80]
演示网站:演示插入过程
->->->->->->->->->
这不提供删除代码,了解即可,有兴趣的同学可根据如下文件提供思路进行实现
红黑树的删除.docx