目录
1.文章概述
本文适合熟悉二叉平衡树和AVL树的人阅读,因为红黑树是在这两者的基础上优化而来的,是一个更加高效的数据结构,里面的许多逻辑基础是与平衡二叉树或者AVL树一样的。
本文会阐述红黑树的实现逻辑并且用c++代码将红黑树增查改的功能用代码实现出来,帮助我们更好地理解红黑树,并且提高理解和编程能力。
2.红黑树介绍
2.1.红黑树概念
红黑树,是一种二叉搜索树,但在每个结点上增加一个存储为表示结点的颜色,可以是RED或BLACK。通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其它路径长出两倍,因而是接近平衡的。
![](https://i-blog.csdnimg.cn/direct/0922408120e749ed86879aaf53374b7d.png)
2.2红黑树的性质
1.每个结点不是红色就是黑色;
2.根结点是黑色的;
3.如果一个结点是红色的,则它的两个孩子结点必须是黑色的,即任何路径上没有连续的红色结点;
4.对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点,每条路径上黑色结点的数量相等;
思考:为什么满足上面的性质,红黑树就能保证:其最长路径中结点个数不会超过最短路径结点个数的两倍?
用两个特例解释:
这两颗树都是红黑树,最长的路径刚好是最短路径的两倍。
2.3红黑树和AVL树的对比
|------------|----------------------|
| AVL树 | 红黑树 |
| 高度差不超过1 | 最长路径不超过最短路径的2倍 |
| 存1000个值 | 存1000个值 |
| logN == 10 | 考虑极端情况:2*logN == 20 |
| 10亿个值 | 10亿个值 |
| logN == 30 | 2*logN == 60 |
| 相当于1毛钱 | 相当于2毛钱 |
[红黑树和AVL树的对比]
综上,性能是同一量级的。但是AVL树,控制严格平衡是付出代价。插入和删除时需要进行大量的旋转。
3.红黑树插入和查找实现
3.1红黑树插入
首先如同二叉平衡树一样,根据每个结点右边大,左边小的性质,先找到插入的位置。
为了简化,我们这里限制不能插入相同的值。
cpp
Node* cur = _pHead->_pLeft;
Node* parent = _pHead;
while (cur)
{
parent = cur;
if (data == cur->data)
return false;
else if (data > cur->data)
cur = cur->_pRight;
else
cur = cur->_pLeft;
}
if (data > parent->data)
parent->_pRight = newNode;
else
parent->_pLeft = newNode;
newNode->_pParent = parent;
cur = newNode;
插入后,如果新结点的父结点是黑色的,那么就插入结束。(默认插入新结点是红色的)
否则,将会进入红黑树插入中较难的环节。
找到cur的父结点,爷爷结点,叔叔结点。
cpp
Node* grandParent = parent->_pParent;
Node* uncle = nullptr;
if (parent == grandParent->_pLeft)
uncle = grandParent->_pRight;
else
uncle = grandParent->_pLeft;
接着,我们根据叔叔结点的三种情况来对新插入结点做处理,以使得新插入后任然保持红黑树的性质。
1.如果叔叔存在且叔叔是红色的,那么需要变色和继续向上更新:
将叔叔和爸爸结点变成黑色,祖先结点变成红色;如果祖先结点的父结点是黑的,那么结束,如果是红色的,那么继续向上更新,将现在的祖先结点当做当前结点(cur)。
2.如果叔叔不存在或者叔叔存在且是黑色的,那么只需要旋转之后再变色:
四种旋转与AVL树一样,分别书左单旋,右单旋,左右和右左旋转,这里不做详细说明。变色则需要将当前结点变成黑色,父结点和祖父结点变成红色。
代码展示:
cpp
while (uncle && uncle->color == RED)
{
uncle->color = parent->color = BLACK;
grandParent->color = RED;
cur = grandParent;
parent = cur->_pParent;
if (parent == _pHead)
{
cur->color = BLACK;
return true;
}
else if (parent->color == BLACK)
return true;
else if (parent->color == RED)
{
grandParent = parent->_pParent;
if (parent == grandParent->_pLeft)
uncle = grandParent->_pRight;
else
uncle = grandParent->_pLeft;
}
else
assert("未知错误");
}
if (uncle == nullptr || (uncle && uncle->color == BLACK))
{
if (grandParent->_pRight == parent && parent->_pRight == cur)
RotateL(grandParent);
else if(grandParent->_pLeft == parent && parent->_pLeft == cur)
RotateR(grandParent);
else if (grandParent->_pRight == parent && parent->_pLeft == cur)
{
RotateR(parent);
RotateL(grandParent);
}
else if (grandParent->_pLeft == parent && parent->_pRight == cur)
{
RotateL(parent);
RotateR(grandParent);
}
grandParent->color = RED;
parent->color = RED;
cur->color = BLACK;
if (_pHead->_pLeft != _pHead->_pRight)
_pHead->_pLeft = _pHead->_pRight = cur;
return true;
}
注意:_pHead是哨兵位的头结点,旋转时会将它的左指针和右指针指向不同的结点,这是不对的,旋转后应该都指向cur,我们在代码最后进行了一次检查。
至此,红黑树的插入就完成了。
3.2红黑树查找
查找类似二叉平衡树,代码如下:
cpp
// 检测红黑树中是否存在值为data的节点,存在返回该节点的地址,否则返回nullptr
Node* Find(const T& data)
{
Node* cur = _pHead->_pLeft;
while (cur)
{
if (cur->data == data)
return cur;
else if (data > cur->data)
cur = cur->_pRight;
else
cur = cur->_pLeft;
}
return nullptr;
}
至此,插入和查找都完成了,修改部分会在下一篇封装部分展现,删除部分可在《算法导论》中进行查看。
4.所有代码
cpp
#pragma once
#include <iostream>
#include <assert.h>
using namespace std;
enum color
{
RED,
BLACK
};
template <class T>
struct RBTreeNode
{
T data;
RBTreeNode* _pParent;
RBTreeNode* _pLeft;
RBTreeNode* _pRight;
color color;
RBTreeNode(T val = T())
{
data = val;
_pParent = nullptr;
_pLeft = nullptr;
_pRight = nullptr;
color = RED;
}
};
// 请模拟实现红黑树的插入--注意:为了后序封装map和set,本文在实现时给红黑树多增加了一个头结点
template <class T>
class RBTree
{
typedef RBTreeNode<T> Node;
public:
RBTree()
{
_pHead = new Node;
_pHead->_pLeft = _pHead;
_pHead->_pRight = _pHead;
}
// 在红黑树中插入值为data的节点,插入成功返回true,否则返回false
// 注意:为了简单起见,本次实现红黑树不存储重复性元素
bool Insert(const T& data)
{
//插入第一个节点
Node* newNode = new Node(data);
if (_pHead->_pLeft == _pHead)
{
_pHead->_pLeft = newNode;
_pHead->_pRight = newNode;
newNode->_pParent = _pHead;
newNode->color = BLACK;
return true;
}
//不是插入第一个节点
Node* cur = _pHead->_pLeft;
Node* parent = _pHead;
while (cur)
{
parent = cur;
if (data == cur->data)
return false;
else if (data > cur->data)
cur = cur->_pRight;
else
cur = cur->_pLeft;
}
if (data > parent->data)
parent->_pRight = newNode;
else
parent->_pLeft = newNode;
newNode->_pParent = parent;
cur = newNode;
//连上了
if (parent->color == BLACK)
return true;
Node* grandParent = parent->_pParent;
Node* uncle = nullptr;
if (parent == grandParent->_pLeft)
uncle = grandParent->_pRight;
else
uncle = grandParent->_pLeft;
while (uncle && uncle->color == RED)
{
uncle->color = parent->color = BLACK;
grandParent->color = RED;
cur = grandParent;
parent = cur->_pParent;
if (parent == _pHead)
{
cur->color = BLACK;
return true;
}
else if (parent->color == BLACK)
return true;
else if (parent->color == RED)
{
grandParent = parent->_pParent;
if (parent == grandParent->_pLeft)
uncle = grandParent->_pRight;
else
uncle = grandParent->_pLeft;
}
else
assert("未知错误");
}
if (uncle == nullptr || (uncle && uncle->color == BLACK))
{
if (grandParent->_pRight == parent && parent->_pRight == cur)
RotateL(grandParent);
else if(grandParent->_pLeft == parent && parent->_pLeft == cur)
RotateR(grandParent);
else if (grandParent->_pRight == parent && parent->_pLeft == cur)
{
RotateR(parent);
RotateL(grandParent);
}
else if (grandParent->_pLeft == parent && parent->_pRight == cur)
{
RotateL(parent);
RotateR(grandParent);
}
grandParent->color = RED;
parent->color = RED;
cur->color = BLACK;
if (_pHead->_pLeft != _pHead->_pRight)
_pHead->_pLeft = _pHead->_pRight = cur;
return true;
}
return false;
}
// 检测红黑树中是否存在值为data的节点,存在返回该节点的地址,否则返回nullptr
Node* Find(const T& data)
{
Node* cur = _pHead->_pLeft;
while (cur)
{
if (cur->data == data)
return cur;
else if (data > cur->data)
cur = cur->_pRight;
else
cur = cur->_pLeft;
}
return nullptr;
}
// 获取红黑树最左侧节点
Node* LeftMost()
{
Node* cur = _pHead;
while (cur->_pLeft)
{
cur = cur->_pLeft;
}
return cur;
}
// 获取红黑树最右侧节点
Node* RightMost()
{
Node* cur = _pHead;
while (cur->_pRight)
{
cur = cur->_pRight;
}
return cur;
}
// 检测红黑树是否为有效的红黑树,注意:其内部主要依靠_IsValidRBTRee函数检测
bool IsValidRBTRee()
{
if (_pHead->_pLeft == nullptr)
return true;
else if (_pHead->_pLeft->color == RED)
return false;
return _IsValidRBTRee(_pHead->_pLeft, 0, pathBlack());
}
private:
int pathBlack()
{
Node* cur = _pHead->_pLeft;
int ret = 0;
while (cur)
{
if (cur->color == BLACK)
ret++;
cur = cur->_pLeft;
}
return ret;
}
bool _IsValidRBTRee(Node* pRoot, size_t blackCount, size_t pathBlack)
{
if (pRoot == nullptr)
{
if (blackCount != pathBlack)
return false;
return true;
}
if (pRoot->color == BLACK)
blackCount++;
if (pRoot->color == RED && pRoot->_pParent->color == RED)
return false;
return _IsValidRBTRee(pRoot->_pLeft, blackCount, pathBlack) &&
_IsValidRBTRee(pRoot->_pRight, blackCount, pathBlack);
}
// 左单旋
void RotateL(Node* pParent)
{
Node* cur = pParent->_pRight;
Node* curLeft = cur->_pLeft;
Node* parentP = pParent->_pParent;
pParent->_pRight = curLeft;
if (curLeft)
curLeft->_pParent = pParent;
cur->_pLeft = pParent;
pParent->_pParent = cur;
cur->_pParent = parentP;
if (parentP->_pLeft == pParent)
parentP->_pLeft = cur;
else
parentP->_pRight = cur;
}
// 右单旋
void RotateR(Node* pParent)
{
Node* cur = pParent->_pLeft;
Node* curRight = cur->_pRight;
Node* parentP = pParent->_pParent;
pParent->_pLeft = curRight;
if (curRight)
curRight->_pParent = pParent;
cur->_pRight = pParent;
pParent->_pParent = cur;
cur->_pParent = parentP;
if (parentP->_pLeft == pParent)
parentP->_pLeft = cur;
else
parentP->_pRight = cur;
}
// 为了操作树简单起见:获取根节点
Node*& GetRoot()
{
return _pHead;
}
private:
Node* _pHead;
};
5.总结
红黑树的实现在理解了平衡二叉树和实现过AVL树的基础上其实也不难,手搓红黑树拿捏。