目录
1. 关联式容器
2. 键值对
用来表示具有一一对应关系的一种结构,该结构中一般只包含两个成员变量 key 和 value , key 代 表键值, value 表示与 key 对应的信息 。比如:现在要建立一个英汉互译的字典,那该字典中必然有英文单词与其对应的中文含义,而且,英文单词与其中文含义是一一对应的关系,即通过该应该单词,在词典中就可以找到与其对应的中文含义。
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)
{}
};
3. 树形结构的关联式容器
根据应用场景的不同, STL 总共实现了两种不同结构的管理式容器:树型结构与哈希结构。 树型结 构的关联式容器主要有四种: map 、 set 、 multimap 、 multiset 。这四种容器的共同点是: 使用平衡搜索树(即红黑树)作为其底层结果 , 容器中的元素是一个有序的序列 。
3.1 set
3.1.1 set的介绍
1. set 是按照一定次序存储元素的容器2. 在 set 中,元素的 value 也标识它 (value 就是 key ,类型为 T) ,并且 每个value必须是唯一的 。set中的元素不能在容器中修改 ( 元素总是 const) ,但是可以从容器中插入或删除它们。3. 在内部, set 中的元素总是按照其内部比较对象 ( 类型比较 ) 所指示的特定严格弱排序准则进行排序。4. set 容器通过 key 访问单个元素的速度通常比 unordered_set 容器慢,但它们允许根据顺序对子集进行直接迭代。5. set在底层是用二叉搜索树(红黑树) 实现的。
3.1.2 set的使用
![](https://img-blog.csdnimg.cn/0b9905bd2abc4ea097fefaf7ae877742.png)
T: set中存放元素的类型,实际在底层存储<value, value>的键值对。
Compare : set 中元素默认按照小于来比较,这里我们可以通过写仿函数进行自己想要的序列Alloc : set 中元素空间的管理方式,使用 STL 提供的空间配置器管理
![](https://img-blog.csdnimg.cn/98ac45af8e0b45eead4ca06b71e4adcd.png)
3. set的迭代器 :正向迭代器以及反向迭代器,及其const版本:
4.set修改操作(重点)
主要看看这个insert:
后面的和我们之前学习过的都类似:
删除:
查找,返回该节点的迭代器
返回该元素出现的个数,这里set并没有什么作用,因为set是不允许出现重复的元素的,但是对multiset来说就有这个意义了,因为multiset允许出现重复的元素。
其他的可以在C++网站自行查找,使用成本不高。
5.set的使用举例
int main()
{
// 用数组array中的元素构造set
int array[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0, 1, 3, 5, 7, 9, 2, 4,6, 8, 0 };
int sz = sizeof(array) / sizeof(array[0]);
//迭代器区间初始化
set<int> s(array,array+sz);
//打印
for (auto& e : s)
{
cout << e << " ";
}
cout << endl;
//反向打印
for (auto i = s.rbegin(); i != s.rend(); ++i)
{
cout << *i << " ";
}
cout << endl;
// set中值为3的元素出现了几次,因为实现了去重,所以元素都是1次
cout << s.count(3) << endl;
return 0;
}
结果:
3.2 map
3.2.1 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 通常被实现为二叉搜索树 ( 更准确的说:平衡二叉搜索树 ( 红黑树 ))
3.2.2 map的使用
![](https://img-blog.csdnimg.cn/13f75ce6321b4e348bfebbe1a98873f5.png)
![](https://img-blog.csdnimg.cn/1dd7ef0d3cc648d1b4584a48fd76026f.png)
![](https://img-blog.csdnimg.cn/1c9d1a0c5c914e528388ead2c3e9af53.png)
4. map的容量与元素访问
重现关注:
在元素访问时,有一个与operator[]类似的操作at()(该函数不常用)函数,都是通过
key找到与key对应的value然后返回其引用 ,不同的是: 当 key 不存在时, operator[] 用默认value 与 key 构造键值对然后插入,返回该默认 value , at() 函数直接抛异常 。这里operator[]的重载实现等我们看完find和insert的接口后再看
![](https://img-blog.csdnimg.cn/ce24206415834382a0490066c59c8df9.png)
剩下的和set其实很类似自行查文档C++网站
下面我们来解释一下operator[]的函数重载实现:
operator[]兼具3个功能:查找,插入,修改
operator[]的原理是:
用 <key, T()> 构造一个键值对,然后调用 insert() 函数将该键值对插入到 map 中如果 key 已经存在,插入失败, insert 函数返回该 key 所在位置的迭代器如果 key 不存在,插入成功, insert 函数返回新插入元素所在位置的迭代器operator[] 函数最后将 insert 返回值键值对中的 value 返回insert需注意:map 中的键值对 key 一定是唯一的,如果 key 存在将插入失败
int main()
{
map<string, string> m;
m.insert(pair<string, string>("字符串","string"));
//直接使用make_pair就可以不用写那么复杂
m.insert(make_pair("banan", "香蕉"));
// 将<"apple", "">插入map中,插入成功,返回value的引用,将“苹果”赋值给该引用结果,
m["apple"] = "苹果";
for (auto& e : m)
{
cout << e.first << " " << e.second << endl;
}
cout << endl;
// 删除key为"apple"的元素
m.erase("apple");
if (1 == m.count("apple"))
cout << "apple还在" << endl;
else
cout << "apple被删除" << endl;
return 0;
}
结果:这里的count一般都是看key是否在map中,如果是multimap就是多个key,这样就可以找到其中的个数
【总结】
1. map 中的的元素是键值对2. map 中的 key 是唯一的,并且不能修改3. 默认按照小于的方式对 key 进行比较4. map 中的元素如果用迭代器去遍历,可以得到一个有序的序列5. map 的底层为平衡搜索树 ( 红黑树 ) ,查找效率比较高:O(logN)6. 支持 [] 操作符, operator[] 中实际进行插入查找。
3.3 multiset
3.3.1 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 底层结构为二叉搜索树 ( 红黑树 ) 。
3.3.2 multiset的使用
和set的使用几乎相同,只是其中允许重复元素的出现
3.4 multimap
3.4.1 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 在底层用二叉搜索树 ( 红黑树 ) 来实现。
3.5 在OJ中的使用
思路:我们可以使用map来对string进行排序,然后放入vector中,用stable_sort稳定地对次数排序,这样可以保证string的相对顺序不变。最后取数组中的前k个元素即可
class Solution {
public:
struct compare{
bool operator()(const pair<int,string>& l,const pair<int,string>& r)
{
return l.first > r.first;
}
};
vector<string> topKFrequent(vector<string>& words, int k) {
//把元素放入map中,然后把数据放入vector中再针对次数进行排序,其中要注意稳定性
map<string,int> m;
for(auto& e:words)
{
m[e]++;
}
vector<pair<int,string>> v;
//将map元素放入v中
for(auto& e:m)
{
v.push_back(make_pair(e.second,e.first));
}
//进行稳定排序
stable_sort(v.begin(),v.end(),compare());
//插入结果集中
vector<string> result;
for(int i = 0;i<k;++i)
{
result.push_back(v[i].second);
}
return result;
}
};
思路:使用set进行排序加去重,之后遍历两个set找相同的元素插入结果集中
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
//使用set进行排序加去重,然后在两个set中找相同的元素
set<int> s1(nums1.begin(),nums1.end());
set<int> s2(nums2.begin(),nums2.end());
vector<int> result;
auto it1 = s1.begin();
auto it2 = s2.begin();
//比较谁的元素小就++谁的iterator
while(it1 != s1.end() && it2 != s2.end())
{
if(*it1 == *it2)
{
result.push_back(*it1);
++it1;
++it2;
}
else if(*it1 > *it2)
{
++it2;
}
else{
++it1;
}
}
return result;
}
};
4. 底层结构(难)
4.1 AVL 树
4.1.1 AVL树的概念
二叉搜索树虽可以缩短查找的效率,但 如果数据有序或接近有序二叉搜索树将退化为单支树,查 找元素相当于在顺序表中搜索元素,效率低下 。因此,两位俄罗斯的数学家 G.M.Adelson-Velskii和E.M.Landis 在 1962 年发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右 子树高度之差的绝对值不超过 1( 需要对树中的结点进行调整 ) ,即可降低树的高度,从而减少平均搜索长度。一棵 AVL 树或者是空树,或者是具有以下性质的二叉搜索树:它的左右子树都是AVL树左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1)
![](https://img-blog.csdnimg.cn/1cc2407b1f3340bf9bc4399e70b05f08.png)
4.1.2 AVL树节点的定义
template <class K ,class V>
struct AVLTreeNode
{
pair<K, V> _kv;
AVLTreeNode* _left;
AVLTreeNode* _right;
AVLTreeNode* _parent;
//平衡因子 balance factor
int _bf;
//构造
AVLTreeNode(const pair<K, V>& kv)
:_kv(kv)
, _left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _bf(0)
{}
};
4.1.3 AVL树的插入(重点理解原理)
AVL 树就是在二叉搜索树的基础上引入了平衡因子,因此 AVL 树也可以看成是二叉搜索树。那么AVL树的插入过程可以分为两步:1. 按照二叉搜索树的方式插入新节点2. 调整节点的平衡因子思路:
4.1.4 AVL树的旋转(难)
如果在一棵原本是平衡的 AVL 树中插入一个新节点,可能造成不平衡,此时必须调整树的结构,使之平衡化。根据节点插入位置的不同,AVL 树的旋转分为四种:
![](https://img-blog.csdnimg.cn/1c9cd98c95f640919921c1c61d49e63b.png)
2. 新节点插入较高左子树的左侧---左左:右单旋
3. 新节点插入较高左子树的右侧---左右:先左单旋再右单旋
4. 新节点插入较高右子树的左侧---右左:先右单旋再左单旋
总结:
假如以p Parent 为根的子树不平衡,即p Parent 的平衡因子为 2 或者 -2 ,分以下情况考虑1. pParent的平衡因子为2,说明pParent的右子树高 ,设 pParent 的右子树的根为 pSubR当pSubR的平衡因子为1时,执行左单旋当pSubR的平衡因子为-1时,执行右左双旋2. pParent的平衡因子为-2,说明pParent的左子树高 ,设 pParent 的左子树的根为 pSubL当pSubL的平衡因子为-1是,执行右单旋当pSubL的平衡因子为1时,执行左右双旋旋转完成后,原 pParent 为根的子树个高度降低,已经平衡,不需要再向上更新。
4.1.5 AVL树的验证
template <class K ,class V>
struct AVLTreeNode
{
pair<K, V> _kv;
AVLTreeNode* _left;
AVLTreeNode* _right;
AVLTreeNode* _parent;
//平衡因子 balance factor
int _bf;
//构造
AVLTreeNode(const pair<K, V>& kv)
:_kv(kv)
, _left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _bf(0)
{}
};
template <class K ,class V>
class AVLTree
{
typedef AVLTreeNode<K, V> Node;
public:
//插入,和搜索二叉树类似
bool insert(const pair<K, V>& kv)
{
//第一个
if (_root == nullptr)
{
_root = new Node(kv);
_root->_parent = nullptr;
return true;
}
Node* cur = _root;
Node* parent = 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);
//链接
if (parent->_kv.first < kv.first)
{
parent->_right = cur;
cur->_parent = parent;
}
else
{
parent->_left = cur;
cur->_parent = parent;
}
//判断平衡因子是否符合AVL树
while (parent)//_root的parent是空
{
//插入节点在parent的左就--_bf
//插入节点在parent的右就++_bf
if (parent->_left == cur)
{
parent->_bf--;
}
else
{
parent->_bf++;
}
//判断平衡因子是否正确
//如果parent的bf为0说明之前不平衡,现在平衡了
//parent的bf为-1或者1说明parent原来是0即平衡,新增节点会改变更上面的节点的bf
//parent的bf为-2或者2就需要赶紧调平
if (parent->_bf == 0)
{
break;
}
else if (parent->_bf == -1 || parent->_bf == 1)
{
//更新上面节点的bf
cur = parent;
parent = parent->_parent;
}
else if (parent->_bf == -2 || parent->_bf == 2)
{
//旋转
//parent为-2 cur为-1需要右旋
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)
{
RotateLR(parent);
}
else if (parent->_bf == 2 && cur->_bf == -1)
{
RotateRL(parent);
}
break;
}
else
{
//不存在,但是你有可能写错,预防错误
assert(false);
}
}
return true;
}
void RotateR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
//建立链接关系
parent->_left = subLR;
if (subLR)
{
subLR->_parent = parent;
}
//更上面的父节点
Node* ppNode = parent->_parent;
subL->_right = parent;
parent->_parent = subL;
//ppNode可能为空
if (ppNode == nullptr)
{
_root = subL;
subL->_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;
}
Node* ppNode = parent->_parent;
parent->_parent = subR;
subR->_left = parent;
if (_root == parent)
{
_root = subR;
subR->_parent = nullptr;
}
else
{
if (ppNode->_left == parent)
{
ppNode->_left = subR;
}
else
{
ppNode->_right = subR;
}
subR->_parent = ppNode;
}
//更新平衡因子
parent->_bf = subR->_bf = 0;
}
void RotateLR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
//记录subLR的bf,在其左子树插入则parent最终bf就为1,在其右子树插入则subL最终为-1
int bf = subLR->_bf;
//先左旋,再右旋
RotateL(subL);
RotateR(parent);
//更新平衡因子
if (bf == 0)
{
//这种就是插入subLR的情况,一共3个节点,刚好平衡
parent->_bf = 0;
subL->_bf = 0;
subLR->_bf = 0;
}
else if (bf == -1)//在subLR左子树插入
{
parent->_bf = 1;
subL->_bf = 0;
subLR->_bf = 0;
}
else if (bf == 1)//在subLR右子树插入
{
parent->_bf = 0;
subL->_bf = -1;
subLR->_bf = 0;
}
else
{
//不存在这种情况
assert(false);
}
}
void RotateRL(Node* parent)
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
int bf = subRL->_bf;
//在左插入subR最终bf为1,在右插入parent的bf最终为-1
RotateR(subR);
RotateL(parent);
if (bf == 0)
{
parent->_bf = 0;
subR->_bf = 0;
subRL->_bf = 0;
}
else if (bf == 1)//在右插入
{
parent->_bf = -1;
subR->_bf = 0;
subRL->_bf = 0;
}
else if (bf == -1)//在左插入
{
parent->_bf = 0;
subR->_bf = 1;
subRL->_bf = 0;
}
else
{
assert(false);
}
}
void Inorder()
{
_Inorder(_root);
}
bool Isbalance()
{
return _Isbalance(_root);
}
private:
int Height(Node* _root)
{
if (_root == nullptr)
return 0;
int lh = Height(_root->_left);
int rh = Height(_root->_right);
return lh > rh ? lh + 1 : rh + 1;
}
bool _Isbalance(Node* _root)
{
if (_root == nullptr)
return true;
int lh = Height(_root->_left);
int rh = Height(_root->_right);
if (rh - lh != _root->_bf)
{
cout <<_root->_kv.first<< "平衡因子异常" << endl;
return false;
}
//查看子树
return abs(rh - lh) < 2 && _Isbalance(_root->_left) && _Isbalance(_root->_right);
}
void _Inorder(Node* _root)
{
if (_root == nullptr)
return;
_Inorder(_root->_left);
cout << _root->_kv.first << endl;
_Inorder(_root->_right);
}
Node* _root = nullptr;
};
void TestAVLTree1()
{
//int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };
int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16 , 14 };
AVLTree<int, int> t;
for (auto& e : a)
{
t.insert(make_pair(e, e));
}
t.Inorder();
}
void TestAVLTree2()
{
srand(time(0));
AVLTree<int, int> t;
for (int i = 0; i < 100000; ++i)
{
int x = rand();
t.insert(make_pair(x, x));
}
cout << t.Isbalance() << endl;
}
4.1.6 AVL树的删除(了解)
4.1.7 AVL树的性能
4.2 红黑树
4.2.1 红黑树的概念
红黑树 ,是一种 二叉搜索树 ,但 在每个结点上增加一个存储位表示结点的颜色,可以是 Red 或 Black 。 通过对 任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出俩倍,因而是接近平衡的。
![](https://img-blog.csdnimg.cn/f3fa78ecf6ce4f898b43113d2d7c8133.png)
4.2.2 红黑树的性质
1. 每个结点不是红色就是黑色2. 根节点是黑色的3. 如果一个节点是红色的,则它的两个孩子结点是黑色的 (不能存在连续的红节点)4. 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点(所有路径的黑色节点数是相同的)5. 每个叶子结点都是黑色的 ( 此处的叶子结点指的是空结点 )
4.2.3 红黑树节点的定义
//定义红黑
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)
, _col(RED)
{}
};
4.2.4 红黑树的插入操作
红黑树是在二叉搜索树的基础上加上其平衡限制条件,因此红黑树的插入可分为两步:1. 按照二叉搜索的树规则插入新节点2. 检测新节点插入后,红黑树的性质是否造到破坏
![](https://img-blog.csdnimg.cn/65b90e172fc94074be45099c039fb9a1.png)
情况二: cur为红,p为红,g为黑,u不存在/u存在且为黑
情况三: cur为红,p为红,g为黑,u不存在/u存在且为黑
总结:最主要看uncle,如果uncle存在且为红,那么就是情况一,直接变色,然后迭代向上更新即可;如果uncle为其他情况,都是旋转加变色,具体怎么旋转和变色看具体情况,旋转的方式和AVL树是一样的,变色的话主要是为了满足规则。
4.2.5 红黑树的验证
红黑树的检测分为两步:1. 检测其是否满足二叉搜索树 ( 中序遍历是否为有序序列 )2. 检测其是否满足红黑树的性质
bool IsvalidRBTree()
{
//空树是红黑树
if (_root == nullptr)
return true;
//根是黑色
if (_root->_col != BLACK)
{
cout << "不满足规则2:根节点必须为黑色" << endl;
return false;
}
//每个路径,黑色节点数量相同
Node* cur = _root;
int blackcount = 0;//记录黑色节点数量
while (cur)
{
if (cur->_col == BLACK)
++blackcount;
cur = cur->_left;
}
int k = 0;//用来记录每个路径的黑色节点个数
return _IsvaildRBTree(_root, k, blackcount);
}
bool _IsvaildRBTree(Node* root, int k,const int blackcount)
{
if (root == nullptr)
{
if (k != blackcount)
{
cout << "不满足规则4,每个路径的黑色节点数相同" << endl;
return false;
}
return true;
}
Node* parent = root->_parent;
//往上找是否存在相连的红色节点
if (parent && parent->_col == root->_col && root->_col == RED)
{
cout << "存在相连的红节点,不满足规则3" << endl;
return false;
}
if (root->_col == BLACK)
++k;
return _IsvaildRBTree(root->_left, k, blackcount) && _IsvaildRBTree(root->_right, k, blackcount);
}
4.2.6 红黑树的删除
这里不做讲解,可以看看这里:红黑树的插入删除
4.2.7 红黑树与AVL树的比较
红黑树和 AVL 树都是高效的平衡二叉树,增删改查的时间复杂度都是 O($log_2 N$) ,红黑树不追求绝对平衡,其只需保证最长路径不超过最短路径的2倍,相对而言,降低了插入和旋转的次数 , 所以在经常进行增删的结构中性能 比AVL树更优 ,而且红黑树实现比较简单,所以 实际运用中红黑树更多
4.2.8红黑树完整实现代码
//定义红黑
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)
, _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* cur = _root;
Node* parent = 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->_parent = parent;
if (parent->_kv.first > kv.first)
{
parent->_left = cur;
}
else
{
parent->_right = cur;
}
//判断是否满足红黑树的规则
while (parent && parent->_col == RED)
{
Node* grandfather = parent->_parent;
//parent在grandfather的左
if (grandfather->_left == parent)
{
Node* uncle = grandfather->_right;
//分3种情况
//1.uncle存在&&uncle和parent都是红,直接把他们改成黑,然后grandfather改成红迭代上去即可
if (uncle && uncle->_col == RED)
{
uncle->_col = parent->_col = BLACK;
grandfather->_col = RED;
//迭代向上更新,有可能grandfather上面是红
cur = grandfather;
parent = cur->_parent;
}
else
{
//情况2或者3
//情况2:cur在parent左边,形成直线型,直接右旋变色即可,parent变成黑,grandfather变成红
if (parent->_left == cur)
{
RotateR(grandfather);
parent->_col = BLACK;
grandfather->_col = RED;
}
else
{
//情况3:形成折线型,先左旋parent再右旋grandfather然后变色,cur最终变黑,grandfather变红
RotateL(parent);
RotateR(grandfather);
cur->_col = BLACK;
grandfather->_col = RED;
}
//无论是情况2还是3,最终上面的节点都是黑,就不需要更新了,直接跳出
break;
}
}
else
{
//parent在grandfather的右
Node* uncle = grandfather->_left;
//情况1
if (uncle && uncle->_col == RED)
{
uncle->_col = parent->_col = BLACK;
grandfather->_col = RED;
//迭代
cur = grandfather;
parent = cur->_parent;
}
else
{
//情况2
if (parent->_right == cur)
{
//左旋加变色,parent变黑,grandfather变红
RotateL(grandfather);
parent->_col = BLACK;
grandfather->_col = RED;
}
else
{
//情况3:先右旋再左旋,cur变黑,grandfather变红
RotateR(parent);
RotateL(grandfather);
cur->_col = BLACK;
grandfather->_col = RED;
}
}
}
}
//最终根一定是黑的
_root->_col = BLACK;
}
void Inorder()
{
_Inorder(_root);
}
bool IsvalidRBTree()
{
//空树是红黑树
if (_root == nullptr)
return true;
//根是黑色
if (_root->_col != BLACK)
{
cout << "不满足规则2:根节点必须为黑色" << endl;
return false;
}
//每个路径,黑色节点数量相同
Node* cur = _root;
int blackcount = 0;//记录黑色节点数量
while (cur)
{
if (cur->_col == BLACK)
++blackcount;
cur = cur->_left;
}
int k = 0;//用来记录每个路径的黑色节点个数
return _IsvaildRBTree(_root, k, blackcount);
}
private:
bool _IsvaildRBTree(Node* root, int k,const int blackcount)
{
if (root == nullptr)
{
if (k != blackcount)
{
cout << "不满足规则4,每个路径的黑色节点数相同" << endl;
return false;
}
return true;
}
Node* parent = root->_parent;
//往上找是否存在相连的红色节点
if (parent && parent->_col == root->_col && root->_col == RED)
{
cout << "存在相连的红节点,不满足规则3" << endl;
return false;
}
if (root->_col == BLACK)
++k;
return _IsvaildRBTree(root->_left, k, blackcount) && _IsvaildRBTree(root->_right, k, blackcount);
}
void _Inorder(Node* root)
{
if (root == nullptr)
return;
_Inorder(root->_left);
cout << root->_kv.first << ":" << root->_kv.second << endl;
_Inorder(root->_right);
}
void RotateR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
parent->_left = subLR;
if (subLR)
{
subLR->_parent = parent;
}
Node* ppNode = parent->_parent;
parent->_parent = subL;
subL->_right = parent;
if (ppNode == nullptr)//等价于_root == parent
{
_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;
}
Node* ppNode = parent->_parent;
parent->_parent = subR;
subR->_left = parent;
//ppNode可能为空
if (ppNode == nullptr)
{
_root = subR;
_root->_parent = nullptr;
}
else
{
//parent可能是ppNode的左右孩子
if (ppNode->_left == parent)
{
ppNode->_left = subR;
}
else
{
ppNode->_right = subR;
}
subR->_parent = ppNode;
}
}
Node* _root = nullptr;
};
void TestRBTree1()
{
//int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };
int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16 ,14};
RBTree<int, int> t;
for (auto e : a)
{
t.insert(make_pair(e, e));
//cout << e << " "<< t.IsvalidRBTree() << endl;
}
cout << endl;
t.Inorder();
}
void TestRBTree2()
{
srand(time(0));
RBTree<int, int> t;
for (int i = 0; i < 100000; ++i)
{
int x = rand();
t.insert(make_pair(x, x));
}
//判断是否满足红黑树的规则
//t.Inorder();
cout << t.IsvalidRBTree() << endl;
}
4.3 红黑树模拟实现STL中的map与set
4.3.1 红黑树的迭代器
迭代器的好处是可以方便遍历,是数据结构的底层实现与用户透明。如果想要给红黑树增加迭代器,需要考虑以前问题
4.3.2 改造红黑树
我们可以看到stl的源码设计部分的模板参数是这样设计的:template < class K , class ValueType , class KeyOfValue >因为关联式容器中存储的是 <key, value> 的键值对,因此k 为 key 的类型,ValueType: 如果是 map ,则为 pair<K, V>; 如果是 set ,则为 kKeyOfValue: 通过 value 来获取 key 的一个仿函数类这么设计的原因就是为了让一颗红黑树就满足map和set两个容器。
4.3.3 set的模拟实现 && map的模拟实现
细节比较多,主要体现在了迭代器那么,这里迭代器的实现其实和list很相似,只是insert以及重载那里多了不少细节。
这里就贴上链接,有红黑树,set,map的模拟实现