一、关联式容器
vector、list、deque、forward_list(C++11)等,这些容器统称为序列式容器,因为其底层为线性序列的数据结构,里面存储的是元素本身。
关联式容器也是用来存储数据的,与序列式容器不同的是,其里面存储的是<key, value>结构的键值对,在数据检索时比序列式容器效率更高。
二、键值对
用来表示具有一一对应关系的一种结构,该结构中一般只包含两个成员变量key和value,key代表键值,value表示与key对应的信息。
SGI-STL中关于键值对的定义:
template <class T1, class T2>
struct pair
{
typedef T1 first_type;
typedef T2 second_type;
T1 first;
T2 second;
pair(): first(T1()), second(T2())
{}
pair(const T1& a, const T2& b): first(a), second(b)
{}
};
三、树形结构的关联式容器
根据应用场景的不同,STL总共实现了两种不同结构的管理式容器:树型结构与哈希结构。树型结构的关联式容器主要有四种:map、set、multimap、multiset。这四种容器的共同点是:使用平衡搜索树(即红黑树)作为其底层结果,容器中的元素是一个有序的序列。下面一依次介绍每一个容器。
3.1 set
3.1.1 set的介绍
3.1.2 set的使用
- set的构造
2. set的迭代器
3. set修改操作
函数声明 | 功能介绍 |
---|---|
pair<iterator,bool> insert (const value_type& x ) | 在set中插入元素x,实际插入的是<x, x>构成的键值对,如果插入成功,返回<该元素在set中的位置,true>,如果插入失败,说明x在set中已经存在,返回<x在set中的位置,false> |
void erase ( iterator position ) | 删除set中position位置上的元素 |
size_type erase ( constkey_type& x ) | 删除set中值为x的元素,返回删除的元素的个数 |
void erase ( iterator first,iterator last ) | 删除set中[first, last)区间中的元素 |
void swap (set<Key,Compare,Allocator>&st ); | 交换set中的元素 |
void clear ( ) | 将set中的元素清空iterator find ( const |
key_type& x ) const | 返回set中值为x的元素的位置 |
size_type count ( constkey_type& x ) const | 返回set中值为x的元素的个数 |
- 举例
#include <set>
void TestSet()
{
// 用数组array中的元素构造set
int array[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0, 1, 3, 5, 7, 9, 2, 4,
6, 8, 0 };
set<int> s(array, array+sizeof(array)/sizeof(array));
cout << s.size() << endl;
// 正向打印set中的元素,从打印结果中可以看出:set可去重
for (auto& e : s)
cout << e << " ";
cout << endl;
// 使用迭代器逆向打印set中的元素
for (auto it = s.rbegin(); it != s.rend(); ++it)
cout << *it << " ";
cout << endl;
// set中值为3的元素出现了几次
cout << s.count(3) << endl;
}
3.2 map
3.1.1 map的介绍
3.1.2 map的使用
-
map的构造
-
map的迭代器
-
map的容量与元素访问
当key不在map中时,通过operator获取对应value时会发生什么问题?
insert的返回值为:pair<iterator, bool>
注意:在元素访问时,有一个与operator[]类似的操作at()(该函数不常用)函数,都是通过key找到与key对应的value然后返回其引用,不同的是:当key不存在时,operator[]用默认value与key构造键值对然后插入,返回该默认value,at()函数直接抛异常. -
map中元素的修改
函数声明 | 功能简介 |
---|---|
pair<iterator,bool> insert (const value_type& x ) | 在map中插入键值对x,注意x是一个键值对,返回值也是键值对:iterator代表新插入元素的位置,bool代表释放插入成功 |
void erase ( iterator position ) | 删除position位置上的元素 |
size_type erase ( constkey_type& x ) | 删除键值为x的元素 |
void erase ( iterator first,iterator last ) | 删除[first, last)区间中的元素 |
void swap (map<Key,T,Compare,Allocator>&mp ) | 交换两个map中的元素 |
void clear ( ) | 将map中的元素清空 |
iterator find ( const key_type& x) | 在map中插入key为x的元素,找到返回该元素的位置的迭代器,否则返回end |
const_iterator find ( constkey_type& x ) const | 在map中插入key为x的元素,找到返回该元素的位置的const迭代器,否则返回cend |
size_type count ( constkey_type& x ) const | 返回key为x的键值在map中的个数,注意map中key是唯一的,因此该函数的返回值要么为0,要么为1,因此也可以用该函数来检测一个key是否在map中. |
举例:
#include <string>
#include <map>
void TestMap()
{
map<string, string> m;
// 向map中插入元素的方式:
// 将键值对<"peach","桃子">插入map中,用pair直接来构造键值对
m.insert(pair<string, string>("peach", "桃子"));
// 将键值对<"peach","桃子">插入map中,用make_pair函数来构造键值对m.insert(make_pair("banan", "香蕉"));
// 借用operator[]向map中插入元素
/*
operator[]的原理是:
用<key, T()>构造一个键值对,然后调用insert()函数将该键值对插入到map中
如果key已经存在,插入失败,insert函数返回该key所在位置的迭代器
如果key不存在,插入成功,insert函数返回新插入元素所在位置的迭代器
operator[]函数最后将insert返回值键值对中的value返回
*/
// 将<"apple", "">插入map中,插入成功,返回value的引用,将“苹果”赋值给该引
用结果,
m["apple"] = "苹果";
// key不存在时抛异常
//m.at("waterme") = "水蜜桃";
cout << m.size() << endl;
// 用迭代器去遍历map中的元素,可以得到一个按照key排序的序列
for (auto& e : m)
cout << e.first << "--->" << e.second << endl;
cout << endl;
// map中的键值对key一定是唯一的,如果key存在将插入失败
auto ret = m.insert(make_pair("peach", "桃色"));
if (ret.second)
cout << "<peach, 桃色>不在map中, 已经插入" << endl;
else
cout << "键值为peach的元素已经存在:" << ret.first->first << "--->"
<< ret.first->second <<" 插入失败"<< endl;
// 删除key为"apple"的元素
m.erase("apple");
if (1 == m.count("apple"))
cout << "apple还在" << endl;
else
cout << "apple被吃了" << endl;
}
【总结】
- map中的的元素是键值对
- map中的key是唯一的,并且不能修改
- 默认按照小于的方式对key进行比较
- map中的元素如果用迭代器去遍历,可以得到一个有序的序列
- map的底层为平衡搜索树(红黑树),查找效率比较高 O ( l o g 2 N ) O(log_2 N) O(log2N)
- 支持[]操作符,operator[]中实际进行插入查找
3.3 multiset
multiset文档介绍
注意:
- multiset中再底层中存储的是<value, value>的键值对
- mtltiset的插入接口中只需要插入即可
- 与set的区别是,multiset中的元素可以重复,set是中value是唯一的
- 使用迭代器对multiset中的元素进行遍历,可以得到有序的序列
- multiset中的元素不能修改
- 在multiset中找某个元素,时间复杂度为 O ( l o g 2 N ) O(log_2 N) O(log2N)
- multiset的作用:可以对元素进行排序
3.4 multimap
multimap文档介绍
注意:
- multimap和map的唯一不同就是:map中的key是唯一的,而multimap中key是可以重复的。
- multimap中的元素默认将key按照小于来比较
- multimap中没有重载operator[]操作
四、底层结构
前面对map/multimap/set/multiset进行了简单的介绍,在其文档介绍中发现,这几个容器有个共同点是:其底层都是按照二叉搜索树来实现的,但是二叉搜索树有其自身的缺陷,假如往树中
插入的元素有序或者接近有序,二叉搜索树就会退化成单支树,时间复杂度会退化成O(N),因此map、set等关联式容器的底层结构是对二叉树进行了平衡处理,即采用平衡树来实现。
4.1 AVL树
4.1.1 AVL树的概念
二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当于在顺序表中搜索元素,效率低下。因此,两位俄罗斯的数学家G.M.Adelson-Velskii
和E.M.Landis在1962年发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。
一棵AVL树或者是空树,或者是具有以下性质的二叉搜索树:
- 它的左右子树都是AVL树
- 左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1)
如果一棵二叉搜索树是高度平衡的,它就是AVL树。如果它有n个结点,其高度可保持在 O ( l o g 2 n ) O(log_2 n) O(log2n),搜索时间复杂度O( l o g 2 n log_2 n log2n)。
4.1.2 AVL树节点的定义
template<class T>
struct AVLTreeNode
{
AVLTreeNode(const T& data)
: _pLeft(nullptr), _pRight(nullptr), _pParent(nullptr)
, _data(data), _bf(0)
{}
AVLTreeNode<T>* _pLeft; // 该节点的左孩子
AVLTreeNode<T>* _pRight; // 该节点的右孩子
AVLTreeNode<T>* _pParent; // 该节点的双亲
T _data;
int _bf; // 该节点的平衡因子
};
4.1.3 AVL树的插入
AVL树就是在二叉搜索树的基础上引入了平衡因子,因此AVL树也可以看成是二叉搜索树。那么
AVL树的插入过程可以分为两步:
- 按照二叉搜索树的方式插入新节点
- 调整节点的平衡因子
bool Insert(const T& data)
{
// 1. 先按照二叉搜索树的规则将节点插入到AVL树中
// ...
// 2. 新节点插入后,AVL树的平衡性可能会遭到破坏,此时就需要更新平衡因子,并检测是否破坏了AVL树的平衡性
/*
pCur插入后,pParent的平衡因子一定需要调整,在插入之前,pParent的平衡因子分为三种情况:-1,0, 1, 分以下两种情况:
1. 如果pCur插入到pParent的左侧,只需给pParent的平衡因子-1即可
2. 如果pCur插入到pParent的右侧,只需给pParent的平衡因子+1即可
此时:pParent的平衡因子可能有三种情况:0,正负1, 正负2
1. 如果pParent的平衡因子为0,说明插入之前pParent的平衡因子为正负1,插入后被调整成0,此时满足AVL树的性质,插入成功
2. 如果pParent的平衡因子为正负1,说明插入前pParent的平衡因子一定为0,插入后被更新成正负1,此时以pParent为根的树的高度增加,需要继续向上更新
3. 如果pParent的平衡因子为正负2,则pParent的平衡因子违反平衡树的性质,需要对其进行旋转处理
*/
while (pParent)
{
// 更新双亲的平衡因子
if (pCur == pParent->_pLeft)
pParent->_bf--;
else
pParent->_bf++;
// 更新后检测双亲的平衡因子
if (0 == pParent->_bf)
{
break;
}
else if (1 == pParent->_bf || -1 == pParent->_bf)
{
// 插入前双亲的平衡因子是0,插入后双亲的平衡因为为1 或者 -1 ,说明以双亲为根的二叉树
// 的高度增加了一层,因此需要继续向上调整
pCur = pParent;
pParent = pCur->_pParent;
}
else
{
// 双亲的平衡因子为正负2,违反了AVL树的平衡性,需要对以pParent
// 为根的树进行旋转处理
if(2 == pParent->_bf)
{
// ...
}
else
{
// ...
}
}
}
return true;
}
4.1.4 AVL树的旋转
引出旋转
如果在一棵原本是平衡的AVL树中插入一个新节点,可能造成不平衡,此时必须调整树的结构,使之平衡化。根据节点插入位置的不同,AVL树的旋转分为四种:
-
右单旋
-
左单旋
-
单旋平衡因子的更新
旋转完之后,parent,和subR的平衡因子都变为0。不管是左旋还是右旋。
示例代码:
/*
上图在插入前,AVL树是平衡的,新节点插入到30的左子树(注意:此处不是左孩子)中,30左子树增加了一层,导致以60为根的二叉树不平衡,要让60平衡,只能将60左子树的高度减少一层,右子树增加一层,即将左子树往上提,这样60转下来,因为60比30大,只能将其放在30的右子树,而如果30有右子树,右子树根的值一定大于30,小于60,只能将其放在60的左子树,旋转完成后,更新节点的平衡因子即可。在旋转过程中,有以下几种情况需要考虑:
1. 30节点的右孩子可能存在,也可能不存在
2. 60可能是根节点,也可能是子树
如果是根节点,旋转完成后,要更新根节点
如果是子树,可能是某个节点的左子树,也可能是右子树
*/
void _RotateR(PNode pParent)
{
// pSubL: pParent的左孩子
// pSubLR: pParent左孩子的右孩子,注意:该
PNode pSubL = pParent->_pLeft;
PNode pSubLR = pSubL->_pRight;
// 旋转完成之后,30的右孩子作为双亲的左孩子
pParent->_pLeft = pSubLR;
// 如果30的左孩子的右孩子存在,更新亲双亲
if(pSubLR)
pSubLR->_pParent = pParent;
// 60 作为 30的右孩子
pSubL->_pRight = pParent;
// 因为60可能是棵子树,因此在更新其双亲前必须先保存60的双亲
PNode pPParent = pParent->_pParent;
// 更新60的双亲
pParent->_pParent = pSubL;
// 更新30的双亲
pSubL->_pParent = pPParent;
// 如果60是根节点,根新指向根节点的指针
if(NULL == pPParent)
{
_pRoot = pSubL;
pSubL->_pParent = NULL;
}
else
{
// 如果60是子树,可能是其双亲的左子树,也可能是右子树
if(pPParent->_pLeft == pParent)
pPParent->_pLeft = pSubL;
else
pPParent->_pRight = pSubL;
}
// 根据调整后的结构更新部分节点的平衡因子
pParent->_bf = pSubL->_bf = 0;
}
-
左右双旋
-
右左双旋
参考左右双旋。
- 双旋平衡因子的更新
双旋和单旋不一样,双旋平衡因子的更新有三种情况:
通过上图可知,
情况一:新节点插入在c节点下面,会导致60的平衡因子变为1, 30,90不变,分别为2,-1。旋转后60, 90变为0,30变为-1.
情况二:新节点插入在b节点下面,会导致60的平衡因子变为-1, 30,90不变,分别为2,-1。旋转后30, 90变为0,90变为1.
情况三:60 是新插入节点,30,90,60的平衡因子分别为2,-1,1,旋转后都变为0.
示例:
// 旋转之前,60的平衡因子可能是-1/0/1,旋转完成之后,根据情况对其他节点的平衡因子进行调整
void _RotateLR(PNode pParent)
{
PNode pSubL = pParent->_pLeft;
PNode pSubLR = pSubL->_pRight;
// 旋转之前,保存pSubLR的平衡因子,旋转完成之后,需要根据该平衡因子来调整其他节点的平衡因子
int bf = pSubLR->_bf;
// 先对30进行左单旋
_RotateL(pParent->_pLeft);
// 再对90进行右单旋
_RotateR(pParent);
if(1 == bf)
pSubL->_bf = -1;
else if(-1 == bf)
pParent->_bf = 1;
}
总结:
假如以pParent为根的子树不平衡,即pParent的平衡因子为2或者-2,分以下情况考虑
6. pParent的平衡因子为2,说明pParent的右子树高,设pParent的右子树的根为pSubR
- 当pSubR的平衡因子为1时,执行左单旋
- 当pSubR的平衡因子为-1时,执行右左双旋
- pParent的平衡因子为-2,说明pParent的左子树高,设pParent的左子树的根为pSubL
- 当pSubL的平衡因子为-1是,执行右单旋
- 当pSubL的平衡因子为1时,执行左右双旋
旋转完成后,原pParent为根的子树个高度降低,已经平衡,不需要再向上更新。
4.1.5 AVL树的验证
AVL树是在二叉搜索树的基础上加入了平衡性的限制,因此要验证AVL树,可以分两步:
- 验证其为二叉搜索树
如果中序遍历可得到一个有序的序列,就说明为二叉搜索树 - 验证其为平衡树
- 每个节点子树高度差的绝对值不超过1(注意节点中如果没有平衡因子)
- 节点的平衡因子是否计算正确
bool IsBalance()
{
return _IsBalance(_root);
}
int Height(Node* root)
{
if (root == NULL)
return 0;
int leftHeight = Height(root->_left);
int rightHeight = Height(root->_right);
return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
}
bool _IsBalance(Node* root)
{
// 空树也是AVL树
if (root == NULL)
return true;
// 对当前树进行检查
// 计算pRoot节点的平衡因子:即pRoot左右子树的高度差
int leftHeight = Height(root->_left);
int rightHeight = Height(root->_right);
// 如果计算出的平衡因子与pRoot的平衡因子不相等,则一定不是AVL树
if (rightHeight-leftHeight != root->_bf)
{
cout << root->_kv.first << "现在是:"<<root->_bf<<endl;
cout << root->_kv.first << "应该是:" << rightHeight - leftHeight << endl;
return false;
}
// pRoot平衡因子的绝对值超过1,则一定不是AVL树
return abs(rightHeight - leftHeight) < 2
&& _IsBalance(root->_left)
&& _IsBalance(root->_right);
}
4.1.6 AVL树的性能
AVL树是一棵绝对平衡的二叉搜索树,其要求每个节点的左右子树高度差的绝对值都不超过1,这样可以保证查询时高效的时间复杂度,即 l o g 2 ( N ) log_2 (N) log2(N)。但是如果要对AVL树做一些结构修改的操作,性能非常低下,比如:插入时要维护其绝对平衡,旋转的次数比较多,更差的是在删除时,有可能一直要让旋转持续到根的位置。因此:如果需要一种查询高效且有序的数据结构,而且数据的个数为静态的(即不会改变),可以考虑AVL树,但一个结构经常修改,就不太适合。
4.2 红黑树
4.2.1 红黑树的概念
红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路
径会比其他路径长出俩倍,因而是接近平衡的。
4.2.2 红黑树的性质
1. 每个结点不是红色就是黑色
2. 根节点是黑色的
3. 如果一个节点是红色的,则它的两个孩子结点是黑色的
4. 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均 包含相同数目的黑色结点
5. 每个叶子结点都是黑色的(此处的叶子结点指的是空结点)
为什么满足上面的性质,红黑树就能保证:其最长路径中节点个数不会超过最短路径节点个数的两倍?
答:因为根据上面的性质,最短节点全为黑节点,最长节点为一黑一红。所以最长路径不超过最短路径的二倍。
4.2.3 红黑树节点的定义
// 节点的颜色
enum Color
{
RED,
BLACK
};
// 红黑树节点的定义
template<class ValueType>
struct RBTreeNode
{
RBTreeNode(const ValueType& data = ValueType(),Color color = RED)
: _pLeft(nullptr),
_pRight(nullptr),
_pParent(nullptr),
_data(data),
_color(color)
{}
RBTreeNode<ValueType>* _pLeft; // 节点的左孩子
RBTreeNode<ValueType>* _pRight; // 节点的右孩子
RBTreeNode<ValueType>* _pParent; // 节点的双亲(红黑树需要旋转,为了实现简单给出该字段)
ValueType _data; // 节点的值域
Color _color; // 节点的颜色
};
在节点的定义中,为什么要将节点的默认颜色给成红色的?
答:因为这样破坏树的结构的的代价最小,容易恢复。
4.2.4 红黑树的插入操作
红黑树是在二叉搜索树的基础上加上其平衡限制条件,因此红黑树的插入可分为两步:
- 按照二叉搜索的树规则插入新节点
template<class ValueType>
class RBTree
{
//……
bool Insert(const ValueType& data)
{
PNode& pRoot = GetRoot();
if (nullptr == pRoot)
{
pRoot = new Node(data, BLACK);
// 根的双亲为头节点
pRoot->_pParent = _pHead;
_pHead->_pParent = pRoot;
}
else
{
// 1. 按照二叉搜索的树方式插入新节点
// 2. 检测新节点插入后,红黑树的性质是否造到破坏,
// 若满足直接退出,否则对红黑树进行旋转着色处理
}
// 根节点的颜色可能被修改,将其改回黑色
pRoot->_color = BLACK;
_pHead->_pLeft = LeftMost();
_pHead->_pRight = RightMost();
return true;
}
private:
PNode& GetRoot(){ return _pHead->_pParent;}
// 获取红黑树中最小节点,即最左侧节点
PNode LeftMost();
// 获取红黑树中最大节点,即最右侧节点
PNode RightMost();
private:
PNode _pHead;
};
- 检测新节点插入后,红黑树的性质是否造到破坏
因为新节点的默认颜色是红色,因此:如果其双亲节点的颜色是黑色,没有违反红黑树任何性质,则不需要调整;但当新插入节点的双亲节点颜色为红色时,就违反了性质三不能有连在一起的红色节点,此时需要对红黑树分情况来讨论:
约定:cur为当前节点,p为父节点,g为祖父节点,u为叔叔节点
- 情况一: cur为红,p为红,g为黑,u存在且为红
- 情况二: cur为红,p为红,g为黑,u不存在/u存在且为黑(p在g的左边, cur在p的左边)
类似的, 当(p在g的右边, cur在p的右边)时进行左单旋。
情况三: cur为红,p为红,g为黑,u不存在/u存在且为黑(p在g的左边,cur在p的右边)
类似的,当(p在g的右边,cur在p的左边)时,先对p进行右单旋,在对g进行右单旋。
示例代码:
//4.控制平衡
//父亲存在且为红
while (parent && parent->_col == RED)
{
Node* grandfather = parent->_pParent;
// g
// p u
//c
// g
// p u
// c
if (parent == grandfather->_pLeft)
{
Node* uncle = grandfather->_pRight;
//叔叔存在且为红
if (uncle && uncle->_col == RED)
{
parent->_col = uncle->_col = BLACK;
grandfather->_col = RED;
cur = grandfather;
parent = cur->_pParent;
}
//叔叔不存在或者存在且为黑
else
{
if (cur == parent->_pLeft)
{
//单旋
RotateR(grandfather);
parent->_col = BLACK;
grandfather->_col = RED;
}
else
{
//双旋
RotateLR(grandfather);
cur->_col = BLACK;
grandfather->_col = RED;
}
break;
}
}
// g
// u p
// c
// g
// u p
// c
else
{
Node* uncle = grandfather->_pLeft;
//叔叔存在且为红
if (uncle && uncle->_col == RED)
{
parent->_col = uncle->_col = BLACK;
grandfather->_col = RED;
cur = grandfather;
parent = cur->_pParent;
}
//叔叔不存在或者存在且为黑
else
{
if (cur == parent->_pRight)
{
//单旋
RotateL(grandfather);
parent->_col = BLACK;
grandfather->_col = RED;
}
else
{
//双旋
RotateRL(grandfather);
cur->_col = BLACK;
grandfather->_col = RED;
}
break;
}
}
}
_root->_col = BLACK;
return make_pair(_iterator(newNode), true);
}
//右旋
void RotateR(Node* parent)
{
Node* subL = parent->_pLeft;
Node* subLR = subL->_pRight;
Node* parentParent = parent->_pParent;
//链接左右节点和父亲
parent->_pLeft = subLR;
parent->_pParent = subL;
if (subLR)
{
subLR->_pParent = parent;
}
subL->_pRight = parent;
if (_root == parent)
{
_root = subL;
subL->_pParent = nullptr;
}
else
{
if (parentParent->_pLeft == parent)
{
parentParent->_pLeft = subL;
}
else
{
parentParent->_pRight = subL;
}
subL->_pParent = parentParent;
}
}
//左旋
void RotateL(Node* parent)
{
Node* subR = parent->_pRight;
Node* subRL = subR->_pLeft;
Node* parentParent = parent->_pParent;
//链接左右节点和父亲
parent->_pRight = subRL;
subR->_pLeft = parent;
if (subRL)
{
subRL->_pParent = parent;
}
parent->_pParent = subR;
if (parent == _root)
{
_root = subR;
subR->_pParent = nullptr;
}
else
{
if (parentParent->_pLeft == parent)
{
parentParent->_pLeft = subR;
}
else
{
parentParent->_pRight = subR;
}
subR->_pParent = parentParent;
}
}
4.2.5 红黑树的验证
红黑树的检测分为两步:
- 检测其是否满足二叉搜索树(中序遍历是否为有序序列)
- 检测其是否满足红黑树的性质
//判断红黑树是否平衡
bool IsBalance()
{
//判断根节点颜色
if (_root && _root->_col == RED)
{
cout << "根节点为红色" << endl;
return false;
}
//判断各路径黑色节点是否一致
Node* left = _root;
int BaseValue = 0;//基准值
int BlackNum = 0;
while (left)
{
if (left->_col == BLACK)
{
BaseValue++;
}
left = left->_pLeft;
}
return _IsBalance(_root, BaseValue, BlackNum);
}
bool _IsBalance(Node* root, int BaseValue, int BlackNum)
{
if (root == nullptr)
{
//与基准值相比较
if (BaseValue != BlackNum)
{
cout << "存在路径黑色节点的数量不相等" << endl;
return false;
}
return true;
}
//判断是否有连续的红节点
if (root->_col == RED && root->_pParent->_col == RED)
{
cout << "出现连续的红节点" << endl;
return false;
}
//记录黑色节点的个数
if (root->_col == BLACK)
{
BlackNum++;
}
return _IsBalance(root->_pLeft, BaseValue, BlackNum)
&& _IsBalance(root->_pRight, BaseValue, BlackNum);
}
4.2.6 红黑树与AVL树的比较
红黑树和AVL树都是高效的平衡二叉树,增删改查的时间复杂度都是O( l o g 2 N log_2 N log2N),红黑树不追求绝对平衡,其只需保证最长路径不超过最短路径的2倍,相对而言,降低了插入和旋转的次数,所以在经常进行增删的结构中性能比AVL树更优,而且红黑树实现比较简单,所以实际运用中红黑树更多。
4.3 红黑树模拟实现STL中的map和set
4.3.1 红黑树的迭代器
- begin()与end()
begin()可以放在红黑树中最小节点(即最左侧节点)的位
置,end()放在最大节点(最右侧节点)的下一个位置,即空位置。
_iterator begin()
{
Node* left = _root;
while (left && left->_pLeft)
{
left = left->_pLeft;
}
return _iterator(left);
}
_iterator end()
{
return _iterator(nullptr);
}
- operator++()与operator–()
template<class T, class Ref, class Ptr>
class __RBTreeIterator
{
public:
typedef RBTreeNode<T> Node;
typedef __RBTreeIterator<T, Ref, Ptr> Self;
Node* _node;
__RBTreeIterator(Node* node)
:_node(node)
{}
//其他代码
//...
//默认父亲的左孩子已经访问了,因为最先访问左孩子,在访问父亲,最后访问右节点
Self& operator++()
{
//判断右孩子是否为空,如果不为空,则找到有孩子的最左节点
if (_node->_pRight)
{
Node* min = _node->_pRight;
while (min->_pLeft)
{
min = min->_pLeft;
}
_node = min;
}
//如果为空,说明,右孩子已经访问完了,或者没有右孩子,说明,该节点的左右孩子包括自己都已经访问完了
else
{
Node* cur = _node;
Node* parent = cur->_pParent;
//如果父亲存在,且当前节点为父亲的右节点,说明该父亲节点已经被访问了,直接回退到父亲的父亲,以此类推,知道父亲为空。否则访问该父亲节点。
while (parent && cur == parent->_pRight)
{
cur = parent;
parent = parent->_pParent;
}
_node = parent;
}
return *this;
}
//与++类似
Self& operator--()
{
if (_node->_pLeft)
{
Node* max = _node->_pLeft;
while (max->_pRight)
{
max = max->_pRight;
}
_node = max;
}
else
{
Node* cur = _node;
Node* parent = cur->_pParent;
while (parent && cur == parent->_pLeft)
{
cur = parent;
parent = cur->_pParent;
}
_node = parent;
}
return *this;
//其他代码
//...
4.3.2 改造红黑树
- 因为关联式容器中存储的是<key, value>的键值对,因此
- k为key的类型,
- ValueType: 如果是map,则为pair<K, V>; 如果是set,则为k
- KeyOfValue: 通过value来获取key的一个仿函数类
示例代码:
#pragma once
#include<iostream>
#include<vector>
#include<time.h>
using namespace std;
enum Color
{
RED,
BLACK
};
template<class T>
struct RBTreeNode
{
RBTreeNode<T>* _pLeft;
RBTreeNode<T>* _pRight;
RBTreeNode<T>* _pParent;
T _data;
Color _col;
RBTreeNode(const T& data)
:_pLeft(nullptr)
, _pRight(nullptr)
, _pParent(nullptr)
, _data(data)
, _col(RED)
{}
};
template<class T, class Ref, class Ptr>
class __RBTreeIterator
{
public:
typedef RBTreeNode<T> Node;
typedef __RBTreeIterator<T, Ref, Ptr> Self;
Node* _node;
__RBTreeIterator(Node* node)
:_node(node)
{}
Ref operator*()
{
return _node->_data;
}
Ptr operator->()
{
return &(_node->_data);
}
Self& operator++()
{
if (_node->_pRight)
{
Node* min = _node->_pRight;
while (min->_pLeft)
{
min = min->_pLeft;
}
_node = min;
}
else
{
Node* cur = _node;
Node* parent = cur->_pParent;
while (parent && cur == parent->_pRight)
{
cur = parent;
parent = parent->_pParent;
}
_node = parent;
}
return *this;
}
Self& operator--()
{
if (_node->_pLeft)
{
Node* max = _node->_pLeft;
while (max->_pRight)
{
max = max->_pRight;
}
_node = max;
}
else
{
Node* cur = _node;
Node* parent = cur->_pParent;
while (parent && cur == parent->_pLeft)
{
cur = parent;
parent = cur->_pParent;
}
_node = parent;
}
return *this;
}
bool operator!=(const Self& s) const
{
return _node != s._node;
}
bool operator==(const Self& s) const
{
return _node == s._node;
}
};
template<class K, class T, class KeyOfT>
class RBTree
{
typedef RBTreeNode<T> Node;
public:
typedef __RBTreeIterator<T, T&, T*> _iterator;
typedef __RBTreeIterator<T, const T&,const T*> const_iterator;
_iterator begin()
{
Node* left = _root;
while (left && left->_pLeft)
{
left = left->_pLeft;
}
return _iterator(left);
}
_iterator end()
{
return _iterator(nullptr);
}
//const_iterator begin()const
//{
// Node* left = _root;
// while (left && left->_pLeft)
// {
// left = left->_pLeft;
// }
// return const_iterator(left);
//}
//const_iterator end()const
//{
// return const_iterator(nullptr);
//}
RBTree()
:_root(nullptr)
{}
RBTree(RBTree<K, T, KeyOfT>& t)
{
_root = Copy(t._root);
}
RBTree<K, T, KeyOfT>& operator=(RBTree<K, T, KeyOfT> t)
{
swap(_root, t._root);
return *this;
}
~RBTree()
{
Destory(_root);
_root = nullptr;
}
_iterator Find(const K& key)
{
KeyOfT kot;
Node* cur = _root;
while (cur)
{
if (key < kot(cur->_data))
{
cur = cur->_pLeft;
}
else if (key > kot(cur->_data))
{
cur = cur->_pRight;
}
else
{
return _iterator(cur);
}
}
return end();
}
pair<_iterator, bool> Insert(const T& data)
{
//1.判断根节点是否为空
if (_root == nullptr)
{
_root = new Node(data);
_root->_col = BLACK;
return make_pair(_iterator(_root), true);
}
//2.遍历二叉树寻找插入的位置
KeyOfT kot;
Node* cur = _root;
Node* parent = nullptr;
while (cur)
{
if (kot(data) < kot(cur->_data))
{
parent = cur;
cur = cur->_pLeft;
}
else if (kot(data) > kot(cur->_data))
{
parent = cur;
cur = cur->_pRight;
}
else
{
return make_pair(_iterator(cur), false);
}
}
//3.插入节点
cur = new Node(data);
Node* newNode = cur;
cur->_col = RED;
if (kot(data) < kot(parent->_data))
{
parent->_pLeft = cur;
cur->_pParent = parent;
}
else
{
parent->_pRight = cur;
cur->_pParent = parent;
}
//4.控制平衡
//父亲存在且为红
while (parent && parent->_col == RED)
{
Node* grandfather = parent->_pParent;
// g
// p u
//c
// g
// p u
// c
if (parent == grandfather->_pLeft)
{
Node* uncle = grandfather->_pRight;
//叔叔存在且为红
if (uncle && uncle->_col == RED)
{
parent->_col = uncle->_col = BLACK;
grandfather->_col = RED;
cur = grandfather;
parent = cur->_pParent;
}
//叔叔不存在或者存在且为黑
else
{
if (cur == parent->_pLeft)
{
//单旋
RotateR(grandfather);
parent->_col = BLACK;
grandfather->_col = RED;
}
else
{
//双旋
RotateLR(grandfather);
cur->_col = BLACK;
grandfather->_col = RED;
}
break;
}
}
// g
// u p
// c
// g
// u p
// c
else
{
Node* uncle = grandfather->_pLeft;
//叔叔存在且为红
if (uncle && uncle->_col == RED)
{
parent->_col = uncle->_col = BLACK;
grandfather->_col = RED;
cur = grandfather;
parent = cur->_pParent;
}
//叔叔不存在或者存在且为黑
else
{
if (cur == parent->_pRight)
{
//单旋
RotateL(grandfather);
parent->_col = BLACK;
grandfather->_col = RED;
}
else
{
//双旋
RotateRL(grandfather);
cur->_col = BLACK;
grandfather->_col = RED;
}
break;
}
}
}
_root->_col = BLACK;
return make_pair(_iterator(newNode), true);
}
//打印红黑树
void InOrder()
{
_InOrder(_root);
}
//判断红黑树是否平衡
bool IsBalance()
{
//判断根节点颜色
if (_root && _root->_col == RED)
{
cout << "根节点为红色" << endl;
return false;
}
//判断各路径黑色节点是否一致
Node* left = _root;
int BaseValue = 0;
int BlackNum = 0;
while (left)
{
if (left->_col == BLACK)
{
BaseValue++;
}
left = left->_pLeft;
}
return _IsBalance(_root, BaseValue, BlackNum);
}
private:
Node* Copy(Node* root)
{
if (root == nullptr)
{
return nullptr;
}
Node* newRoot = new Node(root->_data);
newRoot->_col = root->_col;
newRoot->_pLeft = Copy(root->_pLeft);
newRoot->_pRight = Copy(root->_pRight);
if (newRoot->_pLeft)
{
newRoot->_pLeft->_pParent = newRoot;
}
if (newRoot->_pRight)
{
newRoot->_pRight->_pParent = newRoot;
}
return newRoot;
}
void Destory(Node* root)
{
if (root == nullptr)
{
return;
}
Destory(root->_pLeft);
Destory(root->_pRight);
delete root;
}
//测试代码
//bool _IsBalance(Node* root, int BaseValue, int BlackNum)
//{
// if (root == nullptr)
// {
// if (BaseValue != BlackNum)
// {
// cout << "存在路径黑色节点的数量不相等" << endl;
// return false;
// }
// return true;
// }
// //判断是否有连续的红节点
// if (root->_col == RED && root->_pParent->_col == RED)
// {
// cout << "出现连续的红节点" << endl;
// return false;
// }
// //记录黑色节点的个数
// if (root->_col == BLACK)
// {
// BlackNum++;
// }
// return _IsBalance(root->_pLeft, BaseValue, BlackNum)
// && _IsBalance(root->_pRight, BaseValue, BlackNum);
//}
//测试代码
中序遍历
//void _InOrder(Node* root)
//{
// if (root == nullptr)
// {
// return;
// }
// _InOrder(root->_pLeft);
// cout << root->_kv.first << ":" << root->_kv.second << endl;
// _InOrder(root->_pRight);
//}
//右旋
void RotateR(Node* parent)
{
Node* subL = parent->_pLeft;
Node* subLR = subL->_pRight;
Node* parentParent = parent->_pParent;
//链接左右节点和父亲
parent->_pLeft = subLR;
parent->_pParent = subL;
if (subLR)
{
subLR->_pParent = parent;
}
subL->_pRight = parent;
if (_root == parent)
{
_root = subL;
subL->_pParent = nullptr;
}
else
{
if (parentParent->_pLeft == parent)
{
parentParent->_pLeft = subL;
}
else
{
parentParent->_pRight = subL;
}
subL->_pParent = parentParent;
}
}
//左旋
void RotateL(Node* parent)
{
Node* subR = parent->_pRight;
Node* subRL = subR->_pLeft;
Node* parentParent = parent->_pParent;
//链接左右节点和父亲
parent->_pRight = subRL;
subR->_pLeft = parent;
if (subRL)
{
subRL->_pParent = parent;
}
parent->_pParent = subR;
if (parent == _root)
{
_root = subR;
subR->_pParent = nullptr;
}
else
{
if (parentParent->_pLeft == parent)
{
parentParent->_pLeft = subR;
}
else
{
parentParent->_pRight = subR;
}
subR->_pParent = parentParent;
}
}
//右左双旋
void RotateRL(Node* parent)
{
RotateR(parent->_pRight);
RotateL(parent);
}
//左右双旋
void RotateLR(Node* parent)
{
RotateL(parent->_pLeft);
RotateR(parent);
}
private:
Node* _root;
};
//红黑树测试代码
//void TestRBTree()
//{
// RBTree<int, int> t;
// vector<int> v;
// srand(time(0));
// int N = 1000;
// for (int i = 0; i < N; ++i)
// {
// v.push_back(rand() % 1000);
// //v.push_back(i);
// }
// for (auto e : v)
// {
// t.Insert(e);
// if (!t.IsBalance())
// {
// cout << "Insert" << e << endl;
// break;
// }
// }
// t.InOrder();
// cout << t.IsBalance() << endl;
//}
4.3.3 map的模拟实现
MyMap.hpp
#define _CRT_SECURE_NO_WARNINGS 1
#pragma once
#include "RBTree.h"
namespace sk
{
template<class K, class V>
class MyMap
{
public:
struct MapKeyOfT
{
const K& operator()(const pair<K, V>& kv)
{
return kv.first;
}
};
typedef typename RBTree<K, pair<K, V>, MapKeyOfT>::_iterator iterator;
iterator begin()
{
return _t.begin();
}
iterator end()
{
return _t.end();
}
pair<iterator, bool> insert(const pair<K, V>& kv)
{
return _t.Insert(kv);
}
iterator find(const K& key)
{
return _t.Find(key);
}
V& operator[](const K& key)
{
auto ret = (_t.Insert(make_pair(key, V()))).first;
return ret->second;
}
private:
RBTree<K, pair<K, V>, MapKeyOfT> _t;
};
void MapTest()
{
MyMap<string, int> m;
m.insert(make_pair("1", 1));
m.insert(make_pair("1", 1));
m.insert(make_pair("1", 1));
m.insert(make_pair("1", 1));
m.insert(make_pair("1", 1));
m["5"] = 5;
m["6"];
MyMap<string, int> m2(m);
MyMap<string, int> m3 = m2;
MyMap<string, int>::iterator it = m.begin();
cout << "map: " << endl;
for (auto& e : m3)
{
cout << e.first << ":" << e.second << endl;
}
}
}
4.3.4 set的模拟实现
MySet.hpp
#define _CRT_SECURE_NO_WARNINGS 1
#pragma once
#include "RBTree.h"
namespace sk
{
template < class K>
class MySet
{
public:
struct SetKeyOfT
{
const K& operator()(const K& k)
{
return k;
}
};
typedef typename RBTree<K, K, SetKeyOfT>::_iterator iterator;
iterator begin()
{
return _t.begin();
}
iterator end()
{
return _t.end();
}
pair<iterator, bool> Insert(const K& key)
{
return _t.Insert(key);
}
iterator find(const K& key)
{
return _t.Find(key);
}
private:
RBTree<K, K, SetKeyOfT> _t;
};
void SetTest()
{
MySet<string> s;
s.Insert("1");
s.Insert("2");
s.Insert("3");
s.Insert("4");
s.Insert("5");
MySet<string> s2(s);
MySet<string> s3 = s2;
MySet<string>::iterator it= s2.begin();
cout << "set: " << endl;
while (it != s.end())
{
cout << *it << " ";
++it;
}
cout << endl;
for (auto& e : s3)
{
cout << e << " ";
}
cout << endl;
}
}
谢谢大家观看!!求点赞,关注,收藏。