AVL之外,另外一个颇具历史并且被广泛运用的平衡二叉搜索树是RB-tree(红黑树),所谓的红黑树其实也是一个二叉搜索树,但是他对二叉搜索树的每个结点是必须满足如下规则的。
1.每个结点不是红色就是黑色
2.根节点为黑色
3.如果结点为红色,其子节点必须为黑色(即不能有两个连续的红色结点,黑色结点可以连续)
4.任意结点至NULL(树尾端)的任何路径,所含的黑色结点数必须相同。
如下图就是一个红黑树(红黑树只不过实在二叉搜索树的基础上调整结点使其满足上述条件就可以了)
既然知道红黑树是在二叉搜索树插入完成后的基础上根据上述性质调整的,所以下面我就详细说一下该如何调整
首先根据规则4,我们可以得出来新插入的结点肯定是红色的,根据规则3可以知道新增结点的父结点必须为黑色(因为两个红色不能连在一起),所以当新插入的结点不符合这两个条件的时候就必须做出一些调整来使其符合这两个条件!插入主要分为了如下的几个情况
这里强调一点,当父结点是黑色的时候插入一个新节点肯定符合红黑树的特性,后面调整的停止条件也是根据这个停止的
情况1.插入结点有叔叔结点并且叔叔结点和父结点都是红色,将插入结点的uncle和parent改为黑色,将gparent改为红色,然后向上走继续判断(pParent=pgarent)(这里强调父结点是红色其实是废话,父结点肯定是红色的不然就不用调整了)
情况2:插入结点为其父结点的左子树(左左),并且其没有叔叔结点或者叔叔结点为黑色,需要进行右旋(和平衡二叉树的右旋一模一样),旋转完成以后将pParent的结点改成黑色,将pgarent的结点改成红色,就满足条件了
情况3:和情况2其他条件一样,只不过他是插入点在内测,则需要先左旋,然后改变pgarent和插入点的颜色,然后再右旋就可以了
上面的三种情况是发生在gparent左子节点身上的,所以对应还有三种情况发生在gparent的右子树身上。
给出左单旋和右单旋 的代码
因为红黑树和AVL树的旋转几乎一样,所以看不懂的先去看AVL树,之后再来看红黑树会容易的多。下面给出代码
1.红黑树结点的代码
enum Color
{
Red,
Black
};
template<class K,class V>
struct RBTreeNode
{
RBTreeNode<K, V>* _pLeft;
RBTreeNode<K, V>* _pRight;
RBTreeNode<K, V>* _pParent;
K _key;
V _value;
Color _color;
RBTreeNode(const K& key, const V& value)
:_pLeft(NULL)
, _pRight(NULL)
, _pParent(NULL)
, _key(key)
, _value(value)
, _color(Red){}
};
2.红黑树插入代码和判断调整的代码,插入代码和二叉搜索树一样的,重点看判断和调整就可以了
bool Insert(const K& key, const V& value)
{
if (NULL == _pRoot)//根节点为黑色
{
_pRoot = new Node(key, value);
_pRoot->_color = Black;
return true;
}
PNode cur = _pRoot;
PNode pParent = NULL;
while (cur)//找插入的位置
{
if (cur->_key == key)
return false;
else if (cur->_key > key)
{
pParent = cur;
cur = cur->_pLeft;
}
else
{
pParent = cur;
cur = cur->_pRight;
}
}
//找到插入的位置以后开始插入
PNode pNewNode = new Node(key, value);//新插入的结点的颜色默认是红的
if (key<pParent->_key)
{
pParent->_pLeft = pNewNode;
pNewNode->_pParent = pParent;
}
else
{
pParent->_pRight = pNewNode;
pNewNode->_pParent = pParent;
}
////当结点插入好了以后我们就要根据红黑树的性质来判断结点是否满足,从而调整结点
while (pParent)
{
if (pParent->_color == Black)//父结点是黑的,不用调整直接退出
{
break;
}
//记录祖父结点和定义保存叔叔结点
PNode gparent = pParent->_pParent;
PNode uncle = NULL;
if (pParent == gparent->_pLeft)//在其左子节点上
{
uncle = gparent->_pRight;
if (uncle&& uncle->_color == Red)//叔叔结点存在切为红情况3,4
{
gparent->_color = Red;
uncle->_color = pParent->_color = Black;
pNewNode= gparent;//保存gg结点
pParent = gparent->_pParent;
continue;
}
else if (uncle==NULL||uncle->_color==Black)//叔叔不存在或者为黑
{
if (pNewNode== pParent->_pLeft)//外侧插入
{
_RBRolateR(gparent);
gparent->_color = Red;
pParent->_color = Black;
}
else//内测插入
{
_RBRolateL(pParent);
std::swap(pParent, pNewNode);//交换pParent和插入结点指针的值
_RBRolateR(gparent);
gparent->_color = Red;
pParent->_color = Black;
}
}
break;//直接跳出循环
}
else//右边的情况
{
uncle =gparent->_pLeft;
if (uncle&&uncle->_color == Red)//叔叔存在而且为红色
{
gparent->_color = Red;
uncle->_color = pParent->_color = Black;
pNewNode = gparent;
pParent = pNewNode->_pParent;
continue;
}
else if (uncle == NULL || uncle->_color == Black)//叔叔不存在或者为黑
{
if (pNewNode== pParent->_pRight)
{
_RBRolateL(gparent);
gparent->_color = Red;
pParent->_color = Black;
}
else
{
_RBRolateR(pParent);
std::swap(pParent, pNewNode);
_RBRolateL(gparent);
gparent->_color = Red;
pParent->_color = Black;
}
}
break;
}
}
_pRoot->_color = Black;
return true;
}
二,判断树是否为红黑树
算法描述:主要根据红黑树的几个性质来判断
1.如果根节点为空,则是红黑树,返回true
2.如果根节点为红色肯定不是红黑树返回false
这两个根据根节点来判断的可以直接判断,下面的判断就需要用到递归了
//////////////////////////////////////////////
2.如果当前结点和其父结点都是红色则返回false
3.如果根节点到任意叶子结点上面的黑色结点的个数相等的话,返回true
..............因为根节点到任何一个叶子结点路径上的黑色结点都是相等的,所以首先找到一条参考的路径上面的黑色结点的个数,然后让根节点递归遍每个结点然后判断上述的情况,如果递归到了叶子结点,则只需要比较一下是否和我们给出的参考值是否相等的,相等返回true,否则就返回false
下面给出代码,结合代码理解。。
bool IsRBTree()
{
if (_pRoot == NULL)//根节点 为空是红黑树
return true;
if (_pRoot->_color == Red)//根节点为红色肯定不是红黑树
return false;
int count = 0;//统计黑色结点的数目
PNode cur = _pRoot;
while (cur)//根出一条参考路径的黑色结点的数目
{
if (cur->_color == Black)
++count;
cur = cur->_pLeft;
}
int k = 0;
return _IsRBTree(_pRoot, count, k);
}
private:
bool _IsRBTree(PNode pRoot, int& count, int k)//这里的K不能传引用
{
if (pRoot == NULL)
return true;
//出现两个连续的红色的结点
if (pRoot->_pParent&&pRoot->_color == Red&&pRoot->_pParent->_color == Red)
return false;
//如果是黑色结点k++
if (pRoot->_color == Black)
k++;
if (pRoot->_pLeft == NULL&&pRoot->_pRight == NULL)//如果是叶子结点的话进行判断k和count
{
if (k == count)
return true;
else
return false;
}
return _IsRBTree(pRoot->_pLeft, count, k) && _IsRBTree(pRoot->_pRight, count, k);
}
下面给出所有完整的代码和测试代码
#include<iostream>
using namespace std;
enum Color
{
Red,
Black
};
template<class K,class V>
struct RBTreeNode
{
RBTreeNode<K, V>* _pLeft;
RBTreeNode<K, V>* _pRight;
RBTreeNode<K, V>* _pParent;
K _key;
V _value;
Color _color;
RBTreeNode(const K& key, const V& value)
:_pLeft(NULL)
, _pRight(NULL)
, _pParent(NULL)
, _key(key)
, _value(value)
, _color(Red){}
};
template<class K,class V>
class RBTree
{
public:
typedef RBTreeNode<K, V> Node;
typedef Node* PNode;
private:
PNode _pRoot;
public:
RBTree()
:_pRoot(NULL){}
bool Insert(const K& key, const V& value)
{
if (NULL == _pRoot)//根节点为黑色
{
_pRoot = new Node(key, value);
_pRoot->_color = Black;
return true;
}
PNode cur = _pRoot;
PNode pParent = NULL;
while (cur)//找插入的位置
{
if (cur->_key == key)
return false;
else if (cur->_key > key)
{
pParent = cur;
cur = cur->_pLeft;
}
else
{
pParent = cur;
cur = cur->_pRight;
}
}
//找到插入的位置以后开始插入
PNode pNewNode = new Node(key, value);//新插入的结点的颜色默认是红的
if (key<pParent->_key)
{
pParent->_pLeft = pNewNode;
pNewNode->_pParent = pParent;
}
else
{
pParent->_pRight = pNewNode;
pNewNode->_pParent = pParent;
}
////当结点插入好了以后我们就要根据红黑树的性质来判断结点是否满足,从而调整结点
while (pParent)
{
if (pParent->_color == Black)//父结点是黑的,不用调整直接退出
{
break;
}
//记录祖父结点和定义保存叔叔结点
PNode gparent = pParent->_pParent;
PNode uncle = NULL;
if (pParent == gparent->_pLeft)//在其左子节点上
{
uncle = gparent->_pRight;
if (uncle&& uncle->_color == Red)//叔叔结点存在切为红情况3,4
{
gparent->_color = Red;
uncle->_color = pParent->_color = Black;
pNewNode= gparent;//保存gg结点
pParent = gparent->_pParent;
continue;
}
else if (uncle==NULL||uncle->_color==Black)//叔叔不存在或者为黑
{
if (pNewNode== pParent->_pLeft)//外侧插入
{
_RBRolateR(gparent);
gparent->_color = Red;
pParent->_color = Black;
}
else//内测插入
{
_RBRolateL(pParent);
std::swap(pParent, pNewNode);//交换pParent和插入结点指针的值
_RBRolateR(gparent);
gparent->_color = Red;
pParent->_color = Black;
}
}
break;//直接跳出循环
}
else//右边的情况
{
uncle =gparent->_pLeft;
if (uncle&&uncle->_color == Red)//叔叔存在而且为红色
{
gparent->_color = Red;
uncle->_color = pParent->_color = Black;
pNewNode = gparent;
pParent = pNewNode->_pParent;
continue;
}
else if (uncle == NULL || uncle->_color == Black)//叔叔不存在或者为黑
{
if (pNewNode== pParent->_pRight)
{
_RBRolateL(gparent);
gparent->_color = Red;
pParent->_color = Black;
}
else
{
_RBRolateR(pParent);
std::swap(pParent, pNewNode);
_RBRolateL(gparent);
gparent->_color = Red;
pParent->_color = Black;
}
}
break;
}
}
_pRoot->_color = Black;
return true;
}
void InOrder()
{
_InOrder(_pRoot);
}
bool IsRBTree()
{
if (_pRoot == NULL)//根节点 为空是红黑树
return true;
if (_pRoot->_color == Red)//根节点为红色肯定不是红黑树
return false;
int count = 0;//统计黑色结点的数目
PNode cur = _pRoot;
while (cur)//根出一条参考路径的黑色结点的数目
{
if (cur->_color == Black)
++count;
cur = cur->_pLeft;
}
int k = 0;
return _IsRBTree(_pRoot, count, k);
}
private:
bool _IsRBTree(PNode pRoot, int& count, int k)//这里的K不能传引用
{
if (pRoot == NULL)
return true;
//出现两个连续的红色的结点
if (pRoot->_pParent&&pRoot->_color == Red&&pRoot->_pParent->_color == Red)
return false;
//如果是黑色结点k++
if (pRoot->_color == Black)
k++;
if (pRoot->_pLeft == NULL&&pRoot->_pRight == NULL)//如果是叶子结点的话进行判断k和count
{
if (k == count)
return true;
else
return false;
}
return _IsRBTree(pRoot->_pLeft, count, k) && _IsRBTree(pRoot->_pRight, count, k);
}
//右单旋转
void _RBRolateR(PNode parent)
{
if (NULL == parent)
return;
PNode SubL = parent->_pLeft;//
PNode SubLR = SubL->_pRight;
parent->_pLeft = SubLR;
if (SubLR != NULL)
SubLR->_pParent = parent;
PNode pParent = parent->_pParent;
SubL->_pRight = parent;
parent->_pParent = SubL;
if (pParent == NULL)
{
_pRoot = SubL;
SubL->_pParent = NULL;
}
else
{
if (pParent->_pLeft == parent)
{
pParent->_pLeft = SubL;
SubL->_pParent = parent;
}
else
{
pParent->_pRight = SubL;
SubL->_pParent = parent;
}
}
}
void _RBRolateL(PNode parent)
{
if (NULL == parent)
return;
PNode SubR = parent->_pRight;
PNode SubRL = SubR->_pLeft;
parent->_pRight = SubRL;
if (SubRL)
{
SubRL->_pParent = parent;
}
PNode pParent = parent->_pParent;
SubR->_pLeft = parent;
parent->_pParent = SubR;
if (pParent == NULL)
{
_pRoot = SubR;
SubR->_pParent = NULL;
}
else
{
if (pParent->_pLeft == parent)
{
pParent->_pLeft = SubR;
SubR->_pParent = pParent;
}
else
{
pParent->_pRight = SubR;
SubR->_pRight = pParent;
}
}
}
void _InOrder(PNode pRoot)
{
if (pRoot)
{
_InOrder(pRoot->_pLeft);
cout << pRoot->_key << " ";
_InOrder(pRoot->_pRight);
}
}
};
int main()
{
RBTree<int, int> s;
int arr[] = {5,3,7,1,4,6,13,8,15,12};
int size = sizeof(arr) / sizeof(*arr);
for (int i = 0; i < size; i++)
s.Insert(arr[i], i);
s.InOrder();
cout<<s.IsRBTree();
system("pause");
return 0;
}
代码已经上传至github:
https://github.com/itverson/RBTree