红黑树
一、前言
本文面向群体为刚接触红黑树的初学者群体,文章中构建的红黑树并不与STL中一致(因为对于初学者而言有很多点一下子解释不清),使用了较为简洁明了的方式解释红黑树的基本原理,方便各位萌新更好学习和理解。(编译环境为VS2019)
二、什么是红黑树
1.概念
2.性质
①每个结点不是红色就是黑色
②根节点是黑色的
③如果一个节点是红色的,则它的两个孩子结点是黑色的
④对于每个结点,从该结点到其所有后代叶结点的简单路径上,均 包含相同数目的黑色结点
⑤每个叶子结点都是黑色的(此处的叶子结点指的是空结点)
三、构建一个红黑树
1.枚举两种颜色
// 枚举出红色和黑色两种颜色
// 作为红黑树节点的成员之一
enum colour
{
BLACK,
RED
};
2.定义红黑树节点
此处我们使用pair类型为存储的数据类型,构建出的节点需要有五个成员,分别是颜色,父节点、左节点和右节点的地址,最后一个便是存储的数据类型。同时需要自己写一个构造函数。
template<class K, class V>
struct RBTreeNode
{
// 需要将插入节点颜色默认为红色
RBTreeNode(const pair<K, V> data)
:_data(data)
,_parent(nullptr)
,_left(nullptr)
,_right(nullptr)
,_colour(RED)
{}
colour _colour;
RBTreeNode<K, V>* _parent;
RBTreeNode<K, V>* _right;
RBTreeNode<K, V>* _left;
pair<K, V> _data;
};
3.搭建红黑树
再自定义一个类模板,将树的节点封装起来,这样一个树的雏形就形成了。
template<class K, class V>
class RBTree
{
typedef RBTreeNode<K, V> Node;
public:
private:
Node* _root = nullptr;
};
四、插入
1.查找插入位置
要插入新的节点,就要先找到插入的位置,实现类似与find函数,二叉搜索树的查找原理还是很简单的:比当前节点存储的数据大的则往节点右边走,小的则往节点左边走,这样就保证了左节点都比父节点小,右节点都比父节点大了。具体代码实现如下:
bool Insert(const pair<K, V>& data)
{
// 找到尾节点并插入新节点
Node* parent = nullptr;
Node* cur = _root;
if (_root == nullptr) // 排除极端情况
{
_root = new Node(data);
_root->_colour = BLACK;
return true;
}
while (cur) // 找到
{
if (data.first > cur->_data.first)
{
parent = cur;
cur = cur->_right;
}
else if (data.first < cur->_data.first)
{
parent = cur;
cur = cur->_left;
}
else // 相等 不插入
return false;
}
if (data.first < parent->_data.first) // 插入
{
cur = new Node(data);
cur->_parent = parent;
parent->_left = cur;
parent = cur->_parent;
}
else if (data.first > parent->_data.first)
{
cur = new Node(data);
cur->_parent = parent;
parent->_right = cur;
parent = cur->_parent;
}
else // 意料之外
assert(false);
}
2.调整红黑树
如果说红黑树比较难的话,那么对红黑树的调整绝对是最难的环节之一了,这一段如果不把逻辑捋顺、或者把具体的图画出来,那么到了细节处理时,我们面临的将是数不尽的bug呢(悲)
如何调整?
插入大体可以分为两种情况:
插入时父节点为黑
此时我们不需要对树进行调整,已经满足了红黑树的所有性质了,直接退出调整即可。
插入时父节点为红
这种情况最为复杂,我们需要将插入节点父亲的兄弟也要考虑进去,我们将它命名为叔叔节点,在这个基础上,我们可以再次细分为三种:
情况一.叔叔节点存在且为红,且新插入节点与 父节点和祖父节点的关系 一致;
情况二.叔叔节点不存在或者为黑,且新插入节点与 父节点和祖父节点的关系 一致;
情况三.叔叔节点不存在或者为黑,且新插入节点与 父节点和祖父节点的关系 相反。
情况一:
我们需要让红黑树保持相对平衡,因此不能存在两个连续的红色节点,此处我们将父节点和叔叔节点变成黑色,将祖父节点变成红色,然后将当前节点指向祖父节点,将父节点更新(因为此时在树的最底层),将继续网上迭代更新。代码段实现如下:
// 调整红黑树
while (parent && parent->_colour == RED)
{
// 获取祖父和叔叔节点(注:父节点为红,此时cur要么有祖父,要么没父亲)
Node* grandparent = parent->_parent;
Node* uncle;
if (parent == grandparent->_left)
uncle = grandparent->_right;
else
uncle = grandparent->_left;
// 叔叔节点不存在或者为黑的情况
if (!uncle || uncle->_colour == BLACK)
{
}
else // 为红的情况
{
parent->_colour = uncle->_colour = BLACK;
grandparent->_colour = RED;
cur = grandparent;
}
parent = cur->_parent; // 注意更新父节点
}
// 根节点最终总是为黑
_root->_colour = BLACK
情况二:
此时如果我们对情况二使用类似于情况一的解法,那么我们将会违背红黑树性质第四条,此时我们的解决方法是对红黑树节点进行单次旋转,旋转分为左旋和右旋,结果如图:
注:1.此处的nullptr不仅仅指nullptr,此处为了方便理解只列举出了这一种最容易理解的子树,其也可以是其他的子节点,需要将他们都一一对应起来;2.不同于AVL树的旋转,红黑树的旋转更多需要理解和实现的是颜色该如何变换。
代码段具体实现如下(如果想要真正掌握可以自己先动手尝试一下,细节处理尤为重要):
// 左单旋
void RotateL(Node* parent)
{
Node* cur = parent->_right;
Node* curleft = cur->_left;
// 变色
parent->_colour = RED;
cur->_colour = BLACK;
parent->_right = cur->_left;
if (curleft)
curleft->_parent = parent;
Node* pparent = parent->_parent;
cur->_left = parent;
parent->_parent = cur;
if (pparent == nullptr)
{
_root = cur;
cur->_parent = nullptr;
}
else
{
if (pparent->_left == parent)
{
pparent->_left = cur;
}
else
{
pparent->_right = cur;
}
cur->_parent = pparent;
}
}
// 右单旋
void RotateR(Node* parent)
{
Node* cur = parent->_left;
Node* curright = cur->_right;
// 变色
parent->_colour = RED;
cur->_colour = BLACK;
parent->_left = cur->_right;
if (curright)
curright->_parent = parent;
Node* pparent = parent->_parent;
cur->_right = parent;
parent->_parent = cur;
if (pparent == nullptr)
{
_root = cur;
cur->_parent = nullptr;
}
else
{
if (pparent->_left == parent)
{
pparent->_left = cur;
}
else
{
pparent->_right = cur;
}
cur->_parent = pparent;
}
}
情况三:
第三种如果直接利用第二种情况来调整颜色的话,你会惊奇的发现,“啊!调整了!又好像没有调整...如调!”。直接调整会陷入死循环,这时候我们需要对树进行双旋,双旋其实也就是先对父节点进行单旋,再对祖父节点进行单旋,结果如图:
此时不过是对cur和parent节点进行连续两次的旋转罢了,尽管可以复用已经完成的左右单旋,不过其中的细节较多,具体的实现需要各位通过代码的尝试才能一一试出来,双旋代码如图:
// 叔叔节点不存在或者为黑的情况
if (!uncle || uncle->_colour == BLACK)
{
if (cur == parent->_left && parent == grandparent->_left)
{
RotateR(grandparent);
cur = parent;
}
else if (cur == parent->_right && parent == grandparent->_right)
{
RotateL(grandparent);
cur = parent;
}
else if (cur == parent->_right && parent == grandparent->_left)
{
RotateL(parent);
RotateR(grandparent);
}
else if (cur == parent->_left && parent == grandparent->_right)
{
RotateR(parent);
RotateL(grandparent);
}
}
parent = cur->_parent; // 注意更新父节点
以上就是全部的调整了,插入节点并完成了红黑树的调整,红黑树就能保持相对平衡的状态,各位可以自己测试一下(有测试代码放在源码部分)。
希望本篇能对诸君有所帮助。
五、源码
#pragma once
#include <iostream>
#include <assert.h>
using namespace std;
enum colour
{
BLACK,
RED
};
template<class K, class V>
struct RBTreeNode
{
RBTreeNode(const pair<K, V> data)
:_data(data)
,_parent(nullptr)
,_left(nullptr)
,_right(nullptr)
,_colour(RED)
{}
colour _colour;
RBTreeNode<K, V>* _parent;
RBTreeNode<K, V>* _right;
RBTreeNode<K, V>* _left;
pair<K, V> _data;
};
template<class K, class V>
class RBTree
{
typedef RBTreeNode<K, V> Node;
public:
bool Insert(const pair<K, V>& data)
{
// 找到尾节点并插入新节点
Node* parent = nullptr;
Node* cur = _root;
if (_root == nullptr) // 排除极端情况
{
_root = new Node(data);
_root->_colour = BLACK;
return true;
}
while (cur) // 找到
{
if (data.first > cur->_data.first)
{
parent = cur;
cur = cur->_right;
}
else if (data.first < cur->_data.first)
{
parent = cur;
cur = cur->_left;
}
else // 相等 不插入
return false;
}
if (data.first < parent->_data.first) // 插入
{
cur = new Node(data);
cur->_parent = parent;
parent->_left = cur;
parent = cur->_parent;
}
else if(data.first > parent->_data.first)
{
cur = new Node(data);
cur->_parent = parent;
parent->_right = cur;
parent = cur->_parent;
}
else // 意料之外
assert(false);
// 调整红黑树
while (parent && parent->_colour == RED)
{
// 获取祖父和叔叔节点(注:父节点为红,此时cur要么有祖父,要么没父亲)
Node* grandparent = parent->_parent;
Node* uncle;
if (parent == grandparent->_left)
uncle = grandparent->_right;
else
uncle = grandparent->_left;
// 叔叔节点不存在或者为黑的情况
if (!uncle || uncle->_colour == BLACK)
{
if (cur == parent->_left && parent == grandparent->_left)
{
RotateR(grandparent);
cur = parent;
}
else if (cur == parent->_right && parent == grandparent->_right)
{
RotateL(grandparent);
cur = parent;
}
else if (cur == parent->_right && parent == grandparent->_left)
{
RotateL(parent);
RotateR(grandparent);
}
else if (cur == parent->_left && parent == grandparent->_right)
{
RotateR(parent);
RotateL(grandparent);
}
}
else // 为红的情况
{
parent->_colour = uncle->_colour = BLACK;
grandparent->_colour = RED;
cur = grandparent;
}
parent = cur->_parent; // 注意更新父节点
}
_root->_colour = BLACK; // 根节点始终为黑
}
// 检测
bool IsValidRBTree()
{
Node* pRoot = _root;
// 空树也是红黑树
if (nullptr == pRoot)
return true;
// 检测根节点是否满足情况
if (BLACK != pRoot->_colour)
{
cout << "违反红黑树性质二:根节点必须为黑色" << endl;
return false;
}
// 获取任意一条路径中黑色节点的个数
size_t blackCount = 0;
Node* pCur = pRoot;
while (pCur)
{
if (BLACK == pCur->_colour)
blackCount++;
pCur = pCur->_left;
}
// 检测是否满足红黑树的性质,k用来记录路径中黑色节点的个数
size_t k = 0;
return _IsValidRBTree(pRoot, k, blackCount);
}
private:
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->_colour)
k++;
// 检测当前节点与其双亲是否都为红色
Node* pParent = pRoot->_parent;
if (pParent && RED == pParent->_colour && RED == pRoot->_colour)
{
cout << "违反性质三:没有连在一起的红色节点" << endl;
return false;
}
return _IsValidRBTree(pRoot->_left, k, blackCount) &&
_IsValidRBTree(pRoot->_right, k, blackCount);
}
// 左单旋
void RotateL(Node* parent)
{
Node* cur = parent->_right;
Node* curleft = cur->_left;
// 变色
parent->_colour = RED;
cur->_colour = BLACK;
parent->_right = cur->_left;
if (curleft)
curleft->_parent = parent;
Node* pparent = parent->_parent;
cur->_left = parent;
parent->_parent = cur;
if (pparent == nullptr)
{
_root = cur;
cur->_parent = nullptr;
}
else
{
if (pparent->_left == parent)
{
pparent->_left = cur;
}
else
{
pparent->_right = cur;
}
cur->_parent = pparent;
}
}
// 右单旋
void RotateR(Node* parent)
{
Node* cur = parent->_left;
Node* curright = cur->_right;
// 变色
parent->_colour = RED;
cur->_colour = BLACK;
parent->_left = cur->_right;
if (curright)
curright->_parent = parent;
Node* pparent = parent->_parent;
cur->_right = parent;
parent->_parent = cur;
if (pparent == nullptr)
{
_root = cur;
cur->_parent = nullptr;
}
else
{
if (pparent->_left == parent)
{
pparent->_left = cur;
}
else
{
pparent->_right = cur;
}
cur->_parent = pparent;
}
}
Node* _root = nullptr;
};