-
每个节点要么是黑色,要么是红色。
-
根节点是黑色。
-
每个叶子节点(NIL)是黑色。
-
每个红色结点的两个子结点一定都是黑色。
-
任意一结点到每个叶子结点的路径都包含数量相同的黑结点。
红黑树能自平衡,它靠的是什么?三种操作:左旋、右旋和变色
1 旋转操作
1.1 概念讲解
左旋:以某个节点作为旋转点,其右子节点变为旋转节点的父节点,右子节点的左子节点变为旋转节点的右子节点,左子节点保持不变。
右旋:以某!个节点作为旋转点,其左子节点变为旋转节点的父节点,左子节点的右子节点变为旋转节点的左子节点,右子节点保持不变。
1.2 代码实现
先进行类结构定义
package com.bobo.util.treemap;
public class BRTree {
private static final boolean RED = false;
private static final boolean BLACK = true;
private RBNode root;
public RBNode getRoot() {
return root;
}
public void setRoot(RBNode root) {
this.root = root;
}
/**
* 表示 节点
* @param <K>
* @param <V>
*/
static class RBNode<K extends Comparable<K>,V>{
// 节点是双向的
private RBNode parent;
private RBNode left;
private RBNode right;
private boolean color;
private K key;
private V value;
public RBNode() {
}
public RBNode(RBNode parent, RBNode left, RBNode right, boolean color, K key, V value) {
this.parent = parent;
this.left = left;
this.right = right;
this.color = color;
this.key = key;
this.value = value;
}
public RBNode getParent() {
return parent;
}
public void setParent(RBNode parent) {
this.parent = parent;
}
public RBNode getLeft() {
return left;
}
public void setLeft(RBNode left) {
this.left = left;
}
public RBNode getRight() {
return right;
}
public void setRight(RBNode right) {
this.right = right;
}
public boolean isColor() {
return color;
}
public void setColor(boolean color) {
this.color = color;
}
public K getKey() {
return key;
}
public void setKey(K key) {
this.key = key;
}
public V getValue() {
return value;
}
public void setValue(V value) {
this.value = value;
}
}
}
复制代码
左旋代码实现
/**
* 围绕p左旋
* p pr(r)
* / | / \
* pl pr(r) => p rr
* / \ / \
* rl rr pl rl
*
* 左旋的时候
* p-pl 和 pr-rr的关系不变
* pr-rl 要变为 p-rl
* 也就是 rl要变为 p的右子节点
* 同时 p要成为 rl 的父节点
* 还有就是要判断 p 是否有父节点
* 如果没有
* r 变为 root 节点
* 如果有
* r.parent = p.parent
* 还要设置 r为 p.parent 的子节点(可能左也可能右)
* 如果 p.parent.left == p
* p.parent.left = r;
* 否则
* p.parent.right = r;
* 最后
* p.parent = r;
* r.left = p;
* @param p
*/
private void leftRotate(RBNode p){
if(p != null){
RBNode r = p.right;
// 1.设置 pr-rl 要变为 p-rl
// 把rl设置到p的右子节点
p.right = r.left;
if(r.left != null){
// 设置rl的父节点为p
r.left.parent = p;
}
// 2.判断p的父节点情况
r.parent = p.parent; // 不管 p是否有父节点,都把这个父节点设置为 r的父节点
if(p.parent == null){
root = r; // p没有父节点 则r为root节点
}else if(p.parent.left == p){
p.parent.left = r; // 如果p为 p.parent的左子节点 则 r 也为 p.parent的左子节点
}else{
p.parent.right = r; // 反之设置 r 为 p.parent的右子节点
}
// 最后 设置 p 为 r 的左子节点
r.left = p;
p.parent = r;
}
}
复制代码
右旋实现:
/**
* 围绕p右旋
* @param p
*/
public void rightRotate(RBNode p){
if(p != null){
RBNode r = p.left;
p.left = r.right;
if(r.right != null){
r.right.parent = p;
}
r.parent = p.parent;
if(p.parent == null){
root = r;
}else if(p.parent.left == p){
p.parent.left = r;
}else{
p.parent.right = r;
}
r.right = p;
p.parent = r;
}
}
复制代码
2 新增节点
2-3-4树中结点添加需要遵守以下规则:
-
插入都是向最下面一层插入
-
升元:将插入结点由 2-结点升级成 3-结点,或由 3-结点升级成 4-结点;
-
向 4-结点插入元素后,需要将中间元素提到父结点升元,原结点变成两个 2-结点,再把元素插入2-结点中,如果父结点也是 4-结点,则递归向上层升元,至到根结点后将树高加1;
而将这些规则对应到红黑树里,就是:
-
新插入的结点颜色为 红色 ,这样才可能不会对红黑树的高度产生影响。
-
2-结点对应红黑树中的单个黑色结点,插入时直接成功(对应 2-结点升元)。
-
3-结点对应红黑树中的 黑+红 子树,插入后将其修复成 红+黑+红 子树(对应 3-结点升元);
-
4-结点对应红黑树中的 红+黑+红 子树,插入后将其修复成 红色祖父+黑色父叔+红色孩子 子树,然后再把祖父结点当成新插入的红色结点递归向上层修复,直至修复成功或遇到 root 结点;
公式:红黑树+新增一个节点(红色)**=**对等的2-3-4树+新增一个节点
2.1 新增节点示例
我们通过新增2-3-4树的过程来映射对应的红黑树的节点新增
2-3-4树的新增(全部在叶子节点完成)
1.新增一个节点,2 节点
2.新增一个节点,与2节点合并,直接合并
3.新增一个节点,与3节点合并,直接合并
插入的值的位置会有3种情况
对应的红黑树为:
4.新增一个节点,与4节点合并,此时需要分裂、
插入值的位置可能是
对应的红黑树的结构为
2.2 新增代码实现
红黑树的新增规则我们理清楚了,接下来就可以通过Java代码来具体的实现了。
先实现插入节点,这就是一个普通的二叉树的插入
/**
* 新增节点
* @param key
* @param value
*/
public void put(K key , V value){
RBNode t = this.root;
if(t == null){
// 说明之前没有元素,现在插入的元素是第一个
root = new RBNode<>(key , value == null ? key : value,null);
return ;
}
int cmp ;
// 寻找插入位置
// 定义一个双亲指针
RBNode parent;
if(key == null){
throw new NullPointerException();
}
// 沿着跟节点找插入位置
do{
### 最后
**如果觉得本文对你有帮助的话,不妨给我点个赞,关注一下吧!**
**[资料免费领取方式:点击这里](https://gitee.com/vip204888/java-p7)**
![](https://img-blog.csdnimg.cn/img_convert/daf8cfc84fdb5227b0b1618c391814ae.png)
id put(K key , V value){
RBNode t = this.root;
if(t == null){
// 说明之前没有元素,现在插入的元素是第一个
root = new RBNode<>(key , value == null ? key : value,null);
return ;
}
int cmp ;
// 寻找插入位置
// 定义一个双亲指针
RBNode parent;
if(key == null){
throw new NullPointerException();
}
// 沿着跟节点找插入位置
do{
### 最后
**如果觉得本文对你有帮助的话,不妨给我点个赞,关注一下吧!**
**[资料免费领取方式:点击这里](https://gitee.com/vip204888/java-p7)**
[外链图片转存中...(img-uOQgvt2D-1628432562796)]
![](https://img-blog.csdnimg.cn/img_convert/6e6463247e74e06477dd5eb773261f4b.png)