前言:在前面一篇文章我给老铁们分享了AVL树插入的实现,我们知道AVL树底层也是二叉搜索树,只不过是在二叉搜索树的基础上加入平衡因子,从而通过控制平衡因子让树达到平衡,AVL树要求左右子树绝对平衡(左右子树的高度差绝对值不超过1),因此需要旋转的代价也比较大,那么今天我们来分享一种和AVL树效率差不多的,但是没有AVL旋转的代价那么大的树,红黑树。
1.红黑树定义:红黑树是在二叉搜索树的基础上,再给每一个结点加入颜色,颜色要么是黑色要么是红色,红黑树的名称也是由此而来。
红黑树图片
2.红黑树性质:(1)根结点是黑色结点;(2)每条路径的黑结点个数相同;(3)不能有连续的红结点 (4)如果一个结点是红色的,那么它的两个孩子结点一定是黑色的 (5)每一个叶子结点都是黑色的(6)红黑树要求最长路径结点个数不超过最短路径结点个数的两倍
那么为什么满足前面五个性质就能一定满足第六个性质呢?
我们知道根结点一定是黑色结点,如果树不为空的情况下,那么最短路劲就是只有一个根结点,最长路劲是有红黑结点交替出现,由于结点是红的话,那么它的两个孩子一定是黑结点,所以每条路径上红节点的个数最多等于黑结点的个数,由此可见最长路径结点个数一定不超过最短路径结点个数的两倍。
如果上面的文字说明没听懂,那就举个简单的例子。
假设有一个红黑树,从根到叶子结点的黑结点的个数为3,那么该红黑树最短路径就是3,那么最长路径就是红黑结点交替出现,就相当于红结点黑结点3,所以最长路劲结点个数一定不会超过最短路劲结点个数的两倍。
3.红黑树定义结点(使用三叉链进行定义)
上面我们已经分享完了红黑树的定义和红黑树的性质,那么我们接下来就实践一下吧,定义出红黑树的结点。
要定义红黑树,那么我们就需要明白树中有什么,树中得有指向左右子树的结点和指向父结点的指针,还有数据,还有结点的颜色,那么结点的颜色我们直接使用c语言的枚举类型进行定义。
enum Colour
{
RED,
BLACK,
};
template<class K,class V>
struct RBTreeNode//把结点定义为公有的
{
RBTreeNode* _left;
RBTreeNode* _right;
RBTreeNode* _parent;
pair<K, V> _kv;//数据有key和val
Colour _col;//颜色
//写结点的构造函数,new函数需要调用结点的构造函数
RBTreeNode(const pair<K, V>& kv)
:_left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _kv(kv)
, _col(RED)//颜色先给为红色
{};
};
那么为啥需要把红色设置为默认颜色呢?
为了防止破坏每一个路径的黑结点个数相同的性质
4.红黑树的插入(重点)
红黑树插入有五种情况:
(1)我们先按二叉搜索插入规则,new出一个新的结点,然后再分类讨论插入时的各种情况。
template<class K,class V>
class RBTree
{
typedef RBTreeNode<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 (cur->_kv.first < kv.first)
{
parent = cur;
cur = cur->_right;
}
//往左边寻找
else if (cur->_kv.first > kv.first)
{
parent = cur;
cur = cur->_left;
}
//找到了
else
{
return false;
}
cur = new Node(kv);
//把cur和parent链接起来
//链接到右边
if (parent->_kv.first < kv.first)
{
parent->_right = cur;
}
else
{
parent->_left = cur;
}
//把新增结点颜色给为红色
cur->_col = RED;
}
}
private:
Node* _root = nullptr;
};
肯定有老铁好奇,为什么需要把新增设置为红色,黑色不行吗?
其实两种颜色都行,只是给成红色对树的性质破坏较小,最多只是一条路径破坏,如
果给出黑色,那么多条路径都被破坏了。
当父亲结点时是祖父结点的左边时。
(2)第一种情况:当插入红色结点时,父亲结点为红色,祖父结点为黑色,舅舅结点存在且为红色;
//第一种情况,当父亲结点存在且为红
while (parent&& parent->_col = RED)
{
//祖父结点一定为黑
Node* grandfather = parent->_parent;
//寻找uncle结点
//当父亲结点是左结点,那么uncle结点就是右节点
if (parent == grandfather->_left)
{
Node* uncle = grandfather->_right;
//情况1:uncle结点存在,且颜色为红色
if (uncle&& uncle->_col = RED)
{
//把parent和uncle结点变黑
parent->_col = uncle->_col = BLACK;
//再把祖父结点变红
grandfather->_col = RED;
//如果祖父结点的父亲结点还是红色
//那么继续往上处理
cur = grandfather;
parent = cur->_parent;
}
}
}
(3)第二种情况:当插入红色结点时,父亲结点为红色,祖父结点为黑色,舅舅结点不存在/存在且为黑
//**当cur和parent为红,uncle结点不存在**
while (parent && parent->_col == RED)
{
//红黑树的调节关键看叔叔结点
//找叔叔结点
Node* grandfther = parent->_parent;
if (grandfther->_left == parent)
{
Node* uncle = grandfther->_right;
//情况1:uncle结点存在,且颜色为红色
if (uncle && uncle->_col == RED)
{
//让父结点和叔叔结点变黑
parent->_col = uncle->_col = BLACK;
//再把祖父结点变红
grandfther->_col = RED;
//继续往上处理
cur = grandfther;
parent = cur->_parent;
}
//情况2:uncle不存在/uncle存在且为黑
else
{
//第二种情况
RotateR(grandfther);
//旋转后把祖父颜色变红,把父亲颜色变黑
grandfther->_col = RED;
parent->_col = BLACK;
break;
}
}
//左单旋
//该树是三叉链实现
void RotateL(Node* parent)
{
//定义一个指向parent右子树的subR指针
Node* subR = parent->_right;
//定义一个指向subR的左边的指针
Node* subRL = subR->_left;
//把prent右边指向subRL
parent->_right = subRL;
//当sbuRL不为空时,就让subRL的prent指向prent
if (subRL)
{
subRL->_parent = parent;
}
//让subR的左指向prent
//再让parent的parent指向subR
subR->_left = parent;
//把parent的parent先保存起来,防止parent不是根节点时,让subR链接上parent的parent
Node* ppNode = parent->_parent;
parent->_parent = subR;
//原来parent是节点的根,现在subR是根
if (_root == parent)
{
_root = subR;
//再让subR的根节点指向空
subR->_parent = nullptr;
}
else
{
//如果parent原来是ppNode的左边,那么subR也指向ppNode的左边
if (ppNode->_left == parent)
ppNode->_left = subR;
else
ppNode->_right = subR;
//再把subR的parent指向ppNode
subR->_parent = ppNode;
}
}
//右单旋
void RotateR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
//让parent的左边指向subLR
parent->_left = subLR;
if (subLR)
subLR->_parent = parent;
subL->_right = parent;
//先保存好parent的parent
Node* ppNode = parent->_parent;
parent->_parent = subL;
if (_root == parent)
{
_root = subL;
subL->_parent = nullptr;
}
else
{
if (ppNode->_left == parent)
{
ppNode->_left = subL;
}
else
{
ppNode->_right = subL;
}
//让subL节点的parent指向ppNode
subL->_parent = ppNode;
}
}
(3)第三种情况:当插入红色结点时,父亲结点为红色,,新插入的结点在父亲结点的右子树,祖父结点为黑色,舅舅结点不存在/存在且为黑
while (parent && parent->_col == RED)
{
//红黑树的调节关键看叔叔结点
//找叔叔结点
Node* grandfther = parent->_parent;
if (grandfther->_left == parent)
{
Node* uncle = grandfther->_right;
//情况1:uncle结点存在,且颜色为红色
if (uncle && uncle->_col == RED)
{
//让父结点和叔叔结点变黑
parent->_col = uncle->_col = BLACK;
//再把祖父结点变红
grandfther->_col = RED;
//继续往上处理
cur = grandfther;
parent = cur->_parent;
}
//情况2/情况3:uncle不存在/uncle存在且为黑
else
{
//cur在父亲结点右边
//情况3:双旋
if (cur == parent->_right)
{
RotateL(parent);
swap(parent, cur);
}
//旋转后把祖父颜色变红,把父亲颜色变黑
grandfther->_col = RED;
parent->_col = BLACK;
break;
}
}
当父亲结点时是祖父结点的右边时思路是和左边的一样,就不再重复讲解思路了,直接上代码了。
//父亲结点在祖父结点的右边
else
{
Node* uncle = grandfther->_left;
//情况1:uncle结点存在,且颜色为红色
if (uncle && uncle->_col == RED)
{
//把父亲和叔叔结点变黑
parent->_col = uncle->_col = BLACK;
grandfther->_col = RED;
cur = grandfther;
parent = cur->_parent;
}
else
{
if (cur == parent->_left)
{
//先右单旋
RotateR(parent);
swap(parent, cur);
}
RotateL(grandfther);
//祖父变红,父亲变黑
grandfther->_col = RED;
parent->_col = BLACK;
}
}
}
5.红黑树插入结点的全部代码
//插入
bool Insert(const pair<K, V>& kv)
{
//1.按搜索树的规则进行插入
if (_root == nullptr)
{
_root = new Node(kv);
//根结点颜色为黑色
_root->_col = BLACK;
return true;
}
Node* parent = nullptr;
Node* cur = _root;
while (cur)
{
//如果需要插入的结点的key>根结点的key,那么就往右边查找
if (cur->_kv.first < kv.first)
{
parent = cur;
cur = cur->_right;
}
else if (cur->_kv.first > kv.first)
{
parent = cur;
cur = cur->_left;
}
//表示该结点已经在红黑树中了
else
{
return false;
}
}
//new出一个结点再和父节点链接起来
cur = new Node(kv);
if (parent->_kv.first < kv.first)
{
parent->_right = cur;
cur->_parent = parent;
}
else
{
parent->_left = cur;
cur->_parent = parent;
}
//新增结点的颜色为红色(红色只是破坏一条路径)
cur->_col = RED;
//当当前结点和父结点都是红色
//父亲存在
while (parent && parent->_col == RED)
{
//红黑树的调节关键看叔叔结点
//找叔叔结点
Node* grandfther = parent->_parent;
if (grandfther->_left == parent)
{
Node* uncle = grandfther->_right;
//情况1:uncle结点存在,且颜色为红色
if (uncle && uncle->_col == RED)
{
//让父结点和叔叔结点变黑
parent->_col = uncle->_col = BLACK;
//再把祖父结点变红
grandfther->_col = RED;
//继续往上处理
cur = grandfther;
parent = cur->_parent;
}
//情况2/情况3:uncle不存在/uncle存在且为黑
else
{
//cur在父亲结点右边
//情况3:双旋
if (cur == parent->_right)
{
RotateL(parent);
swap(parent, cur);
}
//第二种情况(注意:第二种情况有可能是第三种情况变过来的)
RotateR(grandfther);
//旋转后把祖父颜色变红,把父亲颜色变黑
grandfther->_col = RED;
parent->_col = BLACK;
break;
}
}
//父亲结点在祖父结点的右边
else
{
Node* uncle = grandfther->_left;
//情况1:uncle结点存在,且颜色为红色
if (uncle && uncle->_col == RED)
{
//把父亲和叔叔结点变黑
parent->_col = uncle->_col = BLACK;
grandfther->_col = RED;
cur = grandfther;
parent = cur->_parent;
}
else
{
if (cur == parent->_left)
{
//先右单旋
RotateR(parent);
swap(parent, cur);
}
RotateL(grandfther);
//祖父变红,父亲变黑
grandfther->_col = RED;
parent->_col = BLACK;
}
}
}
//把根结点变黑
_root->_col = BLACK;
return true;
}
6.红黑树结点的查找
由于红黑树的底层还是二叉搜索树,所以查找结点还是按二叉搜索树查找结点的方法进行查找,这里也不再讲述思路了(不懂可以去看看二叉搜索树那篇博客),直接上代码了。
Node* Find(const K& key)
{
Node* cur = _root;
while (cur)
{
if (cur->_kv.first < key)
{
cur = cur->_right;
}
else if (cur->_kv.first > key)
{
cur = cur->_left;
}
else
{
return cur;
}
}
return nullptr;
}
测试红黑树
前面我们已经分享完了红黑树的插入和查找操作,那么我们来验证一下吧!
//1.先检验是否满足二叉搜索树
void _InOrder(Node* root)
{
if (root == nullptr)
{
return;
}
//中序递归遍历
//先递归左树,再递归右树
_InOrder(root->_left);
cout << root->_kv.first << ":" << root->_kv.second << endl;
_InOrder(root->_right);
}
void InOrder()
{
_InOrder(_root);
cout << endl;
}
void TestRBTree()
{
int a[] = { 16,3,7,11,9,26,18,14,15 };
RBTree<int, int> t;
for (auto e : a)
{
t.Insert(make_pair(e, e));
}
t.InOrder();
}
int main()
{
TestRBTree();
return 0;
}
再检验是否满足红黑树性质
bool IsValidRBTree()
{
Node* pRoot = _root;
// 空树也是红黑树
if (nullptr == pRoot)
return true;
// 检测根节点是否满足情况
if (BLACK != pRoot->_col)
{
cout << "违反红黑树性质二:根节点必须为黑色" << endl;
return false;
}
// 获取任意一条路径中黑色节点的个数
size_t blackCount = 0;
Node* pCur = pRoot;
while (pCur)
{
if (BLACK == pCur->_col)
blackCount++;
pCur = pCur->_left;
}
// 检测是否满足红黑树的性质,k用来记录路径中黑色节点的个数
size_t k = 0;
return _IsValidRBTree(pRoot, k, blackCount);
}
bool _IsValidRBTree(Node* pRoot, size_t k, const size_t blackCount)
{
//走到null之后,判断k和black是否相等
if (nullptr == pRoot)
{
if (k != blackCount)
{
cout << "违反性质四:每条路径中黑色节点的个数必须相同" << endl;
return false;
}
return true;
}
// 统计黑色节点的个数
if (BLACK == pRoot->_col)
k++;
// 检测当前节点与其双亲是否都为红色
Node* parent = pRoot->_parent;
if (parent && RED == parent->_col && RED == pRoot->_col)
{
cout << "违反性质三:没有连在一起的红色节点" << endl;
return false;
}
return _IsValidRBTree(pRoot->_left, k, blackCount) &&
_IsValidRBTree(pRoot->_right, k, blackCount);
}
void TestRBTree()
{
int a[] = { 16,3,7,11,9,26,18,14,15 };
RBTree<int, int> t;
for (auto e : a)
{
t.Insert(make_pair(e, e));
}
t.InOrder();
cout<<"该红黑树是否满足性质:" << t.IsValidRBTree() << endl;
}
int main()
{
TestRBTree();
return 0;
}