1.红黑树的概念及性质
红黑树的概念
红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出俩倍,因而是接近平衡的。
那么红黑树是如何确保没有一条路径会比其他路径长出两倍的呢?要弄清楚这点首先要引入红黑树的性质:
1. 每个结点不是红色就是黑色
2. 根节点是黑色的
3. 如果一个节点是红色的,则它的两个孩子结点是黑色的(没有连续的红结点)
4. 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均 包含相同数目的黑色结点
5. 每个叶子结点都是黑色的(此处的叶子结点指的是空结点)
由红黑树的性质可知可能出现的最短的路径是全黑,而最长的路径是黑红交替,因此没有一条路径会比其他路径长出两倍。
2.红黑树的部分实现
2.1首先要定义红黑树结点:
enum color
{
RED,
BLACK
};
template<typename K,typename V>
struct RB_Node
{
RB_Node<K, V>* _left;
RB_Node<K, V>* _right;
RB_Node<K, V>* _parent; //父节点指针
pair<K, V> _kv; //节点的值域
color _col; //颜色
RB_Node(const pair<K, V>& kv) :
_left(nullptr),
_right(nullptr),
_parent(nullptr),
_kv(kv),
_col(RED) //初始颜色为红色
{}
};
2.2红黑树的插入
插入时先按照搜索二叉树的插入逻辑插入节点,后边在分情况进行变色/旋转处理
注意我们插入的节点默认是红色的,因为根据性质来说我们需要保证每条路径的黑色节点个数一样,如果我们插入的是黑色节点那么就需要控制其他所有路径下的黑色节点数量+1,这是很难做到的。
二叉搜索树的插入逻辑:
bool Insert(const pair<K, V>& kv)
{
if (_root == nullptr)
{
_root = new node(kv);
_root->_col = BLACK;
return true;
}
//搜索二叉树逻辑
node* parent = nullptr;
node* cur = _root;
while (cur)
{
if (kv.first > cur->_kv.first)
{
parent = cur;
cur = cur->_right;
}
else if (kv.first < cur->_kv.first)
{
parent = cur;
cur = cur->_left;
}
else
{
return false;
}
}
cur = new node(kv);
if (cur->_kv.first > parent->_kv.first)
{
parent->_right = cur;
}
else
{
parent->_left = cur;
}
cur->_parent = parent;
下面来讨论变色和旋转逻辑:
假设我们插入节点的父节点是黑色,实际上我们不需要做任何调整,但是如果父节点是红色的话就违反了红黑树的性质需要做出调整,(我们调整应该是一直向上的直到cur节点是根节点或者父节点为黑色)如下图这是可能出现的一种情况。
情况一当叔叔节点为红色时:
具体代码(注意根节点只能是黑色)
while (parent && (parent->_col == RED))
{
node* grandparent = parent->_parent;
node* uncle = nullptr;
if (grandparent->_left == parent)
{
uncle = grandparent->_right;
if (uncle && uncle->_col == RED)
{
parent->_col = uncle->_col = BLACK;
grandparent->_col = RED;
//继续向上处理
cur = grandparent;
parent = grandparent->_parent;
}
情况二当叔叔不存在或者为黑色时(需要旋转加变色):
具体旋转逻辑可以去看我前一篇博客http://t.csdnimg.cn/6ae1v
情况二又分为两种情况:
情况2.1(p为g的左孩子,cur为p的左孩子,则进行右单旋转;相反,p为g的右孩子,cur为p的右孩子,则进行左单旋转):
情况2.2(cur为红,p为红,g为黑,u不存在/u存在且为黑p为g的左孩子,cur为p的右孩子则针对p做左单旋转;相反,p为g的右孩子,cur为p的左孩子,则针对p做右单旋转则转换成了情况2.1):
具体代码(上边全是grandparent->_left == parent,但是grandparent->_right== parent)处理的逻辑是一样或对称的,可以根据代码自行理解:
while (parent && (parent->_col == RED))
{
node* grandparent = parent->_parent;
node* uncle = nullptr;
if (grandparent->_left == parent)
{
uncle = grandparent->_right;
if (uncle && uncle->_col == RED)
{
parent->_col = uncle->_col = BLACK;
grandparent->_col = RED;
//继续向上处理
cur = grandparent;
parent = grandparent->_parent;
}
else //叔叔不存在或者叔叔为黑色 旋转
{
if (parent->_left == cur)
{
RotateR(grandparent);
parent->_col = BLACK;
grandparent->_col = RED;
}
else
{
RotateL(parent);
RotateR(grandparent);
cur->_col = BLACK;
grandparent->_col = RED;
}
}
break;
}
else
{
uncle = grandparent->_left;
if (uncle && uncle->_col == RED)
{
parent->_col = uncle->_col = BLACK;
grandparent->_col = RED;
//继续向上处理
cur = grandparent;
parent = grandparent->_parent;
}
else //叔叔不存在或者叔叔为黑色 旋转
{
if (parent->_right == cur)
{
RotateL(grandparent);
parent->_col = BLACK;
grandparent->_col = RED;
}
else
{
RotateR(parent);
RotateL(grandparent);
cur->_col = BLACK;
grandparent->_col = RED;
}
}
break;
}
}
3.源码
#pragma once
#include <iostream>
using namespace std;
enum color
{
RED,
BLACK
};
template<typename K,typename V>
struct RB_Node
{
RB_Node<K, V>* _left;
RB_Node<K, V>* _right;
RB_Node<K, V>* _parent;
pair<K, V> _kv;
color _col;
RB_Node(const pair<K, V>& kv) :
_left(nullptr),
_right(nullptr),
_parent(nullptr),
_kv(kv),
_col(RED)
{}
};
template<typename K, typename V>
class RBTree
{
typedef RB_Node<K, V> node;
public:
bool Insert(const pair<K, V>& kv)
{
if (_root == nullptr)
{
_root = new node(kv);
_root->_col = BLACK;
return true;
}
//搜索二叉树逻辑
node* parent = nullptr;
node* cur = _root;
while (cur)
{
if (kv.first > cur->_kv.first)
{
parent = cur;
cur = cur->_right;
}
else if (kv.first < cur->_kv.first)
{
parent = cur;
cur = cur->_left;
}
else
{
return false;
}
}
cur = new node(kv);
if (cur->_kv.first > parent->_kv.first)
{
parent->_right = cur;
}
else
{
parent->_left = cur;
}
cur->_parent = parent;
while (parent && (parent->_col == RED))
{
node* grandparent = parent->_parent;
node* uncle = nullptr;
if (grandparent->_left == parent)
{
uncle = grandparent->_right;
if (uncle && uncle->_col == RED)
{
parent->_col = uncle->_col = BLACK;
grandparent->_col = RED;
//继续向上处理
cur = grandparent;
parent = grandparent->_parent;
}
else //叔叔不存在或者叔叔为黑色 旋转
{
if (parent->_left == cur)
{
RotateR(grandparent);
parent->_col = BLACK;
grandparent->_col = RED;
}
else
{
RotateL(parent);
RotateR(grandparent);
cur->_col = BLACK;
grandparent->_col = RED;
}
}
break;
}
else
{
uncle = grandparent->_left;
if (uncle && uncle->_col == RED)
{
parent->_col = uncle->_col = BLACK;
grandparent->_col = RED;
//继续向上处理
cur = grandparent;
parent = grandparent->_parent;
}
else //叔叔不存在或者叔叔为黑色 旋转
{
if (parent->_right == cur)
{
RotateL(grandparent);
parent->_col = BLACK;
grandparent->_col = RED;
}
else
{
RotateR(parent);
RotateL(grandparent);
cur->_col = BLACK;
grandparent->_col = RED;
}
}
break;
}
}
_root->_col = BLACK;
return true;
}
void RotateL(node* parent)
{
node* subr = parent->_right;
node* subrl = subr->_left;
node* ppnode = parent->_parent;
parent->_right = subrl;
parent->_parent = subr;
subr->_left = parent;
if (subrl)
subrl->_parent = parent;
if (parent == _root)
{
_root = subr;
subr->_parent = nullptr;
}
else
{
if (parent == ppnode->_left)
{
ppnode->_left = subr;
}
else
{
ppnode->_right = subr;
}
subr->_parent = ppnode;
}
}
void RotateR(node* parent)
{
node* subl = parent->_left;
node* sublr = subl->_right;
parent->_left = sublr;
if (sublr)
sublr->_parent = parent;
subl->_right = parent;
node* ppnode = parent->_parent;
parent->_parent = subl;
if (parent == _root)
{
_root = subl;
subl->_parent = nullptr;
}
else
{
if (ppnode->_left == parent)
{
ppnode->_left = subl;
}
else
{
ppnode->_right = subl;
}
subl->_parent = ppnode;
}
}
private:
void _InOrder(node* root)
{
if (root == nullptr)
return;
_InOrder(root->_left);
cout << root->_kv.first << endl;
_InOrder(root->_right);
}
public:
void InOrder()
{
_InOrder(_root);
}
private:
node* _root = nullptr;
};
4.红黑树与AVL树的对比
红黑树和AVL树都是高效的平衡二叉树,增删改查的时间复杂度都是O($log_2 N$),红黑树不追求绝对平衡,其只需保证最长路径不超过最短路径的2倍,相对而言,降低了插入和旋转的次数,所以在经常进行增删的结构中性能比AVL树更优,而且红黑树实现比较简单,所以实际运用中红黑树更多。