前言
本系列文章承接C++基础的学习,需要**++有C语言的基础++** 才能学会哦~
第22篇主要讲的是有关于C++的**++红黑树++** 和**++unordered的区别++** 。
C++才起步,都很简单!!
红黑树概念
是一棵二叉搜索树,每一个结点增加一个存储位表示结点颜色(红或黑)。通过颜色进行约束,使其没有一条路径比其他路径长2倍,从而接近平衡。
红黑树规则
①结点只有黑色或者红色。
②根结点是黑色的。
③红色结点的两个孩子必须是黑色(NULL也算是黑色的),也就是说任意一条路径不会有连续的红色结点。(约束红色结点的孩子)
④对于任意一个结点,从该结点到其所有NULL结点的简单路径,均包含相同数量的黑色结点
(简单路径:从根走到空的、没有回退的路径)。
通过4条规则,实现 "最长路径 ≤ 2 * 最短路径"。
极端场景下:
假设每条路径有h个黑结点,
最长路径 = 2*h个结点,一黑一红排列,
最短路径 = h个结点,全黑。
即 "最长路径 ≤ 2 * 最短路径"


上图均为红黑树。
红黑树的效率
红黑树的增删查改效率是2logN ,比AVL树的logN稍高,但是时间复杂度都为O(logN)。但是红黑树通过颜色规则近似控制平衡,由于平衡控制的条件没有AVL树苛刻,所以旋转的次数会少很多。
总的来看,红黑树的效率还是比AVL树稍高。
红黑树的模拟实现
插入
1.按照搜索树的规则插入。
2.空树插入,新结点必须为黑色;非空树插入,新结点必须为红色。
插入新结点后:
若父亲为黑色,未违反规则,插入结束;
若父亲为红色,违反规则③,此时要进行分析,并变色。
情况Ⅰ、Ⅱ起始图
如图:
插入了新结点4违反了规则③,其父亲结点6为红色,则祖父结点10必定为黑色(规则③),叔叔结点15黑、红、空皆可。
情况Ⅰ(变色)
若叔叔为红,让父亲和叔叔变黑,为了保持规则④,再让祖父变红。

再把祖父变为cur,继续按照过程Ⅰ进行更新,直到parent为黑色,就停止更新。

若祖父结点就是根结点,要把变红的祖父结点再变回黑色(如下图情况)。
→ 
情况Ⅱ(单旋+变色)
①叔叔为空
则cur必定为新增结点而不是更新上来的结点。
左边高,则右旋。
,右旋转后父亲和祖父变色

②叔叔为黑
则说明cur原本为黑,在cur的子树下插入了新结点后,由情况Ⅰ更新上来之后为红色的结点。
hb为子树中黑色节点的个数。
cur插入新结点后
cur的左右子树变为hb个黑色结点
左边黑色结点多,进行右单旋

大致结构如图,则右单旋
大致结构如图,则左单旋(略)
再变色
此时红黑树已平衡
情况Ⅲ(双旋+变色)
cur为红,parent为红,uncle为黑或不存在。
①叔叔为空
则cur一定为新增结点。
如图,有一个折点,就需要旋转两次,即双旋。
类似的左右结构,则 p 左旋,再 g 右旋
类似的右左结构,则 p 右旋,再 g 左旋
parent左旋,再祖父右旋
再进行变色,cur变黑,grandfather变红
此时红黑树已平衡
②叔叔为黑
与情况Ⅱ同理,此时说明cur是由情况Ⅰ更新上来的,而不是新增结点。
在原树下添加子树的示意即可,操作逻辑与①叔叔为空相同
查找
同搜索树的搜索方法。
代码示例:
cpp
#pragma once
#include<iostream>
using namespace std;
enum Colour
{
RED,
BLACK
};
template<class K, class V>
struct RBTreeNode
{
pair<K, V> _kv;
RBTreeNode<K, V>* _left;
RBTreeNode<K, V>* _right;
RBTreeNode<K, V>* _parent;
Colour _col;
RBTreeNode(const pair<K, V>& kv)
:_kv(kv)
, _left(nullptr)
,_right(nullptr)
,_parent(nullptr)
{ }
};
template<class K,class V>
class RBTree
{
typedef RBTreeNode<K, V> Node;
public:
bool Insert(const pair<K, V>& kv)
{
if (_root == nullptr)
{
_root = new Node(kv);
_root->_col = BLACK;
return true;
}
Node* parent = nullptr;
Node* cur = nullptr;
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;
}
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 && uncle->_col == RED)
{
//变色
parent->_col == uncle->_col == BLACK;
grandfather->_col = RED;
//继续向上处理
cur = grandfather;
parent = cur->_parent;
}
//叔叔不存在或者叔叔存在且为黑
else
{
if (cur == parent->_left)//单旋+变色
{
//父亲变黑,祖父变红
RotateR(grandfather);
parent->_col = BLACK;
grandfather->_col = RED;
}
else//双旋+变色
{
RotateL(parent);
RotateR(grandfather);
cur->_col = BLACK;
grandparent->_col = RED;
}
break;
}
}
else//上述镜像操作
{
Node* uncle = grandfather->_left;
//叔叔存在且为红
if (uncle && uncle->_col == RED)
{
parent->_col = uncle->_col = BLACK;
grandfather->col = RED;
//继续向上更新
cur = grandfather;
parent = cur->_parent;
}
//叔叔不存在,或叔叔为黑
else
{
if (cur == parent->_parent)
{
RotateL(grandfather);
parent->_col = BLACK;
grandfather - _col = RED;
}
else
{
RotateR(parent);
RotateL(grandfather);
cur->_col = BLACK;
grandfather->_col = RED;
}
break;
}
}
}
_root->_col == BLACK;
return true;
}
void InOrder()
{
_InOrder(_root);
cout << endl;
}
//根据各路径的黑色结点数量来检验
bool check(Node* root, int blackNum, const int refNum)
{
//前序遍历
if (root == nullptr)//递归遍历走到了一条路径的进程
{
if (refNum != blackNum)
{
cout << "存在黑色结点数量不相等的路径" << endl;
return false;
}
return true;
}
//孩子和父亲都为红色
if (root->_col == RED && root->_parent->_col == RED)
{
cout << root->_kv.first << "存在连续的红色结点" << endl;
return false;
}
//孩子为黑色
if (root->_col == BLACK)
{
blackNum++;
}
//左右子树都要检查
return check(root->_left, blackNum, refNum)
&& check(root->_right, blackNum, refNum);
}
bool IsBalance()
{
if (_root == nullptr)
{
return true;
}
if (_root->_col == RED)
{
return false;
}
//获取参考值
int refNum = 0;
Node* cur = _root;
//只挑选一条路进行遍历即可
while (cur)
{
if (cur->_col == BLACK)
{
++refNum;
}
cur = cur->_left;
}
return check(_root, 0, refNum);
}
int Height()
{
return _Height(_root);
}
int Size()
{
return _Size(_root);
}
//使用搜索树的Find即可
Node* Find(const K& key)
{
Node* cur = _root;
while (cur)
{
if (cur->_kv.first < key)
{
cur = cur->_right;
}
else if (cur->_kv.first > key)
{
cur = cur->_left;
}
else
{
return cur;
}
}
return nullptr;
}
protected:
//结点个数
int _Size(Node* root)
{
if (root == nullptr)
{
return 0;
}
//左树结点+右树结点+自己本身
return _Size(root->left) + _Size(root->_right) + 1;
}
//高度
int _Height(Node* root)
{
if (root == nullptr)
{
return 0;
}
int leftHeight = _Height(root->_left);
int rightHeight = _Height(root->_right);
//哪边高返回哪边,+1是加的根结点
return leftHeight > rightHeight ? left + 1 : rightHeight + 1;
}
void RotateR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
//先连接parent与LR结点
parent->_left = subLR;
if (subLR)//不为空时才调整其父指针
{
subLR->_parent = parent;
}
//记录祖父结点
Node* ppNode = parent->_parent;
//L结点变为根结点,根结点变为L的右结点
subL->_right = parent;
parent->_parent = subL;
//旋转完成
//处理祖父结点
if (parent == _root)//若父结点为根,说明祖父结点为空
{
_root = subL;
subL->_parent = nullptr;
}
else//祖父结点存在,连接祖父结点和父结点
{
if (ppNode->_left == parent)
{
ppNode->_left == subL;
}
else
{
ppNode->_right = subL;
}
subL->_parent = ppNode;
}
}
void RotateR(Node* parent)
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
parent->_left = subRL;
if (subRL)
{
subRL->_parent = parent;
}
Node* ppNode = parent->_parent;
subR->_left = parent;
parent->_parent = subR;
if (ppNode == nullptr)
{
_root = sub;
subR->_parent = nullptr;
}
else
{
if (parent == ppNode->_left)
{
ppNode->_left = subR;
}
else
{
ppNode->_right = subR;
}
subR->_parent = ppNode;
}
}
//中序遍历
void _InOrder(Node* root)
{
if (root == nullptr)
{
return;
}
_InOrder(root->_left);
cout << root->_kv.first << ":" << _root->_kv.second << endl;
_InOrder(root->_right);
}
private:
Node* _root = nullptr;
};
补充
unordered_set
cpp
template< class Key,
class Hash = hash<Key>,
class Pred = equal_to<Key>,
class ALLoc = allocator<key>
>class unordered_set;
a.默认要求key支持转换为整型。不支持 or 有特殊需求得自行实现。
b.默认要求key支持相等的比较。不支持 or 有特殊需求得自行实现。
c.底层存储数据的内存由空间配置器申请。有特殊需求可以自行实现内存池。
d.底层由哈希桶实现,增删查平均效率为O( 1 ),迭代器遍历无序。
unordered_set与set的差异
①对Key要求不一样。set要求Key支持小于比较,unordered_set要求Key支持转成整型且支持等于比较(原因在后续文章会说)。
②迭代器有差异。set是双向迭代器,unordered_set是单向迭代器;set底层是红黑树,迭代器遍历有序且去重,unordered_set底层是哈希表,迭代器遍历无序且去重。
③性能有差异。大多数情况下,unordered_set的效率更高。红黑树增删查改的效率为O(logN),哈希表增删查改的效率为O(1)。
unordered_map
与unordered的特点类似。
与map的差异也和unordered_set与set的差异类似。
不过多赘述。
❤~~本文完结!!感谢观看!!接下来更精彩!!欢迎来我博客做客~~❤