文章目录
1. 红黑树的概念
红黑树
(英语:Red–black tree)是一种自平衡二叉搜索树,是在计算机科学中用到的一种数据结构,典型用途是实现关联数组。它在1972年由鲁道夫·贝尔发明,被称为"对称二叉B树",它现代的名字源于Leo J. Guibas和Robert Sedgewick于1978年写的一篇论文。红黑树的结构复杂,但它的操作有着良好的最坏情况运行时间,并且在实践中高效:它可以在O(log n)时间内完成查找、插入和删除,这里的n是树中元素的数目。
- 在计算机科学中,关联数组(英语:Associative Array),又称映射(Map)、字典(Dictionary)是一个抽象的数据结构,它包含着类似于(键,值)的有序对。一个关联数组中的有序对可以重复(如C++中的multimap)也可以不重复(如C++中的map)。
2. 红黑树的性质
红黑树是每个节点都带有颜色属性的二叉查找树,颜色为红色或黑色,对于任何有效的红黑树,需要满足以下性质:
- 每个结点不是红色就是黑色
- 根节点必须是黑色的
- 如果一个节点是红色的,则它的两个孩子结点是黑色的
- 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均 包含相同数目的黑色结点
- 每个叶子结点都是黑色的(此处的叶子结点指的是空结点)
红黑树图例
关键特性:通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出俩倍
3. 红黑树的结构
head头结点中:
- parent指针指向null
- left指针指向树中最小值结点
- right指针指向树中最大值结点
root根结点的parent指针指向head头结点
4. 红黑树节点的定义
节点样式
// 红黑树节点的颜色
enum Color
{
RED,
BLACK
};
template<class T>
struct RBTreeNode
{
RBTreeNode<T>* left; // 节点的左孩子
RBTreeNode<T>* right; // 节点的右孩子
RBTreeNode<T>* parent; // 节点的父节点
T data; // 节点中保存的值
Color color; // 节点的颜色
// 将结点颜色默认设置为红色,因为如果设置成黑色会破坏原有黑色节点的个数
RBTreeNode(const T& x = T(), Color c = RED) :left(nullptr), right(nullptr), parent(nullptr), data(x), color(c){
}
};
// 红黑树结构及定义
// T : 表示红黑树中放置的元素类型
// KOFP:表示从T中提取Key
template<class T, class KOFP>
class RBTree
{
typedef RBTreeNode<T> Node;
typedef RBTreeIterator<T> iterator; //迭代器
private:
Node* head; // 指向红黑树头结点的指针
size_t _size; // 保存红黑树的节点个数
};
5. 红黑树的迭代器
红黑树的迭代器实现了:重载了++、–、!=、==、*、->这些操作符
// 迭代器
template<class T>
struct RBTreeIterator
{
typedef RBTreeNode<T> Node;
typedef RBTreeIterator<T> self;
public:
RBTreeIterator(Node* n = nullptr) : node(n){
}
// 具有指针类似的方法
T& operator*()
{
return node->data;
}
T* operator->()
{
return &(operator*());
}
/*
找比当前节点大的 所有节点中最小的节点:
1. 如果当前节点的右子树存在,应该在其右子树中找最小值节点(最左侧)
2. 如果当前节点的右子树不存在,应该在其双亲中不断查找
*/
self& operator++()
{
Increment();
return *this;
}
self& operator++(int)
{
self temp(*this);
Increment();
return temp;
}
/*
找比当前迭代器小的 所有节点中最大的:
1. 如果当前节点的左子树存在,应该在其左子树中找最大值节点(最右侧)
2. 如果当前节点的左子树不存在,
*/
self& operator--()
{
Decrement();
return *this;
}
self& operator--(int)
{
self temp(*this);
Decrement();
return temp;
}
bool operator!=(const self& s)const
{
return node != s.node;
}
bool operator==(const self& s)const
{
return node == s.node;
}
private:
Node* node; // 封装 节点指针
// 找当前迭代器的后一个位置
void Increment()
{
if (node->right)
{
// 当前节点的右子树如果存在,就在右子树中寻找最小值
node = node->right;
while (node->left)
{
node = node->left;
}
}
else
{
// 当前节点的右子树不存在,在父节点中寻找比当前节点大的节点
// 什么情况下才算大呢? 当node是双亲的左孩子时,才算找到
Node* parent = node->parent;
while (node == parent->right)
{
node = parent;
parent = parent->parent;
}
// 防止特殊情况:当根结点没有右子树时,迭代器恰好在根节点的位置
if (node->right != parent)
node = parent; // 此时node才指向父节点中比之前节点大的节点
}
}
// 找当前迭代器的前一个位置
void Decrement()
{
if (node == node->parent->parent && RED == node->color)
{
// node 在 end 的位置
node = node->right;
}
else if (node->left)
{
// 到 node 的左子树找最大的节点
node = node->left;
while (node->right)
{
node = node->right;
}
}
else
{
// 右子树不存在 -> 应该到 node 的双亲中找出比 node 小的节点
Node* parent = node->parent;
while (node == parent->left)
{
node = parent;
parent = node->parent;
}
node = parent;
}
}
};
6. 红黑树节点的插入
插入会遇到的破坏性质的三种情况:
情况1
:当前插入的节点是红色的,父节点是红色的,祖父节点是黑色的,叔叔节点存在且为红色
解决方法:将父节点和叔叔节点变为黑色,将祖父节点变为红色,
情况二
:cur为红,p为红,g为黑,u不存在/u为黑
解决方式:
①p为g的左孩子,cur为p的左孩子,则进行右单旋转;相反,p为g的右孩子,cur为p的右孩子,则进行左单旋转
②p、g变色–p变黑,g变红
情况三
:cur为红,p为红,g为黑,u不存在/u为黑
解决方式:
①p为g的左孩子,cur为p的右孩子,则针对p做左单旋转;相反,p为g的右孩子,cur为p的左孩子,则针对p做右单旋转,则转换成了情况2
②按照情况二进行处理
代码实现
// 插入数据构造红黑树
pair<iterator, bool> insert(const T& data)
{
Node*& root = getRoot(); // 保存根结点
// 1. 按照BST的规则寻找插入结点的位置
// 1.1 若root为空,则需要建立root节点
if (nullptr == root)
{
root = new Node(data, BLACK); // 根结点默认为黑色
root->parent = head;
head->left = root;
head->right = root;
_size = 1;
return make_pair