需要云服务器等云产品来学习Linux的同学可以移步/-->腾讯云<--/-->阿里云<--/-->华为云<--/官网,轻量型云服务器低至112元/年,新用户首次下单享超低折扣。
目录
一、红黑树简介
1、红黑树的简介
红黑树和AVL树一样,因其逻辑复杂,面试时现场要求手撕就是纯纯刁难面试者。但某大厂面试官曾要求某些求职者现场手撕红黑树(我赌5毛,让面试官撕,他也撕不出来,而且你家员工上班手搓红黑树啊?),随后求职遭遇被发到网上吐槽,这便有了“手撕红黑树”的梗,也让红黑树成为了知名度最高的数据结构。(话虽如此,对于红黑树的性质、插入思想等概念还是需要掌握的)
2、红黑树的性质
红黑树本质也是一种二叉搜索树。底层结构需要使用二叉搜索树的地方,基本上都会使用红黑树来实现,而AVL树也因此坐上了冷板凳。
红黑树通过在每个节点上添加一个存储位,用于存储“RED”或“BLACK”。通过节点上红/黑颜色限制,确保最长路径不超过最短路径的两倍,因而它是接近平衡的树形结构。最短路径:全黑;最长路径:一黑一红交替。
1、红黑树的根节点是黑色的;
2、没有连续的红色节点(如果某个节点为红色,则它的左右孩子必须是黑色)
3、无论哪个节点,其每条路径的黑色节点数量相同;
4、所有的空节点(NIL节点)可以认为是黑色的。
最优情况:全黑或每条路径都是一黑一红的满二叉树,高度logN
最差情况:每颗子树左子树全黑,右子树一黑一红。高度2*logN。
可以发现,最坏情况的时间复杂度和AVL树一样,都是O(logN),但是红黑树这种近似平衡的结构减少了大量旋转,综合性能优于AVL树。
二、红黑树的插入(看叔叔的颜色就行)
1、为什么新插入的节点必须给红色?
新节点给红色,可能会违反上面说的红黑树性质2;如果新节点给黑色,必定会违反性质3。
2、插入红色节点后,判定红黑树性质是否被破坏
情况一调整后可能变成情况一、情况二、情况三。
2.1情况一:uncle存在且为红
这种情况cur、parent、grandfather都是确定颜色的,唯独uncle的颜色是不确定的。
可以这么想:cur为红那么就需要将parent变为黑;parent变黑需要控制每条路径上黑节点的数量相同,那么就要把uncle变黑;如果grandfather不是根,需要反转为红,用以控制路径黑节点数量相同。继续向上调整即可。
2.2情况二:uncle不存在/存在且为黑(直线)
uncle的情况分两种。
uncle不存在,则cur为插入节点,单旋即可。
uncle存在且为黑是第一种情况变过来的。
2.3情况三:uncle不存在/存在且为黑(折线)
uncle的情况分两种。
uncle不存在,则cur为插入节点,两次单旋即可。
uncle存在且为黑,先掰直
2.4总结
插入新节点时,父节点为红,看叔叔的颜色。
1、叔叔存在且为红,变色,向上调整(可能变为三种情况中的任意一种)
2、叔叔不存在/存在且为黑,直线。单旋+变色
3、叔叔不存在/存在且为黑,折线,两次单旋+变色
3、红黑树插入代码
bool Insert(const pair<K,V>& kv)
{
if (_root == nullptr)
{
_root = new Node(kv);
_root->_col = BLACK;//根节点给黑色
return true;
}
//_root不为空
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->_col = RED;//新插入节点给红色,可能违反规则。如果给黑色会导致其他路径的黑色节点数量不相同,必定违反规则。
if (parent->_kv.first < kv.first)
{
parent->_right = cur;
cur->_parent = parent;//维护cur的父指针
}
else
{
parent->_left = cur;
cur->_parent = parent;
}
//调整
while (parent&&parent->_col == RED)
{
Node* grandfather = parent->_parent;//找到祖父
if (grandfather->_left == parent)//如果父亲是祖父的左孩子
{
Node* uncle = grandfather->_right;//找到叔叔
//情况一:叔叔存在且为红
if (uncle != nullptr && uncle->_col == RED)
{
//变色
parent->_col = uncle->_col = BLACK;
grandfather->_col = RED;
cur = grandfather;
parent = cur->_parent;
}
else//情况二或情况三
{
if (cur == parent->_left)//情况二,直线
{
RotateRight(grandfather);//右单旋
parent->_col = BLACK;
grandfather->_col = RED;
}
else//情况三,折线
{
RotateLeft(parent);//左单旋
RotateRight(grandfather);//右单旋
cur->_col = BLACK;
grandfather->_col = RED;
}
break;
}
}
else//如果父亲是祖父的右孩子
{
Node* uncle = grandfather->_left;
if (uncle != nullptr && uncle->_col == RED)
{
parent->_col =uncle->_col= BLACK;
grandfather->_col = RED;
cur = grandfather;
parent = cur->_parent;
}
else
{
if (cur == parent->_right)//情况二,直线
{
//g
// p
// c
RotateLeft(grandfather);//左单旋
parent->_col = BLACK;
grandfather->_col = RED;
}
else//情况三,折线
{
//g
// p
//c
RotateRight(parent);//右单旋
RotateLeft(grandfather);//左单旋
cur->_col = BLACK;
grandfather->_col = RED;
}
break;
}
}
}
_root->_col=BLACK;
return true;
}
三、红黑树的平衡检测
bool Check(Node* root,int blackNum,const int ref)//检查有没有连续红节点
{
if (root == nullptr)
{
if (blackNum != ref)
{
cout << "路径上黑节点数量不一致" << endl;
return false;
}
return true;
}
if (root->_col == BLACK)
{
++blackNum;
}
if (root->_col == RED && root->_parent->_col == RED)
{
cout << "违反规则,父子均为红" << endl;
return false;
}
return Check(root->_left, blackNum,ref) && Check(root->_right, blackNum, ref);
}
bool _IsBalance()
{
if (_root == nullptr)
return true;
if (_root->_col != BLACK)
{
return false;
}
//数一下一条路径黑色节点数量
int ref = 0;//统计一条路上黑色节点的数量
Node* left = _root;
while (left != nullptr)
{
if (left->_col == BLACK)
{
++ref;
}
left = left->_left;
}
return Check(_root,0,ref);
}
1、在_IsBalance中确定号一条路径中黑色节点的数量,作为参数传递给Check函数,Check函数需要在递归至根节点时,统计,每条路径黑色节点数量是否和基准值ref相等。
2、Check函数中还需要判断:子节点为红,父节点也为红(此时不平衡)
四、红黑树整体代码
#pragma once
#include <iostream>
#include <map>
#include <set>
#include <string>
using namespace std;
enum Color
{
RED,
BLACK,
};
template <class K,class V>
struct RBTreeNode
{
RBTreeNode(const pair<K,V>& kv)
:_parent(nullptr)
,_left(nullptr)
,_right(nullptr)
,_kv(kv)
,_col(RED)
{}
RBTreeNode<K,V>* _parent;
RBTreeNode<K, V>* _left;
RBTreeNode<K, V>* _right;
pair<K, V> _kv;
Color _col;
};
template <class K, class V>
class RBTree
{
public:
typedef RBTreeNode<K,V> Node;
bool Insert(const pair<K,V>& kv)
{
if (_root == nullptr)
{
_root = new Node(kv);
_root->_col = BLACK;//根节点给黑色
return true;
}
//_root不为空
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->_col = RED;//新插入节点给红色,可能违反规则。如果给黑色会导致其他路径的黑色节点数量不相同,必定违反规则。
if (parent->_kv.first < kv.first)
{
parent->_right = cur;
cur->_parent = parent;//维护cur的父指针
}
else
{
parent->_left = cur;
cur->_parent = parent;
}
//调整
while (parent&&parent->_col == RED)
{
Node* grandfather = parent->_parent;//找到祖父
if (grandfather->_left == parent)//如果父亲是祖父的左孩子
{
Node* uncle = grandfather->_right;//找到叔叔
//情况一:叔叔存在且为红
if (uncle != nullptr && uncle->_col == RED)
{
//变色
parent->_col = uncle->_col = BLACK;
grandfather->_col = RED;
cur = grandfather;
parent = cur->_parent;
}
else//情况二或情况三
{
if (cur == parent->_left)//情况二,直线
{
RotateRight(grandfather);//右单旋
parent->_col = BLACK;
grandfather->_col = RED;
}
else//情况三,折线
{
RotateLeft(parent);//左单旋
RotateRight(grandfather);//右单旋
cur->_col = BLACK;
grandfather->_col = RED;
}
break;
}
}
else//如果父亲是祖父的右孩子
{
Node* uncle = grandfather->_left;
if (uncle != nullptr && uncle->_col == RED)
{
parent->_col =uncle->_col= BLACK;
grandfather->_col = RED;
cur = grandfather;
parent = cur->_parent;
}
else
{
if (cur == parent->_right)//情况二,直线
{
//g
// p
// c
RotateLeft(grandfather);//左单旋
parent->_col = BLACK;
grandfather->_col = RED;
}
else//情况三,折线
{
//g
// p
//c
RotateRight(parent);//右单旋
RotateLeft(grandfather);//左单旋
cur->_col = BLACK;
grandfather->_col = RED;
}
break;
}
}
}
_root->_col=BLACK;
return true;
}
void Inorder()
{
_Inorder(_root);
}
bool IsBalance()
{
return _IsBalance();
}
private:
void _Inorder(Node* root)
{
if (root == nullptr)
return;
_Inorder(root->_left);
cout << root->_kv.first << ":" << root->_kv.second << endl;
_Inorder(root->_right);
}
bool Check(Node* root,int blackNum,const int ref)//检查有没有连续红节点
{
if (root == nullptr)
{
if (blackNum != ref)
{
cout << "路径上黑节点数量不一致" << endl;
return false;
}
return true;
}
if (root->_col == BLACK)
{
++blackNum;
}
if (root->_col == RED && root->_parent->_col == RED)
{
cout << "违反规则,父子均为红" << endl;
return false;
}
return Check(root->_left, blackNum,ref) && Check(root->_right, blackNum, ref);
}
bool _IsBalance()
{
if (_root == nullptr)
return true;
if (_root->_col != BLACK)
{
return false;
}
//数一下一条路径黑色节点数量
int ref = 0;//统计一条路上黑色节点的数量
Node* left = _root;
while (left != nullptr)
{
if (left->_col == BLACK)
{
++ref;
}
left = left->_left;
}
return Check(_root,0,ref);
}
void RotateLeft(Node* parent)//左单旋
{
Node* grandfather = parent->_parent;
Node* cur = parent->_right;
if (parent == _root)
{
_root = cur;
cur->_parent = nullptr;
}
else
{
if (grandfather->_left == parent)//需要判定parent原来属于grandfather的哪一边
grandfather->_left = cur;
else
grandfather->_right = cur;
cur->_parent = grandfather;
}
parent->_right = cur->_left;
if (cur->_left != nullptr)
cur->_left->_parent = parent;
cur->_left = parent;
parent->_parent = cur;
}
void RotateRight(Node* parent)//右单旋
{
Node* grandfather = parent->_parent;
Node* cur = parent->_left;
if (parent == _root)
{
_root = cur;
cur->_parent = nullptr;
}
else
{
if (grandfather->_left == parent)
{
grandfather->_left = cur;
cur->_parent = grandfather;
}
else
{
grandfather->_right = cur;
cur->_parent = grandfather;
}
}
parent->_parent = cur;
parent->_left = cur->_right;
if (cur->_right != nullptr)
cur->_right->_parent = parent;
cur->_right = parent;
}
private:
Node* _root=nullptr;
};
void TestAVLTree()
{
//int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };
//int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };
int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
//int a[] = { 9,8,7,6,5,4,3,2,1};
RBTree<int, int> t;
for (auto e : a)
{
t.Insert(make_pair(e, e));
}
t.Inorder();
//cout << t.IsBalance() << endl;
}