1.map和set的整体大致架构
1.map和set的整体
平时我们使用map和set时,头文件是map和set的头文件
set头文件:
map头文件
而stl_tree.h代表的就是红黑树
1.2 map和set的大致架构
map和set在源代码基本结构
map的大致特点:
set的大致特点:
tree的大致特点:
红黑树tree的节点数据
2. 封装map和set的底层实现
2.1实现架构
分为三个头文件保存,RBTree.h,Mymap.h,Myset.h
RBTree.h存放模拟实现的红黑树
Mymap.h存放模拟实现的map
Myset.h存放模拟实现的set
应用泛型模板
基本框架如下
如图,由于Myset和Mymap中的存放的数据类型是不一样,map中的时pair类型键值对,set存放的是key。
set和map的数据是给出实例化,去对应RBTree中的T类型,最后对应RBTreeNode的节点数据,来让编译器编译出来存放两个数据类型不同的红黑树封装出来map和set
2.2 RBTree.h
2.2.1 节点数据类和节点颜色定义
节点颜色定义:用枚举类型来定义
// 枚举
enum Colour
{RED,BLACK
};
节点数据类定义:模板来定义,来让map和set实例化,来在这里给与T相应的参数,来实现map和set
// 节点类
// 数据直接用一个来代替
template<class T>
struct RBTreeNode
{RBTreeNode<T>* _parent;// 父亲节点RBTreeNode<T>* _left;// 左孩子节点RBTreeNode<T>* _right;// 右孩子节点T _data;// 节点数据Colour _col;// 节点颜色// 构造函数RBTreeNode(const T& data):_left(nullptr), _right(nullptr), _parent(nullptr), _data(data), _col(RED){}
};
2.2.2 红黑树的迭代器
- begin()与end()
STL明确规定,begin()与end()代表的是一段前闭后开的区间,而对红黑树进行中序遍历后,可以得到一个有序的序列,因此:begin()可以放在红黑树中最小节点(即最左侧节点)的位置,end()放在最大节点(最右侧节点)的下一个位置,关键是最大节点的下一个位置在哪块?
因为对end()位置的迭代器进行–操作,必须要能找最后一个元素,此处就不行,因此最好的方式是将end()放在头结点的位置,但是由于这次是模拟,我们先粗略模拟实现,就把end()的位置定义为nullptr
begin()和end()在迭代器模板类中先不实现,迭代器模板写完之后,而在其他类中电泳迭代器类对象,之后写出红黑树,map,set相应的begin()和end()。
- operator++()和operator–()
a.operator++()
所要满足的条件:
1.当前节点的右子树不为空,中序下一个访问的节点,右子树的最左节点
2.当前节点的右子树为空,下一个访问,倒着在祖先里面找,找孩子是父亲左孩子的祖先
// ++重载
// 左子树 根 右子树
// 1.当前节点的右子树不为空,中序下一个访问的节点,右子树的最左节点
// 2.当前节点的右子树为空,下一个访问,倒着在祖先里面找,找孩子是父亲左孩子的祖先
Self& operator++()
{// 判断右子树是否为空// 右节点存在if (_node->_right){// 查找右子树的最左节点Node* leftMin = _node->_right;// 查找while (leftMin->_left){leftMin = leftMin->_left;}// 再将最左节点在赋值给_node_node = leftMin;}else// 右子树不存在{// 倒着在祖先里面找,找孩子是父亲左孩子的祖先// 用cur来查找Node* cur = _node;// 记录父亲节点Node* parent = _node->_parent;// cur是右节点时则要进循环// 也要判断是否到根节点时的情况,parent为空while (parent && cur == parent->_right){cur = parent;parent = parent->_parent;}// 找到的祖先赋值给_node,根节点时,则直接返回空_node = parent;}// 直接返回++后的_nodereturn *this;
}
b.operator–()
所要满足的条件:
–it,与++方法相反 右子树 根节点 左子树
1.左不为空,左子树中序的最后一个节点(最右节点)
2.左为空,下一个访问,找孩子是父亲右的那个祖先节点
// --重载
// 则和++重载方法颠倒过来,就是反方向看,右子树 根 左子树
// 1.当前节点左子树存在时,下一个访问节点,查找左子树的最大节点
// 2.当前节点左子树不存在时,下一个访问节点,倒着往回走查找祖先,查找孩子是父亲右的祖先
Self& operator--()
{// 判断左孩子是否存在if (_node->_left)// 当前节点左子树存在时{Node* rightMost = _node->_left;// 一直查找到叶子节点while (rightMost->_right){rightMost = rightMost->_right;}_node = rightMost;}else// 当前节点左子树不存在时{Node* cur = _node;Node* parent = cur->_parent;// 倒着往回走查找祖先,查找孩子是父亲右的祖先// 结束条件:循环到根节点或者cur是parent的有孩子节点while (parent && cur == parent->_left){cur = parent;parent = parent->_parent;}_node = cur;}return *this;
}
总体迭代器
// 红黑树迭代器
// 给数据节点封装迭代器
// 类型 data T& T*
template<class T, class Ref, class Ptr>
struct __RBTreeIterator
{
public:// 相关重定义typedef RBTreeNode<T> Node;// 节点重定义// 这里只是一个模板,而__RBTreeIterator红黑树的迭代器实现模板里面的类型应在RBTree传递相应参数typedef __RBTreeIterator<T, Ref, Ptr> Self;// 迭代器重定义// 构造函数__RBTreeIterator(Node* node):_node(node){}// 解引用*// T&Ref operator*(){return _node->_data;}// 解引用->// T*Ptr operator->(){return &_node->_data;}// !=bool operator!=(const Self t){return _node != t._node;// 只用比较地址}// ==bool operator==(const Self t){return _node == t._node;}// ++重载// 左子树 根 右子树// 1.当前节点的右子树不为空,中序下一个访问的节点,右子树的最左节点// 2.当前节点的右子树为空,下一个访问,倒着在祖先里面找,找孩子是父亲左孩子的祖先Self& operator++(){// 判断右子树是否为空// 右节点存在if (_node->_right){// 查找右子树的最左节点Node* leftMin = _node->_right;// 查找while (leftMin->_left){leftMin = leftMin->_left;}// 再将最左节点在赋值给_node_node = leftMin;}else// 右子树不存在{// 倒着在祖先里面找,找孩子是父亲左孩子的祖先// 用cur来查找Node* cur = _node;// 记录父亲节点Node* parent = _node->_parent;// cur是右节点时则要进循环// 也要判断是否到根节点时的情况,parent为空while (parent && cur == parent->_right){cur = parent;parent = parent->_parent;}// 找到的祖先赋值给_node,根节点时,则直接返回空_node = parent;}// 直接返回++后的_nodereturn *this;}// --重载// 则和++重载方法颠倒过来,就是反方向看,右子树 根 左子树// 1.当前节点左子树存在时,下一个访问节点,查找左子树的最大节点// 2.当前节点左子树不存在时,下一个访问节点,倒着往回走查找祖先,查找孩子是父亲右的祖先Self& operator--(){// 判断左孩子是否存在if (_node->_left)// 当前节点左子树存在时{Node* rightMost = _node->_left;// 一直查找到叶子节点while (rightMost->_right){rightMost = rightMost->_right;}_node = rightMost;}else// 当前节点左子树不存在时{Node* cur = _node;Node* parent = cur->_parent;// 倒着往回走查找祖先,查找孩子是父亲右的祖先// 结束条件:循环到根节点或者cur是parent的有孩子节点while (parent && cur == parent->_left){cur = parent;parent = parent->_parent;}_node = cur;}return *this;}private:Node* _node;};
2.2.3 红黑树定义
插入:
插入中右一个要比较数据大小的阶段,但由于map中存放的数据是键值对pair<key,value>,而set中存放的数据是key,所以我吗用用一个同意分模板规范他们,使之让他们实例化出来相应的数据。
解决方案:map和set中用各自相应的仿函数返回相应的数据类型
// 红黑树类
// 由于要封装map和set,但是两个存放的数据类型不一样
// set存放的数据是Key,map存放的数据是pair<Key,Value>
// 所以将数据类型定义为如下// key value 数据比较仿函数
template<class K, class T, class KeyOft>
class RBTree
{
public:// 重定义typedef RBTreeNode<T> Node;// 迭代器typedef __RBTreeIterator<T, T&, T*> Iterator;// 普通迭代器typedef __RBTreeIterator<T, const T&, const T*> Const_Iterator;// const迭代器// 这个里面也是一个模板。为map和set封装提供// Begin// 查找红黑树最小值Iterator Begin(){Node* leftMin = _root;while (leftMin->_left){leftMin = leftMin->_left;}return Iterator(leftMin);}// End// 由于没有哨兵位// 先置为空Iterator End(){return Iterator(nullptr);}// 这个里面也是一个模板。为map和set封装提供// Begin// 查找红黑树最小值Const_Iterator Begin() const{Node* leftMin = _root;while (leftMin->_left){leftMin = leftMin->_left;}return Const_Iterator(leftMin);}// End// 由于没有哨兵位// 先置为空Const_Iterator End() const{return Const_Iterator(nullptr);}// 构造函数RBTree() = default;// 拷贝构造// 拷贝只能老老实实的拷贝,不能插入拷贝// 插入拷贝时,旋转可能会影响红黑树和原本的红黑树不一样RBTree(const RBTree<K, T, KeyOft>& t){_root = Copy(t._root);}// 赋值重载// t2 = t1RBTree<K, T, KeyOft> operator=(RBTree<K, T, KeyOft> t){swap(_root, t._root);return *this;}// 析构函数~RBTree(){Destroy(_root);_root = nullptr;}// 插入// 插入一个data// 返回值是一个pair类型,pair第一个参数是Iterator,第二个bool判断是否插入成功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* cur = _root;Node* parent = nullptr;while (cur){// 由于set和map的数据不一样,数据的比较不好比较// key// pair<key,value>// 两个数据类型都有着自己的比较方式// 在map和set中增加一个仿函数// 用一个仿函数的对象来调用,返回相应类型的数据if (kot(data) > kot(cur->_data)){parent = cur;cur = cur->_right;}else if (kot(data) < kot(cur->_data)){parent = cur;cur = cur->_left;}else{return make_pair(Iterator(cur), false);}}cur = new Node(data);Node* newnode = cur;// 记录一下cur的位置,可能旋转之后不知道cur的位置// 新插入节点,插入红色,可能违反规则三// 插入黑色,必然违反规则四// 所以新增节点为红色cur->_col = RED;if (kot(cur->_data) < kot(parent->_data)){parent->_left = cur;}else{parent->_right = cur;}cur->_parent = parent;// 进行红黑树颜色调整// 分为三种情况:// 1.情况一: 新增cur为红,p为红,g为黑,u存在且为红// 2.情况二: 新增cur为红,p为红,g为黑,u不存在/u存在且为黑// 3.情况三: 新增cur为红,p为红,g为黑,u不存在/u存在且为黑// 情况2和3可以合并为一种进行// uncle有大方向,uncle存在、uncle不存在、uncle存在且wei// 以上情况的解决方案一般parent的颜色是黑色就结束了// 或者为根节点时,parent为空跳出循环while (parent && parent->_col == RED){// grandfather节点Node* grandfather = parent->_parent;// 先看uncle和parent节点的关系// parent节点是grandfather节点的左孩子if (parent == grandfather->_left){// uncle为右孩子Node* uncle = grandfather->_right;// 由于都是uncle节点在变化,关键看uncle节点// 看uncle是否存在,uncle节点的颜色// 情况一: 新增cur为红,p为红,g为黑,u存在且为红// 解决方案:p,u变为黑色,g变为红色// 如果为根节点,则g不变,g的parent为空会跳出循环,在循环外面更改_root节点if (uncle && uncle->_col == RED){parent->_col = uncle->_col = BLACK;grandfather->_col = RED;// 继续向上调整,直到调整到根节点// cur找g,g找g的祖父cur = grandfather;parent = cur->_parent;}else// 其他情况,u为空,或者u节点存在并且为黑色{// 情况2、3:新增cur为红,p为红,g为黑,u不存在/u存在且为黑// 用cur新增节点插入在parent的左右孩子,来将情况2,3分开// cur在左孩子if (cur == parent->_left){// g // p u// c // 进行右单旋RotateR(grandfather);//变色parent->_col = BLACK;grandfather->_col = RED;}else// cur在左孩子{// g // p u// c // 进行左右旋转RotateL(parent);RotateR(grandfather);// 变色cur->_col = BLACK;grandfather->_col = RED;}break;}}else// parent节点是grandfather节点的右孩子{// uncle为左孩子Node* uncle = grandfather->_left;// 情况一: 新增cur为红,p为红,g为黑,u存在且为红// 解决方案:p,u变为黑色,g变为红色if (uncle && uncle->_col == RED){parent->_col = uncle->_col = BLACK;grandfather->_col = RED;// 向上调整cur = grandfather;parent = cur->_parent;}else{// 情况2、3:新增cur为红,p为红,g为黑,u不存在/u存在且为黑// 用cur新增节点插入在parent的左右孩子,来将情况2,3分开// cur在右孩子if (cur == parent->_right){// g// u p// c// 进行左单旋RotateL(grandfather);//变色parent->_col = BLACK;grandfather->_col = RED;}else{// g// u p// c// 进行右左单旋RotateR(parent);RotateL(grandfather);grandfather->_col = RED;cur->_col = BLACK;}// 旋转之后直接跳出循环break;}}}// 将根节点颜色变为黑色_root->_col = BLACK;return make_pair(Iterator(newnode), true);}// 右单旋void RotateR(Node* grandfather){Node* parent = grandfather->_left;Node* subR = parent->_right;grandfather->_left = subR;if (subR != nullptr){subR->_parent = grandfather;}parent->_right = grandfather;Node* ppNode = grandfather->_parent;grandfather->_parent = parent;// parent的节点最后与上面判断// 判断根节点if (ppNode == nullptr){_root = parent;}else{// parent在ppnode的哪个孩子if (ppNode->_right == grandfather){ppNode->_right = parent;}else{ppNode->_left = parent;}parent->_parent = ppNode;}}// 左旋转void RotateL(Node* grandfather){Node* parent = grandfather->_right;Node* subL = parent->_left;grandfather->_right = subL;if (subL != nullptr){subL->_parent = grandfather;}Node* ppNode = grandfather->_parent;grandfather->_parent = parent;parent->_left = grandfather;// 判断是否为根节点if (ppNode == nullptr){_root = parent;}else{// parent在ppnode的哪个孩子if (ppNode->_right == grandfather){ppNode->_right = parent;}else{ppNode->_left = parent;}parent->_parent = ppNode;}}// 查找// 返回值类型是迭代器类型// 用key来查找位置Iterator Find(const K& key){// 从头节点开始查找Node* cur = _root;// 结束条件是while (cur){if (cur->_key < key){cur = cur->_right;}else if (cur->_key > key){cur = cur->_left;}else{return Iterator(cur);}}return Iterator(nullptr);}// 中序排序void InOrder(){_InOrder(_root);}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*// 拷贝构造:深拷贝,应用递归拷贝Node* Copy(Node* root){// 判断空表时if (root == nullptr){return nullptr;}// 创建一个节点,拷贝当前节点Node* newroot = new Node(root->_data);newroot->_col = root->_col;// 颜色拷贝// 拷贝左子树Copy(root->_left);// 左子树不为空时,左子树的_parent指向newrootif (newroot->_left){newroot->_left->_parent = newroot;}// 拷贝右子树Copy(root->_right);if (newroot->_right){newroot->_right->_parent = 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 (blackNum != refNum){cout << "存在黑色节点的数量不相等的路径" << endl;return false;}return true;}// 1.不能有连续的红色节点,遍历红色节点,检查红色节点的父亲节点是不是红色if (root->_col == RED && root->_parent->_col == RED){// cout << root->_kv.first << "存在有连续的红色节点" << endl;return false;}// 2.每条路径黑色节点的数量,每一个黑色节点,记录一下// 根节点到当前路径黑色节点的数量,任意计算一条路径当左参考值,之后与其他路径比较if (root->_col == BLACK){blackNum++;}return Check(root->_left, blackNum, refNum) && Check(root->_left, blackNum, refNum);}// 中序排序基本void _InOrder(Node* root){if (root == nullptr){return;}_InOrder(root->_left);cout << root->_kv.first << ":" << root->_kv.second << endl;_InOrder(root->_right);}Node* _root = nullptr;
};
2.3 Mymap.h
map就是一个红壳,只用调用红黑树当中的方法
// map是Key,pair<key,value>模型
namespace lc
{// Key,Value模型template<class K, class V>class map{// 由于map和set的数据不一样// 写一个仿函数,用来给与插入比较数据struct MapKeyOft{// 返回值为K类型const K& operator()(const pair<K, V>& kv){return kv.first;}};public:// 重定义// 引入红黑树迭代器// 未实例化的类模板,不知道迭代器的类型,在前面加上typename解决typedef typename RBTree<K, pair<K, V>, MapKeyOft>::Iterator iterator;typedef typename RBTree<K, pair<const K, V>, MapKeyOft>::Const_Iterator const_iterator;// 普通迭代器// 直接点调用迭代器中的Beginiterator begin(){return _t.Begin();}// 调用模板End()iterator end(){return _t.End();}// const迭代器// 直接点调用迭代器中的Beginconst_iterator begin() const{return _t.Begin();}// 调用模板End()const_iterator end() const{return _t.End();}// 插入// 调用红黑树的插入方法// 这里的map插入相应的数据参数pair<iterator,bool> Insert(const pair<K, V>& kv){return _t.Insert(kv);}// 查找iterator Find(const K& key){_t.Find(key);}// []重载// 用参数first找到second// 返回值得到secondV& operator[](const K& key){pair<iterator, bool> ret = _t.Insert(make_pair(key, V()));return ret.first->second;}private:// 成员变量就是一个红黑树RBTree<K, pair<K, V>, MapKeyOft> _t;};}
map的测试用例
void test_map1(){map<string, int> m;m.Insert({ "苹果",1 });m.Insert({ "香蕉",1 });m.Insert({ "梨",1 });m.Insert({ "苹果",3 });map<string, int>::iterator it = m.begin();while (it != m.end()){//it->first += 'x';it->second += 1;//cout << it.operator->()->first << ":" << it->second << endl;cout << it->first << ":" << it->second << endl;++it;}cout << endl;}void test_map2(){string arr[] = { "苹果", "香蕉", "梨", "苹果", "草莓", "苹果", "草莓",
"香蕉", "香蕉", "梨", "梨","梨","梨", "草莓","草莓" };map<string, int> countMap;for (auto& e : arr){countMap[e]++;}for (auto& kv : countMap){cout << kv.first << ":" << kv.second << endl;}cout << endl;}
2.4 Myset.h
set只是相当于一个空壳,只用调用相应的红黑树中的方法
// set存放的是K,K模型
// 底层都是红黑树
namespace lc
{template<class K>class set{// 由于map和set的数据不一样// 写一个仿函数,用来给与插入比较数据struct SetKeyOft{const K& operator()(const K& key){return key;}};public:// 重定义// 引入红黑树迭代器// Iterator未实例化的类模板,不知道他的类型,在前面加上typename解决typedef typename RBTree<K, K, SetKeyOft>::Iterator iterator;typedef typename RBTree<K, const K, SetKeyOft>::Const_Iterator const_iterator;// 直接点调用迭代器中的Beginiterator begin(){return _t.Begin();}// 调用模板End()iterator end(){return _t.End();}// const迭代器const_iterator begin() const{return _t.Begin();}// 调用模板End()const_iterator end() const{return _t.End();}// 插入// 调用红黑树的插入方法// 这里的map插入相应的数据参数pair<iterator, bool> Insert(const K& key){return _t.Insert(key);}// 查找iterator Find(const K& key){_t.Find(key);}private:RBTree<K, K, SetKeyOft> _t;};
set测试用例:
void test_set1(){set<int> s;s.Insert(4);s.Insert(2);s.Insert(5);s.Insert(15);s.Insert(7);s.Insert(1);s.Insert(5);s.Insert(7);set<int>::iterator it = s.begin();while (it != s.end()){// *it += 5;cout << *it << " ";// 测试--/*if (*it == 7){--it;cout << *it << endl;break;}*/++it;}cout << endl;/*for (auto e : s){cout << e << endl;}*//*set<int> copy = s;for (auto e : copy){cout << e << " ";}cout << endl;*/// cout << copy._t.IsBalance() << endl;}}