红黑树的性质
/*
1、每个节点是红色的或者黑色的
2、根节点是黑的
3、每个叶子节点是黑的
4、如果一个节点是红的,则它的两个儿子是黑的
5、对每个节点,从该节点到其子孙节点所有路径上的包含相同数目的黑节点
*/
//红黑树与平衡二叉树的区别与优点
// 红黑树黑高保证了平衡 而平衡二叉树在实现上每个节点需要记录当前节点的高度 每次插入都需要判断高度差 比较麻烦
#include<iostream>
#define RED 0
#define BLACK 1
//红黑树与平衡二叉树的区别与优点
// 红黑树黑高保证了平衡 而平衡二叉树在实现上每个节点需要记录当前节点的高度 每次插入都需要判断高度差 比较麻烦
template<typename K, typename V>
class RBtree;
template<typename K, typename V>
class RBtreeNode{
public:
RBtreeNode() = default;
RBtreeNode(K key, V value):lch(nullptr), rch(nullptr), parent(nullptr), color(RED){
kv.first = key;
kv.second = value;
}
RBtreeNode<K, V>* lch;
RBtreeNode<K, V>* rch;
RBtreeNode<K, V>* parent;
int color;
std::pair<K, V> kv;
};
template<typename K, typename V>
class RBtree{
public:
RBtree() {
leaf = new RBtreeNode<K, V>;
leaf->color = BLACK;
root = this->leaf;
}
//中序遍历输出该树内容
void Print(RBtreeNode<K,V>* node){
if(node != leaf){
Print(node->lch);
std::cout<< "key = " << node->kv.first << "\tvalue = " << node->kv.second << std::endl;
Print(node->rch);
}
}
private:
//左旋
void left_rotate(RBtreeNode<K,V>* x){
//对x进行左旋
//如果y不是叶子节点
RBtreeNode<K, V>* y = x->rch;
//第一次
//1.1
x->rch = y->lch;
//1.2
if(y->lch != this->leaf){
y->lch->parent = x;
}
//第二次
y->parent = x->parent;
//x本身没有父节点 那么y成为新的根节点
if(x->parent==this->leaf){
this->root = y;
}
//x有父节点
//并且x是父亲的左孩子
else if(x == x->parent->lch){
x->parent->lch = y;
}//x是父亲的右孩子
else if(x == x->parent->rch){
x->parent->rch = y;
}
//第三次
y->lch = x; //3.1
x->parent = y; //3.2
}
//右旋
void right_rotate(RBtreeNode<K,V>* y){
RBtreeNode<K,V>* x = y->lch;
//1
y->lch = x->rch;
if(x->rch != this->leaf){
x->rch->parent = y;
}
//2
x->parent = y->parent;
if(y->parent==this->leaf){
this->root = x;
}
else if(y == y->parent->lch){
y->parent->lch = x;
}
else if(y == y->parent->rch){
y->parent->rch = x;
}
//3
x->rch = y;
y->parent = x;
}
//记住一下几点:
//1、插入调整之前,该树是一颗红黑树
//2、插入节点是红色 这样不会影响黑高的属性
//调整操作
void adjust(RBtreeNode<K, V>* node){
//如果插入节点的父节点是红色 递归调整
//如果父亲节点是红色 那么祖父节点肯定是黑色
while(node->parent->color == RED){
//如果父节点是祖父节点的左子树
if(node->parent == node->parent->parent->lch){
RBtreeNode<K,V>* uncle = node->parent->parent->rch;
//如果叔叔节点是红色 说明叔叔节点存在 不用旋转 只需要变色
if(uncle->color == RED){
node->parent->color = BLACK;
uncle->color = BLACK;
node->parent->parent->color = RED;
//递归
node = node->parent->parent;
}
//叔叔节点是黑色 说明叔叔节点根本就不存在 是叶子节点
//不然没法保持红黑树的平衡 这时候需要旋转加变色
else{
//如果该节点是父节点的右节点 需要先左旋
if(node == node->parent->rch){
node = node->parent;
this->left_rotate(node);
}
node->parent->color = BLACK;
node->parent->parent->color = RED;
this->right_rotate(node->parent);
}
}
//第二种情况
else if(node->parent == node->parent->parent->rch){
RBtreeNode<K, V>* uncle = node->parent->parent->lch;
//只需要变色
if(uncle->color == RED){
node->parent->color = BLACK;
uncle->color = BLACK;
node->parent->parent->color = RED;
node = node->parent->parent;
}
//需要变色+旋转
else{
//这种情况需要先将node的父节点右旋一次
//如果进入了if 经过右旋后 node也是最底下的节点
if(node==node->parent->lch){
node = node->parent;
this->right_rotate(node);
}
//不管进不进if node始终都表示最下面的一个节点
node->parent->color = BLACK;
node->parent->parent->color = RED;
this->left_rotate(node->parent->parent);
}
}
}
//每次将根节点变为黑色
this->root->color = BLACK;
}
public:
//插入操作
void insert(RBtreeNode<K, V>* node){
//利用z节点遍历
RBtreeNode<K, V>* z = this->root;
//利用一个节点始终保存z节点的父节点
RBtreeNode<K, V>* pre = this->leaf;
while(z != this->leaf){
pre = z;
if(node->kv.first < z->kv.first){
z = z->lch;
}
else if(node->kv.first > z->kv.first){
z = z->rch;
}
else if(node->kv.first == z->kv.first){
return;
}
}
node->parent = pre;
//如果这棵数是空的 root一开始就等于叶子节点 说明pre根本没变化
if(pre == this->leaf){
this->root = node;
}
else if(node->kv.first < pre->kv.first){
pre->lch = node;
}
else if(node->kv.first > pre->kv.first){
pre->rch = node;
}
node->lch = this->leaf;
node->rch = this->leaf;
//默认添加红色 不影响黑高的属性
node->color = RED;
adjust(node);
}
public:
RBtreeNode<K,V>* root;
//如果一个节点左右节点都是这片区域 表明这是叶子节点
RBtreeNode<K,V>* leaf;
};
int main(){
RBtree<int, int> t;
RBtreeNode<int, int>* n1 = new RBtreeNode<int, int>(60, 10);
RBtreeNode<int, int>* n2 = new RBtreeNode<int, int>(50, 20);
RBtreeNode<int, int>* n3 = new RBtreeNode<int, int>(70, 52);
RBtreeNode<int, int>* n4 = new RBtreeNode<int, int>(90, 224);
RBtreeNode<int, int>* n5 = new RBtreeNode<int, int>(20, 2320);
RBtreeNode<int, int>* n6 = new RBtreeNode<int, int>(130, 2320);
RBtreeNode<int, int>* n7 = new RBtreeNode<int, int>(180, 13124);
t.insert(n1);
t.insert(n2);
t.insert(n3);
t.insert(n4);
t.insert(n5);
t.insert(n6);
t.insert(n7);
t.Print(t.root);
return 0;
}
总结一下:
这里目前只实现了插入功能,删除功能还没实现
打印是按中序遍历输出 由于红黑树也有平衡二叉树的性质 所以中序遍历是递增序列
红黑树的插入注意的几个地方:
1、每次插入的节点 默认是红色 因为红黑树黑高的特性 插入节点为红色 不会改变这一特性
2、当插入的节点父节点为红色时 这时候需要循环的调整
调整需要根据情况判断
首先是判断当前插入节点的父节点是祖父节点的左节点还是右节点
其次需要判断叔父节点是黑色还是红色
如果叔父节点是红色 说明叔父节点是存在的 这样插入节点过后并不会影响平衡的特性, 只需要进行变色操作就行, 变色操作就是将父节点与叔父节点变为黑色 然后将祖父节点变为红色 之后递归的检查
如果叔父节点是黑色,(注意此时 父节点是红色,祖父节点是黑色 这个是已知的) 由于在插入节点前,同样是一颗红黑树, 所以叔父节点这时候只可能是叶子节点, 不然不会满足红黑树的特性(黑高), 所以这时候需要进行旋转操作与变色操作
旋转操作同样需要根据情况判断,有旋转一次与两次的区别。 最后是变色。