一、红黑树的性质。
红黑树的原型是一棵二叉搜索树,只不过它在每个结点上增加一个属性来作为该结点的颜色,颜色可以是红色或者黑色。树中的每个结点包含五个属性:key、color、left、right、parent。如果一个结点没有父结点或者子结点,则相应的指针值为NULL。我们可以把这些值为NULL的指针看作是指向树的外部结点(又叫叶结点),而把带有关键字的结点看作是树的内部结点。
红黑树的定义为满足下面5条性质的二叉搜索树:
1.每个结点是红色或者黑色的;
2.根结点是黑色的;
3.每个叶结点(NULL)是黑色的;
4.如果一个结点是红色的,则它的两个子结点是黑色的;
5.对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点。
通过红黑树的性质可以看出,红黑树确保没有一条路径会比其它任何一条路径长出2倍,因此,红黑树是近似“平衡“的(平衡二叉树:它是一棵空树或者它的左右两个子树的高度差的绝对值不超过1,并且它的两个左右子树都是一棵平衡二叉树)。
黑高:从某个结点X出发(不含该结点)到达一个叶结点的任意一条简单路径上的黑结点个数称为该结点的黑高,计作bh(x)。红黑树的黑高就是跟结点的黑高。叶结点的黑高为0。
二、红黑树的实现。
#include <iostream>
using namespace std;
//颜色枚举;
enum _color
{
_None,
_Red,
_Black
};
//结点结构;
struct tNode
{
int iKey;
int iColor;
tNode* pParent;
tNode* pLeft;
tNode* pRight;
tNode():
iKey(NULL),
iColor(_None),
pParent(nullptr),
pLeft(nullptr),
pRight(nullptr)
{
}
};
上面是颜色枚举和结点结构的实现,初始化中全部赋为空。
为了处理红黑树的边界条件,我们使用一个哨兵点来代表NULL(根节点的父结点以及所有叶节点),这个哨兵有着和内部结点一样的结构,且颜色值为黑色,其他值为NULL(因为除了哨兵的颜色之外,哨兵的其他属性取值并不重要,索性设为空)。我们可以为树内的每个NULL新增一个哨兵,但是为了节省空间,我们可以只用一个哨兵代替树内所有的NULL。
于是便有了下面的红黑树初始结构:
class RBTree
{
public:
RBTree():
m_pRoot(nullptr),
m_iCount(0)
{
m_pSentryNULL = new tNode();
m_pSentryNULL->iColor = _Black; //哨兵的颜色为黑色;
m_pRoot = m_pSentryNULL;
}
~RBTree()
{
delete m_pSentryNULL;
m_pSentryNULL = nullptr;
}
private:
//根结点;
tNode* m_pRoot;
//哨兵;
tNode* m_pSentryNULL;
//元素数量;
int m_iCount;
public:
//获得元素数目;
const int& size()
{
return m_iCount;
}
};
旋转:
一个有n个内部结点的红黑树的高度至多为2log(n+1)(证明省略),所以对含有n个内部结点的红黑树进行二叉搜索树的插入和删除操作的时间复杂度也是O(log(n))。由于这两个操作对树做了修改,结果可能违反红黑性质,为了维护红黑性质,必须再改变某些结点的颜色以及指针结构。其中指针结构的修改是通过旋转操作来完成的。
旋转分为左旋和右旋:
前后两个图中的数据按中序遍历均为a,x,b,y,c,可见旋转操作并没有破坏二叉搜索树的性质,并且只有指针改变,其他所有属性均没有改变。
下面附上完整代码:
class RBTree
{
public:
RBTree():
m_pRoot(nullptr),
m_iCount(0)
{
m_pSentryNULL = new tNode();
m_pSentryNULL->iColor = _Black; //哨兵的颜色为黑色;
m_pRoot = m_pSentryNULL;
}
~RBTree()
{
delete m_pSentryNULL;
m_pSentryNULL = nullptr;
}
private:
//根结点;
tNode* m_pRoot;
//哨兵;
tNode* m_pSentryNULL;
//元素数量;
int m_iCount;
private:
//左旋;
void left_Rotate(tNode* pNode)
{
if(pNode == nullptr||pNode == m_pSentryNULL||pNode->pRight == m_pSentryNULL)
{
return;
}
pNode->pRight->pParent = pNode->pParent;
if (pNode->pParent == m_pSentryNULL)
{
m_pRoot = pNode->pRight;
}
else
{
if (pNode == pNode->pParent->pLeft)
{
pNode->pParent->pLeft = pNode->pRight;
}
else
{
pNode->pParent->pRight = pNode->pRight;
}
}
pNode->pParent = pNode->pRight;
if (pNode->pParent->pLeft == m_pSentryNULL)
{
pNode->pRight = m_pSentryNULL;
}
else
{
pNode->pRight->pLeft->pParent = pNode;
pNode->pRight = pNode->pParent->pLeft;
}
pNode->pParent->pLeft = pNode;
}
//右旋;
void right_Rotate(tNode* pNode)
{
if(pNode == nullptr||pNode == m_pSentryNULL||pNode->pLeft == m_pSentryNULL)
{
return;
}
pNode->pLeft->pParent = pNode->pParent;
if (pNode->pParent == m_pSentryNULL)
{
m_pRoot = pNode->pLeft;
}
else
{
if (pNode == pNode->pParent->pLeft)
{
pNode->pParent->pLeft = pNode->pLeft;
}
else
{
pNode->pParent->pRight = pNode->pLeft;
}
}
pNode->pParent = pNode->pLeft;
if (pNode->pParent->pRight == m_pSentryNULL)
{
pNode->pLeft = m_pSentryNULL;
}
else
{
pNode->pLeft->pRight->pParent = pNode;
pNode->pLeft = pNode->pParent->pRight;
}
pNode->pParent->pRight = pNode;
}
public:
//获得元素数目;
const int& size()
{
return m_iCount;
}
};
下个章节我们讲红黑树的插入操作。