红黑树笔记
距离上一次记录笔记可太久了,最近在B站看到一个手写红黑树的教学视频,对于红黑树的一些基本性质,增删改查等操作都有比较详细的讲解,在这里分享一下学习笔记。
视频链接如下:
红黑树第一讲
红黑树第二讲
红黑树基本性质
红黑树是一棵自平衡的二叉搜索树。
- 所谓二叉搜索树,即对于每个节点Node,其左子树上的所有节点值均小于节点Node的值,其右子树上所有节点值均大于节点Node的值;
- 而自平衡则对于二叉树的高度做出了规定,最小树枝高度与最大树枝高度的差不超过2(普通平衡二叉树高度差不超过1)。
红黑树必须满足以下性质:
- 每个节点必须为红色或黑色(相较于普通二叉树的节点,红黑树节点新增了颜色属性)
- 根节点必为黑色;
- 每个叶子节点均为黑色(这里的叶子节点是指外部节点,)即若当前节点为黑色且儿子节点为空,则它为叶子节点;若当前节点为红色,则该红色节点还有两个隐含的叶子节点(空节点)为黑色节点。
- 红色节点的儿子节点一定为黑色,黑色节点的儿子节点既可以是红色,也可以是黑色(换句话说,从叶子节点到根节点的路径中不存在两个连续的红色节点)
- 从** 根节点到每个叶子节点的的所有路径都包含相同数量**的黑色节点(黑高度)
红黑树节点类别
如图:nil为虚拟叶子节点,root为根节点,所有叶子节点到根节点的黑高度均为2(不包含叶子本身)
红黑树维持平衡的基本操作
- 左旋:
- 对于当前节点cur,左旋是将当前节点的右儿子作为当前节点的父节点;
- 当前节点的右儿子的左子树作为当前节点的右子树;
- 其余节点保持不变。
- 右旋:
- 对于当前节点cur,右旋是将当前节点的左儿子作为当前节点的父节点;
- 当前节点的左儿子的右子树作为当前节点的左子树;
- 其余节点保持不变。
- 变色:进行节点的重染色,红变黑,黑变红(只针对指定节点)
红黑树的声明
盘点红黑树需要的属性
- 红黑树的树的属性和函数
- 对于一棵红黑树,我们要能够获取其根节点的属性值,树的大小,以及指定节点的父节点,因此每个红黑树的对象中需要维护这些字段的值;
- 红黑树类的成员函数主要可以分为普通二叉搜索树的操作和红黑树的特有操作:
- 普通二叉搜索树的操作无非是一些判空,初始化,获取大小,插入节点,删除节点,查找节点,左旋,右旋等函数;
- 红黑树的特有操作主要是红黑树的平衡调节,主要包括双红修正和失黑修正。由于我们插入节点默认为红色(防止插入节点后破坏红黑树的性质5),可能在插入节点后出现父子节点均为红色的现象(破坏了性质4),此时需要进行双红修正;且删除节点时可能导致某一条路径(从根到叶子节点)的黑高度与其它路径不一致,破坏性质5,此时需要进行失黑修正。
- 红黑树的节点的属性和函数
- 为了实现上述红黑树的函数,我们在红黑树的类中定义一个结构体RB_Node用于表示每个节点,并在该结构体中定义一系列的成员变量和成员函数:
- 成员变量:节点值、节点颜色、节点的左右儿子指针,节点的父节点指针;
- 成员函数:寻找前驱节点,寻找后继节点,寻找直接后继节点
- 为了实现上述红黑树的函数,我们在红黑树的类中定义一个结构体RB_Node用于表示每个节点,并在该结构体中定义一系列的成员变量和成员函数:
- 红黑树的迭代器方法
- 引入迭代器主要是为了方便红黑树的遍历,迭代器方法主要包括常规的运算符重载函数,首尾迭代器,以及一些基于迭代器的查找、插入、删除函数
红黑树类的声明
rb_tree.h
#include <stdio.h>
using namespace std;
// x的兄弟节点,若x为父节点的左儿子,则兄弟节点为父节点的右儿子;否则,兄弟节点为父节点的左儿子
#define bro(x) (((x)->ftr->lc == (x)) ? ((x)->ftr->rc):((x)->ftr->lc))
typedef bool RB_COLOR; // 定义红黑树节点的颜色
#define RB_COLOR_RED true
#define RB_COLOR_BLACK false
template <typename T>
class rb_tree {
protected:
struct RB_Node;
RB_Node* _root; // 根节点
RB_Node* _hot; // 父节点
int _size; // 树的大小
void init(T); // 初始化
RB_Node* zig(RB_Node*); // 右旋操作
RB_Node* zag(RB_Node*); // 左旋操作
void SolveDoubleRed(RB_Node*); // 双红修正
void SolveLostBlack(RB_Node*); // 失黑修正
RB_Node* find(T); // 寻找某一指定节点
void removeTree(RB_Node*); // 删除整棵树,dfs后续遍历
public:
struct iterator; // 迭代器
rb_tree() : _root(nullptr), _hot(nullptr), _size(0) {} // 红黑树构造函数
iterator insert(T); // 基于迭代器的插入操作
bool remove(T); // 按值删除
bool remove(iterator&); // 按迭代器删除
iterator search(T); // 寻找
iterator lower_bound(T); // 下界寻找, >= T a
iterator upper_bound(T); // 上界寻找, <= T a
void clear(); // 清理整棵树
int size(); // 获取树的大小
bool empty(); // 判空
iterator begin(); // 首迭代器
static iterator end(); // 尾迭代器
};
红黑树成员变量和函数的定义
rb_tree.cpp
- 红黑树节点
template <typename T>
struct rb_tree<T>::RB_Node {
T val; // 节点值
RB_COLOR rbc; // 节点颜色
RB_Node* ftr; // 指向父亲的指针
RB_Node* lc; // 左儿子指针
RB_Node* rc; // 右儿子指针
RB_Node* succ() { // 寻找当前节点的直接后继节点,即右子树的最左节点
RB_Node* ptn = rc;
while(ptn->lc) {
ptn = ptn->lc;
}
return ptn;
}
RB_Node* left_node() { // 寻找当前节点的前驱节点
RB_Node* ptn = this;
if(!lc) {
while(ptn->ftr && ptn->ftr->lc = ptn) {
ptn = ptn->ftr;
}
ptn = ptn->ftr;
}else {
ptn = lc;
while(ptn->rc) {
ptn = ptn->rc;
}
}
return ptn;
}
RB_Node* right_node() { // 寻找当前节点的后继节点
RB_Node* ptn = this;
if(!rc) {
while(ptn->ftr && ptn->ftr->rc == ptn) {
ptn = ptn->ftr;
}
ptn = ptn->ftr;
}else {
ptn = rc;
while(ptn->lc) {
ptn = ptn->lc;
}
}
return ptn;
}
// 构造函数
RB_Node(T v = T(), RB_COLOR rb=RB_COLOR_RED, RB_Node* f = nullptr, RB_Node* l = nullptr, RB_Node* r = nullptr)
: val(v), rbc(rb), ftr(f), lc(l), rc(r) {}
};
- 红黑树迭代器
template <typename T>
struct rb_tree<T>::iterator {
protected:
RB_Node* _real_node; // 迭代器代表的真实节点
public:
/************************************* 运算符重载 ************************************/
T operator*() {
return _real_node->val;
}
bool operator==(iterator const& iter) {
return _real_node == iter._real_node;
}
bool operator!=(iterator const& iter) {
return _real_node != iter._real_node;
}
bool operator!() {
return !_real_node;
}
iterator& operator=(iterator const& iter) {
_real_node = iter._real_node;
return *this;
}
iterator& operator++() {
_real_node = _real_node->right_node();
return *this;
}
iterator& operator--() {
_real_node = _real_node->left_node();
return *this;
}
/************************************* 构造函数 ************************************/
iterator(RB_Node* node_nn = nullptr) : _real_node(node_nn) {}
iterator(T const& val_vv) : _real_node(find(val_vv)) {}
iterator(iterator const& iter) : _real_node(iter._real_node) {}
};
- 红黑树查找函数
template <typename T>
typename
rb_tree<T>::RB_Node* rb_tree<T>::find(T v) { // 按值查找节点
RB_Node* ptn = _root;
_hot = nullptr;
while(ptn && ptn->val != v) {
_hot = ptn;
if(ptn->val > v) {
ptn = ptn->lc;
}else {
ptn = ptn->rc;
}
}
return ptn;
}
template <typename T>
typename
rb_tree<T>::iterator rb_tree<T>::search(T v) { // 基于迭代器查找,相当于按值查找的封装
return iterator(find(v));
}
template <typename T>
typename
rb_tree<T>::iterator rb_tree<T>::lower_bound(T v) { // 返回不小于目标值v的第一个节点的迭代器
RB_Node* ptn = _root;
_hot = nullptr;
while(ptn) {
_hot = ptn;
if(ptn->val >= v) {
ptn = ptn->lc;
}else {
ptn = ptn->rc;
}
}
if(ptn->val >= v) {
ptn = _hot;
}else {
ptn = _hot->right_node();
}
return iterator(ptn);
}
template <typename T>
typename
rb_tree<T>::iterator rb_tree<T>::upper_bound(T v) { // 返回区域中第一个值大于v的节点的迭代器
RB_Node* ptn = _root;
_hot = nullptr;
while(ptn) {
_hot = ptn;
if(ptn->val > v) {
ptn = ptn->lc;
}else {
ptn = ptn->rc;
}
}
if(_hot->val > v) {
ptn = _hot;
}else {
ptn = _hot->right_node();
}
return iterator(ptn);
}
- 红黑树的右旋,左旋和初始化函数
template <typename T>
void rb_tree<T>::init(T v) {
// 初始化函数,新建一个根节点并染成黑色,红黑树大小置为1,其余节点指针置为空
_root = new RB_Node(v, RB_COLOR_BLACK);
_hot = nullptr;
_size = 1;
}
template <typename T>
typename
rb_tree<T>::RB_Node* rb_tree<T>::zig(RB_Node* ptn) { // 右旋操作,以ptn为旋转支点
ptn->lc->ftr = ptn->ftr; // 令ptn的左儿子的父指针指向ptn的父亲节点
if(ptn->ftr) { // 若ptn的父亲不为空
if(ptn->ftr->lc == ptn) { // ptn为父亲的左孩子,则令父亲的左孩子指向ptn的左儿子
ptn->ftr->lc = ptn->lc;
}else { // ptn为父亲的右孩子,则令父亲的右孩子指向ptn的左儿子
ptn->ftr->rc = ptn->lc;
}
}
if(ptn->lc->rc) { // 若ptn的左儿子有右孩子,则令其右孩子的父指针指向ptn
ptn->lc->rc->ftr = ptn;
}
ptn->ftr = ptn->lc; // 原ptn的左儿子变成ptn的父亲
ptn->lc = ptn->lc->rc; // 原ptn的左儿子的右孩子变成ptn的左儿子
ptn->ftr->rc = ptn; // 原ptn的父亲的右儿子变成ptn
return ptn->ftr;
}
template <typename T>
typename
rb_tree<T>::RB_Node* rb_tree<T>::zag(RB_Node* ptn) { // 左旋操作,以ptn为旋转支点
ptn->rc->ftr = ptn->ftr; // 令ptn的右儿子的父指针指向ptn的父亲节点
if(ptn->ftr) { // 若ptn的父亲不为空
if(ptn->ftr->lc == ptn) { // ptn为父亲的左孩子,则令父亲的右孩子指向ptn的左儿子
ptn->ftr->lc = ptn->rc;
}else { // ptn为父亲的右孩子,则令父亲的右孩子指向ptn的右儿子
ptn->ftr->rc = ptn->rc;
}
}
if(ptn->rc->lc) { // 若ptn的右儿子有左孩子,则令其左孩子的父指针指向ptn
ptn->rc->lc->ftr = ptn;
}
ptn->ftr = ptn->rc;
ptn->rc = ptn->rc->lc;
ptn->ftr->lc = ptn;
return ptn->ftr;
}
- 红黑树的插入,删除函数
// 红黑树的插入操作,插入单个节点
template <typename T>
typename
rb_tree<T>::iterator rb_tree<T>::insert(T v) {
RB_Node* ptn = find(v); // 查找待插入的值是否存在,红黑树不支持重复值
if(ptn) { // 存在则直接返回迭代器
return iterator(ptn);
}
if(!_hot) { // 树为空,使用值v初始化红黑树并返回
init(v);
return iterator(_root);
}
++_size; // 进行插入操作需要使红黑树的大小++
ptn = new RB_Node(v, RB_COLOR_RED, _hot); // 构造新节点,默认为红色,值为v,父亲为_hot
if(_hot->val < v) { // 若父亲的值小于v,新节点为右孩子
_hot->rc = ptn;
}else { // 否则新节点为左孩子
_hot->lc = ptn;
}
SolveDoubleRed(ptn); // 双红修正
return iterator(ptn);
}
// 按值删除单个节点
template <typename T>
bool rb_tree<T>::remove(T v) {
RB_Node* ptn = find(v);
RB_Node* node_succ;
if(!ptn) { //没有该元素,直接返回false
return false;
}
--_size; // 进行删除操作需要使红黑树的大小--
while(ptn->lc || ptn->rc) {
if(!ptn->lc) {
// 如果ptn没有左儿子,那么它的右子树必定只有一个红色节点,否则会违反红黑树的性质5,则ptn的直接后继即为它的右儿子
node_succ = ptn->rc;
}else if(!ptn->rc) {
// 如果ptn没有右儿子,同理,ptn的直接后继为它的左儿子
node_succ = ptn->lc;
}else {
node_succ = ptn->succ();
}
ptn->val = node_succ->val;
ptn = node_succ;
}
if(ptn->rbc == RB_COLOR_BLACK) { // 如果待删除节点为黑色,删除触发失黑修正
SolveLostBlack(ptn);
}
if(ptn->ftr) {
// 如果ptn有指向父节点的指针,将其父节点指向ptn的指针置空
if(ptn->ftr->lc == ptn) {
ptn->ftr->lc = nullptr;
}else {
ptn->ftr->rc = nullptr;
}
}
if(_root == ptn) {
assert(_size == 0);
_root = nullptr;
}
delete ptn;
return true;
}
// 按迭代器删除单个节点,逻辑与按值删除基本一致
template <typename T>
bool rb_tree<T>::remove(iterator& iter) {
RB_Node* ptn = iter._real_node;
iter._real_node = iter._real_node->right_node();
if(!(iter._real_node)) { //没有该元素,直接返回false
iter._real_node = ptn->left_node();
}
RB_Node* node_succ;
--_size;
while(ptn->lc || ptn->rc) {
if(!ptn->lc) {
node_succ = ptn->rc;
}else if(!ptn->rc) {
node_succ = ptn->lc;
}else {
node_succ = ptn->succ();
}
ptn->val = node_succ->val;
ptn = node_succ;
}
if(ptn->rbc == RB_COLOR_BLACK) {
SolveLostBlack(ptn);
}
if(ptn->ftr) {
if(ptn->ftr->lc == ptn) {
ptn->ftr->lc = nullptr;
}else {
ptn->ftr->rc = nullptr;
}
}
if(_root == ptn) {
assert(_size == 0);
_root = nullptr;
}
delete ptn;
return true;
}
// 删除整棵树
template <typename T>
void rb_tree<T>::removeTree(RB_Node* ptn) { // dfs后序遍历删除
if(!ptn)
return;
removeTree(ptn->lc);
removeTree(ptn->rc);
delete ptn;
}
template <typename T>
void rb_tree<T>::clear() { // 对removeTree的封装
removeTree(_root);
_size = 0;
_root = nullptr;
}
- 红黑树修正
/**
* 双红修正
* RR-0
* 如果父节点为黑色,则无需修正
* RR-1
* 如果父节点为红色,叔父节点是黑色,祖父节点必为黑色,先进行1~2次旋转,
* 再进行两次染色。
* RR-2
* 父节点和叔父节点均为红色,祖父节点必为黑色
* 首先将父节点和叔父节点染黑,将祖父节点染红,若此时符合RR-0,则递归结束
* 否则递归进行RR-2,若根节点被染红,则将根节点染黑
*/
template <typename T>
void rb_tree<T>::SolveDoubleRed(RB_Node* nn) {
while((nn->ftr) && (nn->ftr->rbc == RB_COLOR_RED)) { // 排除递归到根和RR_0的情况
RB_Node* pftr = nn->ftr;
RB_Node* uncle = bro(pftr);
RB_Node* gradftr = pftr->ftr;
if(uncle && uncle->rbc == RB_COLOR_RED) { // RR-2
gradftr->rbc = RB_COLOR_RED;
uncle->rbc = RB_COLOR_BLACK;
pftr->rbc = RB_COLOR_BLACK;
nn = gradftr;
}else { // RR-1
if(gradftr->lc == pftr) {
if(pftr->lc == nn) {
if(gradftr == _root) {
_root = pftr;
}
zig(gradftr);
pftr->rbc = RB_COLOR_BLACK;
gradftr->rbc = RB_COLOR_RED;
}else {
if(gradftr == _root) {
_root = nn;
}
zag(pftr);
zig(gradftr);
nn->rbc = RB_COLOR_BLACK;
gradftr->rbc = RB_COLOR_RED;
}
}else {
if(pftr->lc == nn) {
if(gradftr == _root) {
_root = nn;
}
zig(pftr);
zag(gradftr);
nn->rbc = RB_COLOR_BLACK;
gradftr->rbc = RB_COLOR_RED;
}else {
if(gradftr == _root) {
_root = pftr;
}
zag(gradftr);
pftr->rbc = RB_COLOR_BLACK;
gradftr->rbc = RB_COLOR_RED;
}
}
return;
}
}
if(nn == _root) {
nn->rbc = RB_COLOR_BLACK;
}
}
/**
* 失黑修正,4种情况
* LB-1(父亲为黑色,兄弟为红色)(递归)
* 先旋转父亲节点,使兄弟节点替代原来父亲节点的位置
* 然后将原父亲节点染红,将原兄弟节点染黑
* 最后进行LB-2R或LB-3修正
*
* LB-2B(没有红色侄子,父亲和兄弟均为黑色)
* 染红兄弟节点,递归修正父亲节点
*
* LB-2R(没有红色侄子,父亲为红色,兄弟为黑色)
* 对父亲和兄弟各做一次重染色
*
* LB-3(有红色侄子)
* 进行1~2次旋转,然后进行1或2次重染色
*/
template <typename T>
void rb_tree<T>::SolveLostBlack(RB_Node* nn) {
while(nn != _root) {
RB_Node* pftr = nn->ftr;
RB_Node* bthr = bro(nn);
if(bthr->rbc == RB_COLOR_RED) { // LB-1
bthr->rbc = RB_COLOR_BLACK;
pftr->rbc = RB_COLOR_RED;
if(_root = pftr) {
_root = bthr;
}
if(pftr->lc == nn) {
/**
* nn为父节点的左儿子,则它的兄弟为右儿子
* 以父节点为支点,进行左旋,使兄弟成为新的父节点
*/
zag(pftr);
}else {
zig(pftr);
}
bthr = bro(nn);
pftr = nn->ftr;
}
// LB-3
if(bthr->lc && bthr->lc->rbc == RB_COLOR_RED) {
RB_COLOR oldrbc = pftr->rbc;
pftr->rbc = RB_COLOR_BLACK;
if(pftr->lc == nn) {
// nn是pftr的左儿子,bthr是pftr的右儿子且bthr的儿子为红色
if(_root == pftr) {
_root = bthr->lc;
}
zig(bthr); // 右旋兄弟
zag(pftr); // 左旋父亲
}else{
// nn是pftr的右儿子,bthr是pftr的左儿子且bthr的儿子为红色
if(_root == pftr) {
_root = bthr;
}
zig(pftr); // 右旋父亲
}
pftr->ftr->rbc = oldrbc;
return;
}else if(bthr->rc && bthr->rc->rbc == RB_COLOR_RED) {
RB_COLOR oldrbc = pftr->rbc;
pftr->rbc = RB_COLOR_BLACK;
if(pftr->lc == nn) {
if(_root == pftr) {
_root = bthr;
}
zag(pftr);
}else {
if(_root == pftr) {
_root = bthr->rc;
}
zag(bthr);
zig(pftr);
}
pftr->ftr->rbc = oldrbc;
return;
}
if(pftr->rbc == RB_COLOR_RED) { // LB-2R
pftr->rbc = RB_COLOR_BLACK;
bthr->rbc = RB_COLOR_RED;
return;
}else { // LB-2B
// 父亲和兄弟均为黑色,且兄弟没有红色的儿子
bthr->rbc = RB_COLOR_RED;
nn = pftr;
}
}
}
- 红黑树的其他函数
template <typename T>
typename
rb_tree<T>::iterator rb_tree<T>::begin() { // 首迭代器
RB_Node* ptn = _root;
while(ptn->lc) {
ptn = ptn->lc;
}
return iterator(ptn);
}
template <typename T>
typename
rb_tree<T>::iterator rb_tree<T>::end() { // 尾迭代器
return iterator(nullptr);
}
template <typename T>
int rb_tree<T>::size() { // 获取红黑树的大小
return _size;
}
template <typename T>
bool rb_tree<T>::empty() { // 判空
return !_size;
}
红黑树的属性和应用场景
- 对于一棵n个节点的红黑树,树的高度至多为2·lg(n+1),其插入,删除,查找一个节点的时间复杂度均为O(lg n)。
- 红黑树虽然属于二叉搜索树,是一种特殊的平衡二叉树,但其维持平衡的条件不像平衡二叉树那样苛刻,且红黑树可以保证最坏情况下时间复杂度仍为O(lg n),当数据量达到一定规模后,红黑树的效率更高。
- C++STL库中的容器map和set底层都是基于红黑树实现的。