红黑树
1、特性
- 所有节点均为黑色或者红色
- 根节点为黑色
- 所有为 NULL 的叶子节点都是黑色
- 如果该节点是红色的,那么该节点的子节点一定都是黑色
- 所有的 NULL 节点到根节点的路径上的黑色节点数量一定是相同的
2、操作
- 左旋
以某个结点作为支点(旋转结点),其右子结点变为旋转结点的父结点,右子结点的左子结点变为旋转结点的右子结点,左子结点保持不变。
/**
* 对目标节点进行左旋操作
*
* @param targetNode 目标节点
*/
private void leftRotate(RBNode<T> targetNode) {
// 目标节点的右子节点,下面称右儿子
RBNode<T> targetRightSon = targetNode.getRight();
// 将目标节点的右子节点设置为右儿子的左子节点
targetNode.setRight(targetRightSon.getLeft());
// 如果右儿子的左子节点不为空,则需要将它的父节点设置为目标节点
if (null != targetRightSon.getLeft()) {
targetRightSon.getLeft().setParent(targetNode);
}
// 将右儿子的父节点设为目标节点的父节点
targetRightSon.setParent(targetNode.getParent());
// 如果目标节点的父节点为空,说明目标节点是父节点,左旋后父节点应该变为右儿子
if (null == targetNode.getParent()) {
root = targetRightSon;
} else {
// 如果目标节点不是父节点,需要判断目标节点为其父节点的左子节点还是右子节点,将右儿子替换进去
if (targetNode.getParent().getLeft() == targetNode) {
targetNode.getParent().setLeft(targetRightSon);
} else {
targetNode.getParent().setRight(targetRightSon);
}
}
// 将右儿子的左子节点设置为目标节点
targetRightSon.setLeft(targetNode);
// 将目标节点的父节点设置为右儿子
targetNode.setParent(targetRightSon);
}
- 右旋
以某个结点作为支点(旋转结点),其左子结点变为旋转结点的父结点,左子结点的右子结点变为旋转结点的左子结点,右子结点保持不变。
/**
* 对目标节点进行右旋操作
*
* @param targetNode 目标节点
*/
private void rightRotate(RBNode<T> targetNode) {
// 目标节点的左子节点,下面称左儿子
RBNode<T> targetLeftSon = targetNode.getLeft();
// 将目标节点的左子节点设置为左儿子的右子节点
targetNode.setLeft(targetLeftSon.getRight());
// 如果左儿子的右节点不为空,则需要将它的父节点设置为目标节点
if (null != targetLeftSon.getRight()) {
targetLeftSon.getRight().setParent(targetNode);
}
// 将左儿子的父节点设置为目标节点的父节点
targetLeftSon.setParent(targetNode.getParent());
// 如果目标节点的父节点为空,说明目标节点是根节点,右旋后左二子应该变为父节点
if (null == targetNode.getParent()) {
root = targetLeftSon;
} else {
// 如果目标节点不是父节点,需要判断目标节点为其父节点的左子节点还是右子节点,将右儿子替换进去
if (targetNode.getParent().getLeft() == targetNode) {
targetNode.getParent().setLeft(targetLeftSon);
} else {
targetNode.getParent().setRight(targetLeftSon);
}
}
// 将左儿子的右子节点设置为目标节点
targetLeftSon.setRight(targetNode);
// 将目标节点的父节点设置为左儿子
targetNode.setParent(targetLeftSon);
}
- 变色
结点的颜色由红变黑或由黑变红。
3、插入
插入步骤
- 第一步:按二插查找树将节点插入
- 第二步:新插入节点置红
- 第三步:通过旋转、变色等操作,使它重新成为红黑树
场景说明 | 处理方式 | |
---|---|---|
情景一 | 插入节点为根节点 | 直接置黑 |
情景二 | 插入节点的父节点为黑色 | 无需处理 |
情景三 | 父节点为红色,叔叔节点也为红色 | 父节点置黑,叔叔节点置黑,祖父节点置红,将祖父节点设为当前节点,递归操作 |
情景四 | 父节点为红色,叔叔节点为黑色,父节点为祖父节点的左孩子,当前节点为父节点的左孩子(LL) | 父节点置黑,祖父节点置红,对祖父节点右旋 |
情景五 | 父节点为红色,叔叔节点为黑色,父节点为祖父节点的左孩子,当前节点为父节点的右孩子(LR) | 对父节点左旋,变成情景四(LL)处理 |
情景六 | 父节点为红色,叔叔节点为黑色,父节点为祖父节点的右孩子,当前节点为父节点的右孩子(RR) | 父节点置黑,祖父节点置红,对祖父节点左旋 |
情景七 | 父节点为红色,叔叔节点为黑色,父节点为祖父节点的右孩子,当前节点为父节点的左孩子(RL) | 对父节点右旋,变成情景六(RR)处理 |
本质就是将红色节点移动到根节点,然后置黑
/**
* 将结点插入到红黑树中
*
* @param newNode 插入的节点
*/
private void insert(RBNode<T> newNode) {
int flag;
RBNode<T> curr = null;
RBNode<T> temp = root;
// 找到新节点的父节点
while (null != temp) {
curr = temp;
flag = newNode.getKey().compareTo(curr.getKey());
if (flag < 0) {
temp = temp.getLeft();
} else {
temp = temp.getRight();
}
}
newNode.setParent(curr);
// 判断找到的父节点是否为空
if (null != curr) {
// 比较新节点与父节点的大小,决定插左边还是右边
flag = newNode.getKey().compareTo(curr.getKey());
if (flag < 0) {
curr.setLeft(newNode);
} else {
curr.setRight(newNode);
}
} else {
// 为空,新节点成为父节点
root = newNode;
}
// 重新修正
insertFixUp(newNode);
}
/**
* 红黑树插入修正
*
* @param node 要插入的节点
*/
private void insertFixUp(RBNode<T> node) {
// 生命父节点,祖父节点
RBNode<T> parent;
RBNode<T> grandParent;
// 当插入节点父节点不为空, 且为红色时
while ((parent = node.getParent()) != null && RED == parent.isColor()) {
grandParent = parent.getParent();
// 如果父节点是祖父节点左儿子
if (grandParent.getLeft() == parent) {
RBNode<T> uncle = grandParent.getRight();
// Case1: 叔叔节点为红色
if (null != uncle && RED == uncle.isColor()) {
parent.setColor(BLACK);
uncle.setColor(BLACK);
grandParent.setColor(RED);
node = grandParent;
continue;
}
if (parent.getRight() == node) {
// Case2: 叔叔节点为黑色,且当前节点为右孩子(LR——先对父节点左旋,变成LL处理)
leftRotate(parent);
}
// Case3: 叔叔节点为黑色,且当前节点为左孩子(LL——父节点置黑,祖父节点置红,对祖父节点右旋)
parent.setColor(BLACK);
parent.getParent().setColor(BLACK);
rightRotate(parent.getParent());
} else {
// 父节点是祖父节点的右儿子
RBNode<T> uncle = grandParent.getLeft();
// Case1: 叔叔节点是红色
if (null != uncle && RED == uncle.isColor()) {
parent.setColor(BLACK);
uncle.setColor(BLACK);
grandParent.setColor(RED);
node = grandParent;
continue;
}
if (parent.getLeft() == node) {
// Case2: 叔叔节点为黑色,且当前节点是左孩子(RL——先对父节点右旋,变成RR处理)
rightRotate(parent);
}
// Case3: 叔叔节点为黑色,且当前节点是右孩子(RR——父节点置黑,祖父节点置红,对祖父节点左旋)
parent.setColor(BLACK);
parent.getParent().setColor(RED);
leftRotate(parent.getParent());
}
}
// 根节点置黑
root.setColor(BLACK);
}
4、删除
删除步骤
- 第一步:将红黑树当做一颗二叉查找树,将节点删除
- 第二步:通过旋转和变色使其重新变为红黑树
删除二叉查找树
- 情况一:被删除节点为叶子结点,直接删除
- 情况二:被删除节点只有一个子节点,删除该节点,用唯一的子节点代替他
- 情况三:被删除的节点有两个子节点,则先找到其后继节点,把后继节点的值赋给它,然后把后继节点删除掉,其后继节点只可能为情况一或者情况二,按上面的步骤处理
修正操作
若替代节点为红色,无需处理,若为黑色有以下情况(讨论替代节点为左儿子情况,右儿子对称操作)
场景说明 | 处理方式 | |
---|---|---|
场景一 | 替代节点为父节点的左儿子(黑色),兄弟节点为红色 | 父节点置红,兄弟节点置黑,左旋,更新兄弟节点 |
场景二 | 替代节点为父节点的左儿子(黑色),兄弟节点为黑色,且左侄子节点,右侄子节点均为黑色 | 兄弟节点置红,父节点设为新的替代节点递归 |
场景三 | 替代节点为父节点的左儿子(黑色),兄弟节点为黑色,且左侄子节点为红色,右侄子节点为黑色 | 兄弟节点置红,左侄子节点置黑,右旋,更新兄弟节点 |
场景四 | 替代节点为父节点的左儿子(黑色),兄弟节点为黑色,右侄子节点为红色,左侄子节点颜色任意 | 将父节点的颜色给到兄弟节点,父节点置黑,右侄子节点置黑,对P左旋,将替代节点指向root跳出循环 |
private void remove(RBNode<T> node) {
if (null == node) {
return;
}
RBNode<T> replaceNode;
// Case1: 左右子树都不为空
if (null != node.getLeft() && null != node.getRight()) {
// 找到代替节点(中序遍历的后继节点,及右子树的最左节点)
replaceNode = node;
RBNode<T> temp = node.getRight();
while (null != temp) {
replaceNode = temp;
temp = temp.getLeft();
}
// 找到代替节点,两者的值交换,删除代替节点
T key = node.getKey();
node.setKey(replaceNode.getKey());
replaceNode.setKey(key);
remove(replaceNode);
return;
}
// Case2: 至少一个子树为空
if (null != node.getLeft()) {
replaceNode = node.getLeft();
} else {
replaceNode = node.getRight();
}
RBNode<T> parent = node.getParent();
if (null == parent) {
// 如果当前节点的父节点为空
root = replaceNode;
if (null != replaceNode) {
replaceNode.setParent(null);
}
} else {
// 如果当前节点的父节点不为空
if (null != replaceNode) {
replaceNode.setParent(parent);
}
if (parent.getLeft() == node) {
parent.setLeft(replaceNode);
} else {
parent.setRight(replaceNode);
}
}
// 如果删除的节点是黑色 会导致红黑树失衡 需要从新着色
if (BLACK == node.isColor()) {
removeFix(parent, replaceNode);
}
}
/**
* 删除颜色调整
*
* @param parent 替代节点的父节点
* @param replaceNode 替代节点
*/
private void removeFix(RBNode<T> parent, RBNode<T> replaceNode) {
while ((null == replaceNode || BLACK == replaceNode.isColor()) && root != replaceNode) {
// 替代节点为父节点的左子节点
if (parent.getLeft() == replaceNode) {
RBNode<T> brother = parent.getRight();
if (null != brother && RED == brother.isColor()) {
// Case1: 兄弟节点为红色, 将父节点置红,兄弟节点置黑,父节点左旋
brother.setColor(BLACK);
parent.setColor(RED);
leftRotate(parent);
brother = parent.getRight();
}
if (brother == null || (isBlack(brother.getLeft()) && isBlack(brother.getRight()))) {
// Case2: 兄弟节点黑 左侄子黑 右侄子黑, 将兄弟节点置红,父节点成为新的替代节点,继续递归
if (null != brother) {
brother.setColor(RED);
}
replaceNode = parent;
parent = parent.getParent();
continue;
}
if (isRed(brother.getLeft())) {
// Case3: 兄弟节点黑 左侄子红 右侄子黑, 将兄弟节点置红, 左侄子置黑 右旋兄弟节点
brother.setColor(RED);
brother.getLeft().setColor(BLACK);
rightRotate(brother);
// 更新兄弟节点
brother = brother.getParent();
}
// Case4: S黑 B黑 BR红, 将父节点颜色给到B,父节点置黑, 右侄子节点置黑,父节点左旋,跳出循环
brother.setColor(parent.isColor());
brother.getRight().setColor(BLACK);
parent.setColor(BLACK);
leftRotate(parent);
replaceNode = root;
} else {
// 替代节点为父节点的右子节点 和上面对称操作
RBNode<T> brother = parent.getLeft();
if (null != brother && RED == brother.isColor()) {
brother.setColor(BLACK);
parent.setColor(RED);
rightRotate(parent);
brother = parent.getLeft();
}
if (null == brother || (isBlack(brother.getLeft()) && isBlack(brother.getRight()))) {
if (null != brother) {
brother.setColor(RED);
}
replaceNode = parent;
parent = parent.getParent();
continue;
}
if (isRed(brother.getRight())) {
brother.setColor(BLACK);
brother.getRight().setColor(BLACK);
rightRotate(brother);
brother = brother.getParent();
}
brother.setColor(parent.isColor());
parent.setColor(BLACK);
brother.getLeft().setColor(BLACK);
rightRotate(parent);
replaceNode = root;
}
}
if (replaceNode != null) {
replaceNode.setColor(BLACK);
}
}
5、完整实现
package com.yuanjia.tree;
import lombok.Data;
@Data
public class RBTree<T extends Comparable<T>> {
private RBNode<T> root;
private final static boolean RED = false;
private final static boolean BLACK = true;
/**
* 新建一个节点插入到红黑树中
*
* @param key 需要插入的值
*/
public void insert(T key) {
insert(new RBNode<>(RED, key, null, null, null));
}
/**
* 将结点插入到红黑树中
*
* @param newNode 插入的节点
*/
private void insert(RBNode<T> newNode) {
int flag;
RBNode<T> curr = null;
RBNode<T> temp = root;
// 找到新节点的父节点
while (null != temp) {
curr = temp;
flag = newNode.getKey().compareTo(curr.getKey());
if (flag < 0) {
temp = temp.getLeft();
} else {
temp = temp.getRight();
}
}
newNode.setParent(curr);
// 判断找到的父节点是否为空
if (null != curr) {
// 比较新节点与父节点的大小,决定插左边还是右边
flag = newNode.getKey().compareTo(curr.getKey());
if (flag < 0) {
curr.setLeft(newNode);
} else {
curr.setRight(newNode);
}
} else {
// 为空,新节点成为父节点
root = newNode;
}
// 重新修正
insertFixUp(newNode);
}
/**
* 红黑树插入修正
*
* @param node 要插入的节点
*/
private void insertFixUp(RBNode<T> node) {
// 生命父节点,祖父节点
RBNode<T> parent;
RBNode<T> grandParent;
// 当插入节点父节点不为空, 且为红色时
while ((parent = node.getParent()) != null && RED == parent.isColor()) {
grandParent = parent.getParent();
// 如果父节点是祖父节点左儿子
if (grandParent.getLeft() == parent) {
RBNode<T> uncle = grandParent.getRight();
// Case1: 叔叔节点为红色
if (null != uncle && RED == uncle.isColor()) {
parent.setColor(BLACK);
uncle.setColor(BLACK);
grandParent.setColor(RED);
node = grandParent;
continue;
}
if (parent.getRight() == node) {
// Case2: 叔叔节点为黑色,且当前节点为右孩子(LR——先对父节点左旋,变成LL处理)
leftRotate(parent);
}
// Case3: 叔叔节点为黑色,且当前节点为左孩子(LL——父节点置黑,祖父节点置红,对祖父节点右旋)
parent.setColor(BLACK);
parent.getParent().setColor(BLACK);
rightRotate(parent.getParent());
} else {
// 父节点是祖父节点的右儿子
RBNode<T> uncle = grandParent.getLeft();
// Case1: 叔叔节点是红色
if (null != uncle && RED == uncle.isColor()) {
parent.setColor(BLACK);
uncle.setColor(BLACK);
grandParent.setColor(RED);
node = grandParent;
continue;
}
if (parent.getLeft() == node) {
// Case2: 叔叔节点为黑色,且当前节点是左孩子(RL——先对父节点右旋,变成RR处理)
rightRotate(parent);
}
// Case3: 叔叔节点为黑色,且当前节点是右孩子(RR——父节点置黑,祖父节点置红,对祖父节点左旋)
parent.setColor(BLACK);
parent.getParent().setColor(RED);
leftRotate(parent.getParent());
}
}
// 根节点置黑
root.setColor(BLACK);
}
public void remove(T key) {
RBNode<T> node;
if ((node = search(root, key)) != null) {
remove(node);
}
}
private void remove(RBNode<T> node) {
if (null == node) {
return;
}
RBNode<T> replaceNode;
// Case1: 左右子树都不为空
if (null != node.getLeft() && null != node.getRight()) {
// 找到代替节点(中序遍历的后继节点,及右子树的最左节点)
replaceNode = node;
RBNode<T> temp = node.getRight();
while (null != temp) {
replaceNode = temp;
temp = temp.getLeft();
}
// 找到代替节点,两者的值交换,删除代替节点
T key = node.getKey();
node.setKey(replaceNode.getKey());
replaceNode.setKey(key);
remove(replaceNode);
return;
}
// Case2: 至少一个子树为空
if (null != node.getLeft()) {
replaceNode = node.getLeft();
} else {
replaceNode = node.getRight();
}
RBNode<T> parent = node.getParent();
if (null == parent) {
// 如果当前节点的父节点为空
root = replaceNode;
if (null != replaceNode) {
replaceNode.setParent(null);
}
} else {
// 如果当前节点的父节点不为空
if (null != replaceNode) {
replaceNode.setParent(parent);
}
if (parent.getLeft() == node) {
parent.setLeft(replaceNode);
} else {
parent.setRight(replaceNode);
}
}
// 如果删除的节点是黑色 会导致红黑树失衡 需要从新着色
if (BLACK == node.isColor()) {
removeFix(parent, replaceNode);
}
}
/**
* 删除颜色调整
*
* @param parent 替代节点的父节点
* @param replaceNode 替代节点
*/
private void removeFix(RBNode<T> parent, RBNode<T> replaceNode) {
while ((null == replaceNode || BLACK == replaceNode.isColor()) && root != replaceNode) {
// 替代节点为父节点的左子节点
if (parent.getLeft() == replaceNode) {
RBNode<T> brother = parent.getRight();
if (null != brother && RED == brother.isColor()) {
// Case1: 兄弟节点为红色, 将父节点置红,兄弟节点置黑,父节点左旋
brother.setColor(BLACK);
parent.setColor(RED);
leftRotate(parent);
brother = parent.getRight();
}
if (brother == null || (isBlack(brother.getLeft()) && isBlack(brother.getRight()))) {
// Case2: 兄弟节点黑 左侄子黑 右侄子黑, 将兄弟节点置红,父节点成为新的替代节点,继续递归
if (null != brother) {
brother.setColor(RED);
}
replaceNode = parent;
parent = parent.getParent();
continue;
}
if (isRed(brother.getLeft())) {
// Case3: 兄弟节点黑 左侄子红 右侄子黑, 将兄弟节点置红, 左侄子置黑 右旋兄弟节点
brother.setColor(RED);
brother.getLeft().setColor(BLACK);
rightRotate(brother);
// 更新兄弟节点
brother = brother.getParent();
}
// Case4: S黑 B黑 BR红, 将父节点颜色给到B,父节点置黑, 右侄子节点置黑,父节点左旋,跳出循环
brother.setColor(parent.isColor());
brother.getRight().setColor(BLACK);
parent.setColor(BLACK);
leftRotate(parent);
replaceNode = root;
} else {
// 替代节点为父节点的右子节点 和上面对称操作
RBNode<T> brother = parent.getLeft();
if (null != brother && RED == brother.isColor()) {
brother.setColor(BLACK);
parent.setColor(RED);
rightRotate(parent);
brother = parent.getLeft();
}
if (null == brother || (isBlack(brother.getLeft()) && isBlack(brother.getRight()))) {
if (null != brother) {
brother.setColor(RED);
}
replaceNode = parent;
parent = parent.getParent();
continue;
}
if (isRed(brother.getRight())) {
brother.setColor(BLACK);
brother.getRight().setColor(BLACK);
rightRotate(brother);
brother = brother.getParent();
}
brother.setColor(parent.isColor());
parent.setColor(BLACK);
brother.getLeft().setColor(BLACK);
rightRotate(parent);
replaceNode = root;
}
}
if (replaceNode != null) {
replaceNode.setColor(BLACK);
}
}
public RBNode<T> search(T key) {
return search(root, key);
}
/**
* 查找"红黑树x"中键值为key的节点(递归实现)
*
* @param node node
* @param key key
* @return RBNode
*/
private RBNode<T> search(RBNode<T> node, T key) {
if (null == node) {
return null;
}
int flag = key.compareTo(node.getKey());
if (flag < 0) {
return search(node.getLeft(), key);
} else if (flag > 0) {
return search(node.getRight(), key);
} else {
return node;
}
}
/**
* 中序遍历
*
* @param root root
*/
public void infix(RBNode<T> root) {
if (null == root) {
return;
}
if (null != root.getLeft()) {
infix(root.getLeft());
}
System.out.println(root);
if (null != root.getRight()) {
infix(root.getRight());
}
}
/**
* 对目标节点进行左旋操作
*
* @param targetNode 目标节点
*/
private void leftRotate(RBNode<T> targetNode) {
// 目标节点的右子节点,下面称右儿子
RBNode<T> targetRightSon = targetNode.getRight();
// 将目标节点的右子节点设置为右儿子的左子节点
targetNode.setRight(targetRightSon.getLeft());
// 如果右儿子的左子节点不为空,则需要将它的父节点设置为目标节点
if (null != targetRightSon.getLeft()) {
targetRightSon.getLeft().setParent(targetNode);
}
// 将右儿子的父节点设为目标节点的父节点
targetRightSon.setParent(targetNode.getParent());
// 如果目标节点的父节点为空,说明目标节点是父节点,左旋后父节点应该变为右儿子
if (null == targetNode.getParent()) {
root = targetRightSon;
} else {
// 如果目标节点不是父节点,需要判断目标节点为其父节点的左子节点还是右子节点,将右儿子替换进去
if (targetNode.getParent().getLeft() == targetNode) {
targetNode.getParent().setLeft(targetRightSon);
} else {
targetNode.getParent().setRight(targetRightSon);
}
}
// 将右儿子的左子节点设置为目标节点
targetRightSon.setLeft(targetNode);
// 将目标节点的父节点设置为右儿子
targetNode.setParent(targetRightSon);
}
/**
* 对目标节点进行右旋操作
*
* @param targetNode 目标节点
*/
private void rightRotate(RBNode<T> targetNode) {
// 目标节点的左子节点,下面称左儿子
RBNode<T> targetLeftSon = targetNode.getLeft();
// 将目标节点的左子节点设置为左儿子的右子节点
targetNode.setLeft(targetLeftSon.getRight());
// 如果左儿子的右节点不为空,则需要将它的父节点设置为目标节点
if (null != targetLeftSon.getRight()) {
targetLeftSon.getRight().setParent(targetNode);
}
// 将左儿子的父节点设置为目标节点的父节点
targetLeftSon.setParent(targetNode.getParent());
// 如果目标节点的父节点为空,说明目标节点是根节点,右旋后左二子应该变为父节点
if (null == targetNode.getParent()) {
root = targetLeftSon;
} else {
// 如果目标节点不是父节点,需要判断目标节点为其父节点的左子节点还是右子节点,将右儿子替换进去
if (targetNode.getParent().getLeft() == targetNode) {
targetNode.getParent().setLeft(targetLeftSon);
} else {
targetNode.getParent().setRight(targetLeftSon);
}
}
// 将左儿子的右子节点设置为目标节点
targetLeftSon.setRight(targetNode);
// 将目标节点的父节点设置为左儿子
targetNode.setParent(targetLeftSon);
}
/**
* 当前节点颜色是否为黑色
*/
private boolean isBlack(RBNode<T> node) {
if (node == null) {
return true;
}
return node.isColor() == BLACK;
}
/**
* 当前节点颜色是否为红色
*/
private boolean isRed(RBNode<T> node) {
if (node == null) {
return false;
}
return node.isColor() == RED;
}
@Data
public static class RBNode<T extends Comparable<T>> {
// 颜色
boolean color;
// 值
T key;
// 左子节点
RBNode<T> left;
// 右子节点
RBNode<T> right;
// 父节点
RBNode<T> parent;
public RBNode(boolean color, T key, RBNode<T> left, RBNode<T> right, RBNode<T> parent) {
this.color = color;
this.key = key;
this.left = left;
this.right = right;
this.parent = parent;
}
@Override
public String toString() {
String colorText = color ? "黑色" : "红色";
return "RBNode{" +
"color=" + colorText +
", key=" + key +
", left=" + (left == null ? "" : left.getKey()) +
", right=" + (right == null ? "" : right.getKey()) +
", parent=" + (parent == null ? "" : parent.getKey()) +
'}';
}
}
}