目录
1. 关联式容器
2. 键值对
3. 树形结构的关联式容器
3.1 set
3.1.1 set的介绍
3.1.2 set的使用
3.2 map
3.2.1 map的介绍
3.2.2 map的使用
3.3 multiset
3.3.1 multiset的介绍
3.3.2 multiset的使用
3.4 multimap
3.4.1 multimap的介绍
3.5 在OJ中的使用
4. 底层结构(难)
4.1 AVL 树
4.1.1 AVL树的概念
4.1.2 AVL树节点的定义
4.1.3 AVL树的插入(重点理解原理)
4.1.4 AVL树的旋转(难)
4.1.5 AVL树的验证
4.1.6 AVL树的删除(了解)
4.1.7 AVL树的性能
4.2 红黑树
4.2.1 红黑树的概念
4.2.2 红黑树的性质
4.2.3 红黑树节点的定义
4.2.4 红黑树的插入操作
4.2.5 红黑树的验证
4.2.6 红黑树的删除
4.2.7 红黑树与AVL树的比较
4.2.8红黑树完整实现代码
4.3 红黑树模拟实现STL中的map与set
4.3.1 红黑树的迭代器
4.3.2 改造红黑树
4.3.3 set的模拟实现 && map的模拟实现
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的使用
T: set中存放元素的类型,实际在底层存储<value, value>的键值对。
Compare : set 中元素默认按照小于来比较,这里我们可以通过写仿函数进行自己想要的序列Alloc : set 中元素空间的管理方式,使用 STL 提供的空间配置器管理
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的使用
4. map的容量与元素访问
重现关注:
在元素访问时,有一个与operator[]类似的操作at()(该函数不常用)函数,都是通过
key找到与key对应的value然后返回其引用 ,不同的是: 当 key 不存在时, operator[] 用默认value 与 key 构造键值对然后插入,返回该默认 value , at() 函数直接抛异常 。这里operator[]的重载实现等我们看完find和insert的接口后再看
剩下的和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中的使用
前K个高频单词
思路:我们可以使用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)
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 树的旋转分为四种:
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 。 通过对 任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出俩倍,因而是接近平衡的。
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. 检测新节点插入后,红黑树的性质是否造到破坏
情况二: 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的模拟实现
红黑树,map,set实现