关联式容器
在初阶阶段,我们已经接触过STL中的部分容器,比如:vector、list、deque、
forward_list(C++11)等,这些容器统称为序列式容器,因为其底层为线性序列的数据结构,里面
存储的是元素本身。那什么是关联式容器?它与序列式容器有什么区别?
关联式容器也是用来存储数据的,与序列式容器不同的是,其里面存储的是
<key, value>
结构的
键值对,在数据检索时比序列式容器效率更高
![](https://img-blog.csdnimg.cn/direct/aee710bf6038443ebcc9324c200e0686.png)
键值对
用来表示具有一一对应关系的一种结构,该结构中一般只包含两个成员变量
key
和
value
,
key
代
表键值,
value
表示与
key
对应的信息
。比如:现在要建立一个英汉互译的字典,那该字典中必然 有英文单词与其对应的中文含义,而且,英文单词与其中文含义是一一对应的关系,即通过该应 该单词,在词典中就可以找到与其对应的中文含义。
树形结构的关联式容器
根据应用场景的不桶,STL总共实现了两种不同结构的管理式容器:树型结构与哈希结构。树型结 构的关联式容器主要有四种:map、set、multimap、multiset。这四种容器的共同点是:使用平衡搜索树(即红黑树)作为其底层结果,容器中的元素是一个有序的序列.下面一依次介绍每一个容器
set
1. set是按照一定次序存储元素的容器
2. 在set中,元素的value也标识它(value就是key,类型为T),并且每个
value必须是唯一的
。
set中的元素
不能在容器中修改
(元素总是const),但是可以从容器中插入或删除它们。
3. 在内部,set中的元素总是按照其内部比较对象(类型比较)所指示的特定严格弱排序准则进行
排序。
4. set容器通过key访问单个元素的速度通常比unordered_set容器慢,但它们允许根据顺序对
子集进行直接迭代。
5. set在底层是用二叉搜索树(红黑树)实现的。
注意:
1. 与map/multimap不同,map/multimap中存储的是真正的键值对<key, value>,set中只放
value,但在底层实际存放的是由<value, value>构成的键值对。
2. set中插入元素时,只需要插入value即可,不需要构造键值对。
3. set中的元素不可以重复(因此可以使用set进行去重)。
4. 使用set的迭代器遍历set中的元素,可以得到有序序列
5. set中的元素默认按照小于来比较
6. set中查找某个元素,时间复杂度为:
7. set中的元素不允许修改(为什么?)
8. set中的底层使用二叉搜索树(红黑树)来实现
set的使用
![](https://img-blog.csdnimg.cn/direct/537bbb05032843a995e3b86d1a6711e3.png)
set
的构造
函数声明
|
功能介绍
|
set (const Compare& comp = Compare(), const Allocator&
= Allocator() );
|
构造空的
set
|
set (InputIterator first, InputIterator last, const
Compare& comp = Compare(), const Allocator& =
Allocator() );
|
用
[first, last)
区
间中的元素构造
set
|
set ( const set<Key,Compare,Allocator>& x);
|
set
的拷贝构造
|
set
的迭代器
函数声明
|
功能介绍
|
iterator begin()
|
返回
set
中起始位置元素的迭代器
|
iterator end()
|
返回
set
中最后一个元素后面的迭代器
|
const_iterator cbegin()
const
|
返回
set
中起始位置元素的
const
迭代器
|
const_iterator cend() const
|
返回
set
中最后一个元素后面的
const
迭代器
|
reverse_iterator rbegin()
|
返回
set
第一个元素的反向迭代器,即
end
|
reverse_iterator rend()
|
返回
set
最后一个元素下一个位置的反向迭代器,
即
begin
|
const_reverse_iterator
crbegin()const
|
返回
set
第一个元素的反向
const
迭代器,即
cend
|
const_reverse_iterator
crend() const
|
返回
set
最后一个元素下一个位置的反向
const
迭
代器,即
cbegin
|
set
的容量
函数声明
|
功能介绍
|
bool empty ( ) const
|
检测
set
是否为空,空返回
true
,否则返回
true
|
size_type size() const
|
返回
set
中有效元素的个数
|
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 ( const
key_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 ( const
key_type& x ) const
|
返回
set
中值为
x
的元素的个数
|
补充:
lower_bound
iterator lower_bound (const value_type& val);
const_iterator lower_bound (const value_type& val) const;
该函数将返回一个指向不小于val的第一个元素的迭代器
upper_bound
iterator upper_bound (const value_type& val);
const_iterator upper_bound (const value_type& val) const;
该函数将返回一个指向大于val的第一个元素的迭代器
multiset
1. multiset是按照特定顺序存储元素的容器,其中元素是可以重复的。
2. 在multiset中,元素的value也会识别它(因为multiset中本身存储的就是<value, value>组成
的键值对,因此value本身就是key,key就是value,类型为T). multiset元素的值不能在容器
中进行修改(因为元素总是const的),但可以从容器中插入或删除。
3. 在内部,multiset中的元素总是按照其内部比较规则(类型比较)所指示的特定严格弱排序准则
进行排序。
4. multiset容器通过key访问单个元素的速度通常比unordered_multiset容器慢,但当使用迭
代器遍历时会得到一个有序序列。
5. multiset底层结构为二叉搜索树(红黑树)。
注意:
1. multiset中再底层中存储的是
<value, value>
的键值对
2. mtltiset的插入接口中只需要插入即可
3. 与set的区别是,multiset中的元素可以重复,set中value是唯一的
4. 使用迭代器对multiset中的元素进行遍历,可以得到有序的序列
5. multiset
中的
元素不能修改
6.
在
multiset
中找某个元素,时间复杂度为
7. multiset
的作用:可以对元素进行排序
#include <set>
void TestSet()
{
int array[] = { 2, 1, 3, 9, 6, 0, 5, 8, 4, 7 };
// 注意:multiset在底层实际存储的是<int, int>的键值对
multiset<int> s(array, array + sizeof(array)/sizeof(array[0]));
for (auto& e : s)
cout << e << " ";
cout << endl;
return 0;
}
map
1. map是关联容器,它按照特定的次序(按照key来比较)存储由键值key和值value组合而成的元
素。
2. 在map中,键值key通常用于排序和惟一地标识元素,而值value中存储与此键值key关联的
内容。键值key和值value的类型可能不同,并且在map的内部,key与value通过成员类型
value_type绑定在一起,为其取别名称为pair:
typedef pair<const key, T> value_type;
3. 在内部,map中的元素总是按照键值key进行比较排序的。
4. map中通过键值访问单个元素的速度通常比unordered_map容器慢,但map允许根据顺序
对元素进行直接迭代(即对map中的元素进行迭代时,可以得到一个有序的序列)。
5. map支持下标访问符,即在[]中放入key,就可以找到与key对应的value。
6. map通常被实现为二叉搜索树(更准确的说:平衡二叉搜索树(红黑树))。
![](https://img-blog.csdnimg.cn/direct/0648104be4c446be87a13a64f5df8d86.png)
![](https://img-blog.csdnimg.cn/direct/f7ad981f28e24a7090e41e70ecd2e4a3.png)
map的使用
函数声明
|
功能介绍
|
map()
|
构造一个空的
map
|
map(const map &x) | 拷贝构造 |
map的迭代器
函数声明
|
功能介绍
|
begin()
和
end()
|
begin:
首元素的位置,
end
最后一个元素的下一个位置
|
cbegin()
和
cend()
|
与
begin
和
end
意义相同,但
cbegin
和
cend
所指向的元素不
能修改
|
rbegin()
和
rend()
|
反向迭代器,
rbegin
在
end
位置,
rend
在
begin
位置,其
++
和
--
操作与
begin
和
end
操作移动相反
|
crbegin()
和
crend()
|
与
rbegin
和
rend
位置相同,操作相同,但
crbegin
和
crend
所
指向的元素不能修改
|
map
的容量与元素访问
函数声明
|
功能简介
|
bool empty ( ) const
|
检测
map
中的元素是否为空,是返回
true
,否则返回
false
|
size_type size() const
|
返回
map
中有效元素的个数
|
mapped_type& operator[ ] (const
key_type& k)
|
返回
key
对应的
value
|
问题:当key不在map中时,通过operator获取对应value时会发生什么问题?
注意:在元素访问时,有一个与
operator[]
类似的操作
at()(
该函数不常用
)
函数,都是通过
key
找到与
key
对应的
value
然后返回其引用,不同的是:
当
key
不存在时,
operator[]用默认
value与key构造键值对然后插入,返回该默认value
,
at()
函数直接抛异常
。
![](https://img-blog.csdnimg.cn/direct/d6a358997ddd423e9ff170998e5c56cb.png)
//pair<K,V>
V& operator[](const K& key)
{
// 不管插入成功还是失败,pair中iterator始终指向key所在节点的iterator
pair<iterator, bool> ret = insert(make_pair(key, V()));
iterator it = ret.fisrt;
return it->second;
}
key存在,插入失败 返回 --> pair<存在的key所在节点的迭代器,false>key不存在,插入成功 返回 --> pair<新插入key所在节点的迭代器,true>
string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜",
"苹果", "香蕉", "苹果", "香蕉","苹果","草莓", "苹果","草莓" };
map<string, int> countMap;
for (auto& e : arr)
{
countMap[e]++;
//auto it = countMap.find(e);
//if (it != countMap.end())
//{
// it->second++;
//}
//else
//{
// //const pair<string, int>& val = { e, 1 };
// countMap.insert({ e, 1 });
//}
}
for (auto& kv : countMap)
{
cout << kv.first << ":" << kv.second << endl;
}
cout << endl;
map
中元素的修改
函数声明
|
功能简介
|
pair<iterator,bool> insert (
const value_type& x )
|
在
map
中插入键值对
x
,注意
x
是一个键值
对,返回值也是键值对:
iterator
代表新插入
元素的位置,
bool
代表释放插入成功
|
void erase ( iterator position )
|
删除
position
位置上的元素
|
size_type erase ( const
key_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 ( const
key_type& x ) const
|
在
map
中插入
key
为
x
的元素,找到返回该元
素的位置的
const
迭代器,否则返回
cend
|
size_type count ( const
key_type& x ) const
|
返回
key
为
x
的键值在
map
中的个数,注意
map
中
key
是唯一的,因此该函数的返回值
要么为
0
,要么为
1
,因此也可以用该函数来
检测一个
key
是否在
map
中
|
map<string, string> dict;
pair<string, string> kv1("sort", "排序");
dict.insert(kv1);
dict.insert(pair<string, string>("left", "左边"));
dict.insert(make_pair("right", "右边"));
dict.insert(make_pair("right", "xxxx"));
// 隐式类型转换
//pair<string, string> kv2 = { "string", "字符串" };
dict.insert({ "string", "字符串" });
//map<string, string>::iterator it = dict.begin();
auto it = dict.begin();
while (it != dict.end())
{
// iterator key不能修改 value可以修改
// const_iterator key不能修改 value不能修改
//it->first += 'x';
it->second += 'x';
//cout << (*it).first << ":" << (*it).second << endl;
cout << it->first << ":" << it->second << endl;
//cout << it.operator->()->first << ":" << it.operator->()->second << endl;
++it;
}
cout << endl;
for (auto& kv : dict)
{
//auto& [x, y] = kv;
cout << kv.first << ":" << kv.second << endl;
}
cout << endl;
/*for (auto& [x, y] : dict)
{
cout << x << ":" << y << endl;
}
cout << endl;*/
//map<string, string> dict2 = { {"string", "字符串"}, {"left", "左边"},{"right", "右边"} };
map<string, string> dict2 = { kv1, {"left", "左边"},{"right", "右边"} };
#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;
}
【总结】1. map中的的元素是键值对2. map中的key是唯一的,并且不能修改3. 默认按照小于的方式对key进行比较4. map中的元素如果用迭代器去遍历,可以得到一个有序的序列5. map的底层为平衡搜索树(红黑树),查找效率比较高$O(log_2 N)$6. 支持[]操作符,operator[]中实际进行插入查找。
multimap
1. Multimaps是关联式容器,它按照特定的顺序,存储由key和value映射成的键值对<key,value>,其中多个键值对之间的 key是可以重复的 。2. 在multimap中,通常按照key排序和惟一地标识元素,而映射的value存储与key关联的内容。key和value的类型可能不同,通过multimap内部的成员类型value_type组合在一起,value_type是组合key和value的键值对: typedef pair<const Key, T> value_type;3. 在内部,multimap中的元素总是通过其内部比较对象,按照指定的特定严格弱排序标准对key进行排序的。4. multimap通过key访问单个元素的速度通常比unordered_multimap容器慢,但是使用迭代 器直接遍历multimap中的元素可以得到关于key有序的序列。5. multimap在底层用二叉搜索树(红黑树)来实现。
注意:multimap和map的唯一不同就是:map中的key是唯一的,而multimap中key是可以
重复的。
- multimap中的key是可以重复的。
- multimap中的元素默认将key按照小于来比较
- 使用时与map包含的头文件相同
相关例题
两个数组的交集I
给定两个数组
输出结果中的每个元素一定是 唯一 的。我们可以 不考虑输出结果的顺序 。
nums1
和
nums2
,返回
它们的
交集
输出结果中的每个元素一定是 唯一 的。我们可以 不考虑输出结果的顺序 。
![](https://img-blog.csdnimg.cn/direct/34e14b999bd9401383625b89921f0517.png)
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
// 先去重
set<int> s1(nums1.begin(),nums1.end());
set<int> s2(nums2.begin(),nums2.end());
// set排过序,依次比较,小的一定不是交集,相等的是交集
auto it1 = s1.begin();
auto it2 = s2.begin();
vector<int> ret;
while(it1 != s1.end() && it2 != s2.end())
{
if(*it1 < *it2)
{
it1++;
}
else if(*it2 < *it1)
{
it2++;
}
else
{
ret.push_back(*it1);
it1++;
it2++;
}
}
return ret;
}
};
前K个高频单词
给定一个单词列表 words
和一个整数 k
,返回前 k
个出现次数最多的单词。
返回的答案应该按单词出现频率由高到低排序。如果不同的单词有相同出现频率, 按字典顺序 排序。
![](https://img-blog.csdnimg.cn/direct/997b3909ae5a49bda562f4a56ba9bd87.png)
![](https://img-blog.csdnimg.cn/direct/36691a83dfad49bbb9918fd2d2809f6f.png)
底层结构
前面对map/multimap/set/multiset进行了简单的介绍,在其文档介绍中发现,这几个容器有个
共同点是:其底层都是按照二叉搜索树来实现的,但是二叉搜索树有其自身的缺陷,假如往树中 插入的元素有序或者接近有序,二叉搜索树就会退化成单支树,时间复杂度会退化成O(N),因此 map、set等关联式容器的底层结构是对二叉树进行了平衡处理,即采用平衡树来实现。
AVL 树
二叉搜索树虽可以缩短查找的效率,但
如果数据有序或接近有序二叉搜索树将退化为单支树,查
找元素相当于在顺序表中搜索元素,效率低下
。因此,两位俄罗斯的数学家
G.M.Adelson-Velskii 和E.M.Landis
在
1962
年发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右
子树高度之差的绝对值不超过
1(
需要对树中的结点进行调整
)
,即可降低树的高度,从而减少平均搜索长度。
一棵
AVL
树或者是空树,或者是具有以下性质的二叉搜索树:
- 它的左右子树都是AVL树
- 左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1)
![](https://img-blog.csdnimg.cn/direct/c60fa1dbbfc44ce4bd7c06920f15765f.png)
如果一棵二叉搜索树是高度平衡的,它就是
AVL
树。如果它有
n
个结点,其高度可保持在
AVL
树节点的定义
template<class K, class V>
struct AVLTreeNode
{
AVLTreeNode<K, V>* _left; // 该节点的左孩子
AVLTreeNode<K, V>* _right; // 该节点的右孩子
AVLTreeNode<K, V>* _parent;// 该节点的父亲
pair<K, V> _kv;
int _bf; // 该节点的平衡因子
AVLTreeNode(const pair<K, V>& kv)
:_left(nullptr)
, _right(nullptr)
, _parent(nullptr)
,_kv(kv)
,_bf(0)
{}
};
AVL树的插入
AVL
树就是在二叉搜索树的基础上引入了平衡因子,因此
AVL
树也可以看成是二叉搜索树。那么 AVL树的插入过程可以分为两步:
1. 按照二叉搜索树的方式插入新节点 2. 调整节点的平衡因子
1. 先按照二叉搜索树的规则将节点插入到AVL树中2. 新节点插入后,AVL树的平衡性可能会遭到破坏,此时就需要更新平衡因子,并检测是否破坏了AVL树的平衡性Cur插入后,Parent的平衡因子一定需要调整在插入之前,Parent的平衡因子分为三种情况:-1,0, 1, 分以下两种情况:1. 如果Cur插入到Parent的左侧,只需给Parent的平衡因子-1即可2. 如果Cur插入到Parent的右侧,只需给Parent的平衡因子+1即可此时:Parent的平衡因子可能有三种情况:0,正负1, 正负21. 如果Parent的平衡因子为0,说明插入之前Parent的平衡因子为正负1,插入后被调整成0,此时满足AVL树的性质,插入成功2. 如果Parent的平衡因子为正负1,说明插入前Parent的平衡因子一定为0,插入后被更新成正负1,此时以pParent为根的树的高度增加,需要继续向上更新3. 如果Parent的平衡因子为正负2,则Parent的平衡因子违反平衡树的性质,需要对其进行旋转处理
AVL树的旋转
如果在一棵原本是平衡的AVL树中插入一个新节点,可能造成不平衡,此时必须调整树的结构,
使之平衡化。根据节点插入位置的不同,AVL树的旋转分为四种:
1.
新节点插入较高左子树的左侧
---
左左:右单旋
![](https://img-blog.csdnimg.cn/direct/d1ea44b8955c463b8539768a14772a63.png)
上图在插入前,AVL树是平衡的,新节点插入到30的左子树中,30左子树增加了一层,导致以60为根的二叉树不平衡,要让60平衡,只能将60左子树的高度减少一层,右子树增加一层,即将左子树往上提,这样60转下来,因为60比30大,只能将其放在30的右子树,而如果30有右子树,右子树根的值一定大于30,小于60,只能将其放在60的左子树,旋转完成后,更新节点的平衡因子即可。在旋转过程中, 有以下几种情况需要考虑:1. 30节点的右孩子可能存在,也可能不存在2. 60可能是根节点,也可能是子树如果是根节点,旋转完成后,要更新根节点如果是子树,可能是某个节点的左子树,也可能是右子树
2. 新节点插入较高右子树的右侧---右右:左单旋
![](https://img-blog.csdnimg.cn/direct/d65903645cb4433aa8239bfd0b429ce2.png)
3.
新节点插入较高左子树的右侧
---
左右:先左单旋再右单旋
![](https://img-blog.csdnimg.cn/direct/3ce3e971e1244e8c978818b7864ce10b.png)
将双旋变成单旋后再旋转,即:先对30进行左单旋,然后再对90进行右单旋,旋转完成后再
考虑平衡因子的更新。
4.
新节点插入较高右子树的左侧
---
右左:先右单旋再左单旋
![](https://img-blog.csdnimg.cn/direct/37981580c8b2400b8711ccff0ffe89bd.png)
总结:
假如以Parent为根的子树不平衡,即Parent的平衡因子为2或者-2,分以下情况考虑
1. Parent的平衡因子为2,说明Parent的右子树高,设Parent的右子树的根为SubR
- 当SubR的平衡因子为1时,执行左单旋
- 当SubR的平衡因子为-1时,执行右左双旋
2. Parent的平衡因子为-2,说明Parent的左子树高,设Parent的左子树的根为SubL
- 当SubL的平衡因子为-1是,执行右单旋
- 当SubL的平衡因子为1时,执行左右双旋
旋转完成后,原Parent为根的子树个高度降低,已经平衡,不需要再向上更新。
AVL树的验证
AVL树是在二叉搜索树的基础上加入了平衡性的限制,因此要验证AVL树,可以分两步:
1. 验证其为二叉搜索树
如果中序遍历可得到一个有序的序列,就说明为二叉搜索树
2. 验证其为平衡树
- 每个节点子树高度差的绝对值不超过1(注意节点中如果没有平衡因子)
- 节点的平衡因子是否计算正确
template<class K, class V>
struct AVLTreeNode
{
AVLTreeNode<K, V>* _left;
AVLTreeNode<K, V>* _right;
AVLTreeNode<K, V>* _parent;
pair<K, V> _kv;
int _bf; // balance factor
AVLTreeNode(const pair<K, V>& kv)
:_left(nullptr)
, _right(nullptr)
, _parent(nullptr)
,_kv(kv)
,_bf(0)
{}
};
template<class K, class V>
class AVLTree
{
typedef AVLTreeNode<K, V> Node;
public:
// logN
bool Insert(const pair<K, V>& kv)
{
if (_root == nullptr)
{
_root = new Node(kv);
return true;
}
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);
if (parent->_kv.first < kv.first)
{
parent->_right = cur;
}
else
{
parent->_left = cur;
}
cur->_parent = parent;
//...
// 更新平衡因子
while (parent)
{
if (cur == parent->_left)
{
parent->_bf--;
}
else
{
parent->_bf++;
}
if (parent->_bf == 0) // 1 -1 -> 0
{
// 更新结束
break;
}
else if(parent->_bf == 1 || parent->_bf == -1) // 0 -> 1 -1
{
// 继续往上更新
cur = parent;
parent = parent->_parent;
}
else if (parent->_bf == 2 || parent->_bf == -2) // 1 -1 -> 2 -2
{
// 当前子树出问题了,需要旋转平衡一下
if (parent->_bf == -2 && cur->_bf == -1)
{
RotateR(parent);
}
else if (parent->_bf == 2 && cur->_bf == 1)
{
RotateL(parent);
}
else if (parent->_bf == 2 && cur->_bf == -1)
{
RotateRL(parent);
}
else if (parent->_bf == -2 && cur->_bf == 1)
{
RotateLR(parent);
}
break;
}
else
{
// 理论而言不可能出现这个情况
assert(false);
}
}
return true;
}
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;
}
void InOrder()
{
_InOrder(_root);
cout << endl;
}
void RotateR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
parent->_left = subLR;
if(subLR)
subLR->_parent = parent;
subL->_right = parent;
Node* ppNode = parent->_parent;
parent->_parent = subL;
if (parent == _root)
{
_root = subL;
_root->_parent = nullptr;
}
else
{
if (ppNode->_left == parent)
{
ppNode->_left = subL;
}
else
{
ppNode->_right = subL;
}
subL->_parent = ppNode;
}
parent->_bf = subL->_bf = 0;
}
void RotateL(Node* parent)
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
parent->_right = subRL;
if (subRL)
subRL->_parent = parent;
subR->_left = parent;
Node* ppNode = parent->_parent;
parent->_parent = subR;
if (parent == _root)
{
_root = subR;
_root->_parent = nullptr;
}
else
{
if (ppNode->_right == parent)
{
ppNode->_right = subR;
}
else
{
ppNode->_left = subR;
}
subR->_parent = ppNode;
}
parent->_bf = subR->_bf = 0;
}
void RotateRL(Node* parent)
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
int bf = subRL->_bf;
RotateR(subR);
RotateL(parent);
subRL->_bf = 0;
if (bf == 1)
{
subR->_bf = 0;
parent->_bf = -1;
}
else if (bf == -1)
{
parent->_bf = 0;
subR->_bf = 1;
}
else
{
parent->_bf = 0;
subR->_bf = 0;
}
}
void RotateLR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
int bf = subLR->_bf;
RotateL(parent->_left);
RotateR(parent);
if (bf == -1)
{
subLR->_bf = 0;
subL->_bf = 0;
parent->_bf = 1;
}
else if (bf == 1)
{
subLR->_bf = 0;
subL->_bf = -1;
parent->_bf = 0;
}
else if (bf == 0)
{
subLR->_bf = 0;
subL->_bf = 0;
parent->_bf = 0;
}
else
{
assert(false);
}
}
bool IsBalance()
{
return _IsBalance(_root);
}
int Height()
{
return _Height(_root);
}
int Size()
{
return _Size(_root);
}
private:
int _Size(Node* root)
{
return root == nullptr ? 0 : _Size(root->_left) + _Size(root->_right) + 1;
}
int _Height(Node* root)
{
if (root == nullptr)
return 0;
return max(_Height(root->_left), _Height(root->_right)) + 1;
}
bool _IsBalance(Node* root)
{
if (root == nullptr)
return true;
int leftHeight = _Height(root->_left);
int rightHeight = _Height(root->_right);
// 不平衡
if (abs(leftHeight - rightHeight) >= 2)
{
cout << root->_kv.first << endl;
return false;
}
// 顺便检查一下平衡因子是否正确
if (rightHeight - leftHeight != root->_bf)
{
cout << root->_kv.first << endl;
return false;
}
return _IsBalance(root->_left)
&& _IsBalance(root->_right);
}
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;
};
AVL树的性能
AVL树是一棵绝对平衡的二叉搜索树,其要求每个节点的左右子树高度差的绝对值都不超过1,这样可以保证查询时高效的时间复杂度,即$log_2 (N)$。但是如果要对AVL树做一些结构修改的操作,性能非常低下,比如:插入时要维护其绝对平衡,旋转的次数比较多,更差的是在删除时,有可能一直要让旋转持续到根的位置。因此:如果需要一种查询高效且有序的数据结构,而且数据的个数为静态的(即不会改变),可以考虑AVL树,但一个结构经常修改,就不太适合。
红黑树
红黑树的概念
红黑树
,是一种
二叉搜索树
,但
在每个结点上增加一个存储位表示结点的颜色,可以是
Red
或
Black
。 通过对
任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路
径会比其他路径长出俩倍
,因而是
接近平衡
的。
![](https://img-blog.csdnimg.cn/direct/1002f705269d4af59cdf09169f9eb7ff.png)
红黑树的性质
1.
每个结点不是红色就是黑色
2.
根节点是黑色的
3.
如果一个节点是红色的,则它的两个孩子结点是黑色的
(不存在连续的红色结点)
4.
对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点
(每条路径都存在相同数量的黑色结点)
5.
每个空结点(NIL)都是黑色的
满足上面的性质,红黑树就能保证:其最长路径中节点个数不会超过最短路径节点个数的两倍
红黑树节点的定义
enum Colour
{
RED,
BLACK
};
template<class K, class V>
struct RBTreeNode
{
RBTreeNode<K, V>* _left;
RBTreeNode<K, V>* _right;
RBTreeNode<K, V>* _parent;
pair<K, V> _kv;
Colour _col;
RBTreeNode(const pair<K, V>& kv)
:_left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _kv(kv)
, _col(RED)
{}
};
红黑树的插入操作
红黑树是在二叉搜索树的基础上加上其平衡限制条件,因此红黑树的插入可分为两步:
1.
按照二叉搜索的树规则插入新节点
2.
检测新节点插入后,红黑树的性质是否造到破坏
因为
新节点的默认颜色是
红色
,因此:如果
其双亲节点的颜色是黑色,没有违反红黑树任何
性质
,则不需要调整;但
当新插入节点的双亲节点颜色为红色时,就违反了性质三不能有连
在一起的红色节点
,此时需要对红黑树分情况来讨论:
约定
:cur
为当前节点,
p
为父节点,
g
为祖父节点,
u
为叔叔节点
- 情况一: cur为红,p为红,g为黑,u存在且为红
![](https://img-blog.csdnimg.cn/direct/77958b567cf84b3bb361658c6b67d5dd.png)
- 情况二: cur为红,p为红,g为黑,u不存在
p为g的左孩子,cur为p的左孩子,则进行右单旋转;相反,p为g的右孩子,cur为p的右孩子,则进行左单旋转
- 情况三: cur为红,p为红,g为黑,u存在且为黑
![](https://img-blog.csdnimg.cn/direct/3c486106da12447f94fb4d1887c806c2.png)
p为g的左孩子,cur为p的左孩子,则进行右单旋转;相反,p为g的右孩子,cur为p的右孩子,则进行左单旋转p、g变色--p变黑,g变红
![](https://img-blog.csdnimg.cn/direct/5d5c7b5cea2b4a32878df3d75b87294d.png)
![](https://img-blog.csdnimg.cn/direct/3c486106da12447f94fb4d1887c806c2.png)
再执行:p为g的左孩子,cur为p的左孩子,则进行右单旋转;相反,p为g的右孩子,cur为p的右孩子,则进行左单旋转p、g变色--p变黑,g变红
红黑树的验证
红黑树的检测分为两步:
1. 检测其是否满足二叉搜索树(中序遍历是否为有序序列)
2. 检测其是否满足红黑树的性质
enum Colour
{
RED,
BLACK
};
template<class K, class V>
struct RBTreeNode
{
RBTreeNode<K, V>* _left;
RBTreeNode<K, V>* _right;
RBTreeNode<K, V>* _parent;
pair<K, V> _kv;
Colour _col;
RBTreeNode(const pair<K, V>& kv)
:_left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _kv(kv)
, _col(RED)
{}
};
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 = _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;
}
else
{
parent->_left = cur;
}
cur->_parent = parent;
// parent的颜色是黑色也结束
while (parent && parent->_col == RED)
{
// 关键看叔叔
Node* grandfather = parent->_parent;
if (parent == grandfather->_left)
{
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)
{
// g
// p u
// c
RotateR(grandfather);
parent->_col = BLACK;
grandfather->_col = RED;
}
else
{
// g
// p u
// c
RotateL(parent);
RotateR(grandfather);
cur->_col = BLACK;
grandfather->_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 // 叔叔不存在,或者存在且为黑
{
// 情况二:叔叔不存在或者存在且为黑
// 旋转+变色
// g
// u p
// c
if (cur == parent->_right)
{
RotateL(grandfather);
parent->_col = BLACK;
grandfather->_col = RED;
}
else
{
// g
// u p
// c
RotateR(parent);
RotateL(grandfather);
cur->_col = BLACK;
grandfather->_col = RED;
}
break;
}
}
}
_root->_col = BLACK;
return true;
}
void RotateR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
parent->_left = subLR;
if (subLR)
subLR->_parent = parent;
subL->_right = parent;
Node* ppNode = parent->_parent;
parent->_parent = subL;
if (parent == _root)
{
_root = subL;
_root->_parent = nullptr;
}
else
{
if (ppNode->_left == parent)
{
ppNode->_left = subL;
}
else
{
ppNode->_right = subL;
}
subL->_parent = ppNode;
}
}
void RotateL(Node* parent)
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
parent->_right = subRL;
if (subRL)
subRL->_parent = parent;
subR->_left = parent;
Node* ppNode = parent->_parent;
parent->_parent = subR;
if (parent == _root)
{
_root = subR;
_root->_parent = nullptr;
}
else
{
if (ppNode->_right == parent)
{
ppNode->_right = subR;
}
else
{
ppNode->_left = subR;
}
subR->_parent = ppNode;
}
}
void InOrder()
{
_InOrder(_root);
cout << endl;
}
bool IsBalance()
{
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);
}
private:
bool Check(Node* root, int blackNum, const int refNum)
{
if (root == nullptr)
{
//cout << blackNum << endl;
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);
}
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;
//size_t _size = 0;
};
红黑树与AVL树的比较
红黑树和
AVL
树都是高效的平衡二叉树,增删改查的时间复杂度都是O(
)
,红黑树不追
求绝对平衡,其只需保证最长路径不超过最短路径的
2
倍,相对而言,降低了插入和旋转的次数,
所以在经常进行增删的结构中性能比
AVL
树更优,而且红黑树实现比较简单,所以实际运用中红
黑树更多。
红黑树模拟实现STL中的map与set
RBTree.h
#include<vector>
enum Colour
{
RED,
BLACK
};
template<class T>
struct RBTreeNode
{
RBTreeNode<T>* _left;
RBTreeNode<T>* _right;
RBTreeNode<T>* _parent;
T _data;
Colour _col;
RBTreeNode(const T& data)
:_left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _data(data)
, _col(RED)
{}
};
template<class T, class Ref, class Ptr>
struct __RBTreeIterator
{
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;
}
bool operator!=(const Self& s)
{
return _node != s._node;
}
Self& operator++()
{
if (_node->_right)
{
// 下一个,右树最左节点
Node* leftMin = _node->_right;
while (leftMin->_left)
{
leftMin = leftMin->_left;
}
_node = leftMin;
}
else
{
// 下一个,孩子等于父亲左的那个祖先
Node* cur = _node;
Node* parent = cur->_parent;
while (parent && cur == parent->_right)
{
cur = parent;
parent = parent->_parent;
}
_node = parent;
}
return *this;
}
};
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*> ConstIterator;
RBTree() = default;
RBTree(const RBTree<K, T, KeyOfT>& t)
{
_root = Copy(t._root);
}
// t2 = t1
RBTree<K, T, KeyOfT>& operator=(RBTree<K, T, KeyOfT> t)
{
swap(_root, t._root);
return *this;
}
~RBTree()
{
Destroy(_root);
_root = nullptr;
}
Iterator Begin()
{
Node* leftMin = _root;
while (leftMin && leftMin->_left)
{
leftMin = leftMin->_left;
}
return Iterator(leftMin);
}
Iterator End()
{
return Iterator(nullptr);
}
ConstIterator End() const
{
return ConstIterator(nullptr);
}
ConstIterator Begin() const
{
Node* leftMin = _root;
while (leftMin && leftMin->_left)
{
leftMin = leftMin->_left;
}
return ConstIterator(leftMin);
}
Iterator Find(const K& key)
{
KeyOfT kot;
Node* cur = _root;
while (cur)
{
if (kot(cur->_data) < key)
{
cur = cur->_right;
}
else if (kot(cur->_data) > key)
{
cur = cur->_left;
}
else
{
return Iterator(cur);
}
}
return End();
}
pair<Iterator, bool> Insert(const T& data)
{
if (_root == nullptr)
{
_root = new Node(data);
_root->_col = BLACK;
return make_pair(Iterator(_root), true);
}
KeyOfT kot;
Node* parent = nullptr;
Node* cur = _root;
while (cur)
{
// K
// pair<K, V>
// kot对象,是用来取T类型的data对象中的key
if (kot(cur->_data) < kot(data))
{
parent = cur;
cur = cur->_right;
}
else if (kot(cur->_data) > kot(data))
{
parent = cur;
cur = cur->_left;
}
else
{
return make_pair(Iterator(cur), false);
}
}
cur = new Node(data);
Node* newnode = cur;
cur->_col = RED; // 新增节点给红色
if (kot(parent->_data) < kot(data))
{
parent->_right = cur;
}
else
{
parent->_left = cur;
}
cur->_parent = parent;
// parent的颜色是黑色也结束
while (parent && parent->_col == RED)
{
// 关键看叔叔
Node* grandfather = parent->_parent;
if (parent == grandfather->_left)
{
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)
{
// g
// p u
// c
RotateR(grandfather);
parent->_col = BLACK;
grandfather->_col = RED;
}
else
{
// g
// p u
// c
RotateL(parent);
RotateR(grandfather);
cur->_col = BLACK;
grandfather->_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 // 叔叔不存在,或者存在且为黑
{
// 情况二:叔叔不存在或者存在且为黑
// 旋转+变色
// g
// u p
// c
if (cur == parent->_right)
{
RotateL(grandfather);
parent->_col = BLACK;
grandfather->_col = RED;
}
else
{
// g
// u p
// c
RotateR(parent);
RotateL(grandfather);
cur->_col = BLACK;
grandfather->_col = RED;
}
break;
}
}
}
_root->_col = BLACK;
return make_pair(Iterator(newnode), true);
}
void RotateR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
parent->_left = subLR;
if (subLR)
subLR->_parent = parent;
subL->_right = parent;
Node* ppNode = parent->_parent;
parent->_parent = subL;
if (parent == _root)
{
_root = subL;
_root->_parent = nullptr;
}
else
{
if (ppNode->_left == parent)
{
ppNode->_left = subL;
}
else
{
ppNode->_right = subL;
}
subL->_parent = ppNode;
}
}
void RotateL(Node* parent)
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
parent->_right = subRL;
if (subRL)
subRL->_parent = parent;
subR->_left = parent;
Node* ppNode = parent->_parent;
parent->_parent = subR;
if (parent == _root)
{
_root = subR;
_root->_parent = nullptr;
}
else
{
if (ppNode->_right == parent)
{
ppNode->_right = subR;
}
else
{
ppNode->_left = subR;
}
subR->_parent = ppNode;
}
}
void InOrder()
{
_InOrder(_root);
cout << endl;
}
bool IsBalance()
{
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);
}
private:
Node* Copy(Node* root)
{
if (root == nullptr)
return nullptr;
Node* newroot = new Node(root->_data);
newroot->_col = root->_col;
newroot->_left = Copy(root->_left);
if (newroot->_left)
newroot->_left->_parent = newroot;
newroot->_right = Copy(root->_right);
if (newroot->_right)
newroot->_right->_parent = newroot;
return newroot;
}
void Destroy(Node* root)
{
if (root == nullptr)
return;
Destroy(root->_left);
Destroy(root->_right);
delete root;
root = nullptr;
}
bool Check(Node* root, int blackNum, const int refNum)
{
if (root == nullptr)
{
//cout << blackNum << endl;
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);
}
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;
//size_t _size = 0;
};
Myset.h
namespace bit
{
template<class K>
class set
{
struct SetKeyOfT
{
const K& operator()(const K& key)
{
return key;
}
};
public:
typedef typename RBTree<K, const K, SetKeyOfT>::Iterator iterator;
typedef typename RBTree<K, const K, SetKeyOfT>::ConstIterator const_iterator;
const_iterator begin() const
{
return _t.Begin();
}
const_iterator end() const
{
return _t.End();
}
iterator begin()
{
return _t.Begin();
}
iterator end()
{
return _t.End();
}
iterator find(const K& key)
{
return _t.Find(key);
}
pair<iterator, bool> insert(const K& key)
{
return _t.Insert(key);
}
private:
RBTree<K, const K, SetKeyOfT> _t;
};
}
Mymap.h
namespace bit
{
template<class K, class V>
class map
{
struct MapKeyOfT
{
const K& operator()(const pair<K, V>& kv)
{
return kv.first;
}
};
public:
typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::Iterator iterator;
typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::ConstIterator const_iterator;
const_iterator begin() const
{
return _t.Begin();
}
const_iterator end() const
{
return _t.End();
}
iterator begin()
{
return _t.Begin();
}
iterator end()
{
return _t.End();
}
iterator find(const K& key)
{
return _t.Find(key);
}
pair<iterator, bool> insert(const pair<K, V>& kv)
{
return _t.Insert(kv);
}
V& operator[](const K& key)
{
pair<iterator, bool> ret = _t.Insert(make_pair(key, V()));
return ret.first->second;
}
private:
RBTree<K, pair<const K, V>, MapKeyOfT> _t;
};
}