红黑树
是一颗二叉搜索树,它在每个节点上增加了一个存储位来表示节点的颜色,可以是red或black。通过对任何一条从根节点到叶子节点的简单路径上的颜色来约束,红黑树保证了最长路径不超过最短路经的两倍,因此近似于平衡。
红黑树的规则:
1、每个节点不是红色就是黑色的。
2、根结点是黑色的。
3、如果一个节点是红色的,则它的两个子结点是黑色的。即每条路径上不能存在两个连续的红节点。
4、对每个节点,从该节点到其他节点的简单路径上,均包含相同数目的黑色节点。
红黑树节点RBTreeNode的实现,利用三叉链(left、right、parent)、key、value及颜色col。
enum colour
{
RED,
BLACK,
};
template<class K, class V>
struct RBTreeNode
{
RBTreeNode<K, V>* _left;
RBTreeNode<K, V>* _right;
RBTreeNode<K, V>* _parent;
K _key;
V _value;
colour _col;
RBTreeNode(const K& key = K(), const V& value = V())
:_left(NULL)
, _right(NULL)
, _parent(NULL)
, _key(key)
, _value(value)
, _col(RED)//初始化插入节点的颜色为红色,不影响黑节点个数
{}
};
一、红黑树的插入
红黑树的插入类似于二叉搜索树,但是每次插入后都到要注意是否满足红黑树的规则,特别是规则3和规则4。如果不满足就需要调整树的结构,下面对插入节点时分成以下几种情况:
注:cur(插入节点)parent(cur的父亲节点)grandfather(parent的父亲节点)uncle(父亲的兄弟节点)
1、根节点root为空,直接插入新节点并给root,设置根节点的颜色为BLACK。
2、根节点root不为空,找到插入节点的位置并插入节点cur。cur节点是红色,若parent是红节点,则需要进行调整。
存在以下三种情况:
情况一:cur为红,parent为红,grandfather为黑,uncle存在且为红。
调整方案,如下如所示:
情况二:cur为红,parent为红,grandfather为黑,uncle不存在或者uncle为黑。
左单旋:parent为grantfather的右孩子,cur为parent的右孩子。
右单旋: parent为grantfather的左孩子,cur为parent的左孩子。
调整方案,下面以右单旋进行分析,如下图所示:
左单旋转类似右单旋转的实现过程。
情况三:cur为红,parent为红,grantfather为黑,uncle不存在或者uncle为黑。
左右单旋:parent为grantfather的右孩子,cur为parent的左孩子。
右左单旋:parent为grantfather的左孩子,cur为parent的右孩子。
调整方案,下面以做右单旋进行分析,如下图所示:
右左旋转的实现类似左单旋转的实现过程。
具体实现如下:
RBTree()
:_root(NULL)
{}
bool Insert(const K& key, const V& value)
{
//1、_root为空时,插入节点是根节点
if (_root == NULL)
{
_root = new Node(key, value);
_root->_col = BLACK;
return true;
}
Node* parent = NULL;
Node* cur = _root;
while (cur)//找到插入节点的位置
{
if (cur->_key > key)
{
parent = cur;
cur = cur->_left;
}
else if (cur->_key < key)
{
parent = cur;
cur = cur->_right;
}
else
return false;
}
cur = new Node(key, value);//插入节点
if (parent->_key > cur->_key)
{
parent->_left = cur;
cur->_parent = parent;
}
if (parent->_key < cur->_key)
{
parent->_right = cur;
cur->_parent = parent;
}
//2、出现两个连续红节点,进行调整
while (cur != _root && parent->_col == RED)//此条件说明存在parent节点,parent存在父亲节点
{
Node* grandfather = parent->_parent;
if (parent == grandfather->_left)
{
Node* uncle = grandfather->_right;
if (uncle && uncle->_col == RED)//情况一:uncle为RED
{
grandfather->_col = RED;
parent->_col = BLACK;
uncle->_col = BLACK;
}
else //情况二、三:uncle为BLACK或不存在(右单旋或左右单旋)
{//先考虑左右单旋,先进行左单旋,转化为情况二,再进行右单旋
if (cur == parent->_right)
{
_RotateL(parent);//左旋不需要变颜色
}
_RotateR(grandfather);
}
}
else
{
Node* grandfather = parent->_parent;
if (parent == grandfather->_right)
{
Node* uncle = grandfather->_left;
if (uncle && uncle->_col == RED)//情况一:uncle为RED
{
grandfather->_col = RED;
parent->_col = BLACK;
uncle->_col = BLACK;
}
else //情况二、三:uncle为BLACK或不存在(左单旋或右左单旋)
{
if (cur == parent->_left)
{
_RotateR(parent);//右旋不需要变颜色
}
_RotateL(grandfather);
}
}
}
cur = grandfather;
parent = cur->_parent;
_root->_col = BLACK;
}
}
void _RotateL(Node* parent)
{
Node* SubR = parent->_right;
Node* SubRL = SubR->_left;
parent->_right = SubRL;
if (SubRL)
SubRL->_parent = parent;
SubR->_parent = parent->_parent;
SubR->_left = parent;
parent->_parent = SubR;
//变色
SubR->_col = BLACK;//情况二中:parent变黑,grandfather变红
parent->_col = RED;
parent = SubR;
if (parent->_parent == NULL)
_root = parent;
else
{
Node* ppNode = parent->_parent;
if (ppNode->_key > parent->_key)
ppNode->_left = parent;
else
ppNode->_right = parent;
}
}
void _RotateR(Node* parent)
{
Node* SubL = parent->_left;
Node* SubLR = SubL->_right;
parent->_left = SubLR;
if (SubLR)
SubLR->_parent = parent;
SubL->_parent = parent->_parent;
SubL->_right = parent;
parent->_parent = SubL;
//变色
SubL->_col = BLACK;//情况二中:parent变黑,grandfather变红
parent->_col = RED;
parent = SubL;
if (parent->_parent == NULL)
_root = parent;
else
{
Node* ppNode = parent->_parent;
if (ppNode->_key > parent->_key)
ppNode->_left = parent;
else
ppNode->_right = parent;
}
}
二、红黑树的判断
1、根结点是否满足红黑树规则,是否为黑色。
2、每条路径的黑色节点相等。统计出一条路径的黑色节点的个数,然后与其他路径黑色节点个数进行比较。
3、不存在连续的红色节点,判断红色节点的父亲节点是否为红色。
具体实现如下:
bool Check()
{
if (_root->_col == RED)
return false;
int count = 0;//统计出一条路径的黑色节点的个数
int num = 0;//需要与count比较的其他路径黑色节点个数
Node* cur = _root;
while (cur)
{
if (cur->_col == BLACK)
count++;
cur = cur->_left;
}
return _Check(_root, count, num);
}
bool _Check(Node* root, int BlackNum, int CurBlackNum)
{
if (root == NULL)
return true;
if (root->_col == RED && root->_parent->_col == RED)//存在两个连续的红节点
return false;
if (root->_col == BLACK)//黑节点就CurBlackNum++
CurBlackNum++;
if (root->_left == NULL && root->_right == NULL)
{
if (CurBlackNum == BlackNum)
return true;
else//黑色节点不相等返回false
return false;
}
return _Check(root->_left, BlackNum, CurBlackNum)
&& _Check(root->_right, BlackNum, CurBlackNum);//进行左右递归
}
红黑树的效率:
1、最坏情况下,红黑树高度不超过2lgN
最坏的情况就是,红黑树中除了最左侧路径全部是由3-node节点组成,即红黑相间的路径长度是全黑路径长度的2倍。
2、红黑树的平均高度大约为lgN
红黑树的运用(高效的二叉搜索树)
红黑树这种数据结构应用十分广泛,在多种编程语言中被用作符号表的实现,如:
- Java中的java.util.TreeMap,java.util.TreeSet
- C++ STL中的:map,multimap,multiset
- .NET中的:SortedDictionary,SortedSet 等
红黑树和AVL树的比较
1、红黑树和AVL树都是高效的平衡二叉树,增删查改的时间复杂度都是O(lg(N))
2、红黑树的不追求完全平衡,保证最长路径不超过最短路径的2倍,相对而言,降低了旋转的要求,所以性能优于AVL树,所以实际运用中红黑树更多。红黑树是一种特殊的二叉查找树,他的查找方法也和二叉查找树一样,不需要做太多更改,但是由于红黑树比一般的二叉查找树具有更好的平衡,所以查找起来更快。
以上为个人学习的一些总结,如有纰漏,请多多指教。