map和set的底层都是通过红黑树来实现的,但并不是原生态的红黑树,而是经过改造后的红黑树。且容器都会在各自的类中添加一些独特的函数来解决各自适配的问题

map和set底层是改造后的红黑树,我们先来看看改造后的红黑树

和普通的红黑树不同的是,在根节点上再加了一个头结点,该结点不是真实的结点,只是一个辅助结点,是为了后面实现红黑树的迭代器而出现的。该header结点的父节点就是真实的根节点,其左孩子是这棵树的最左结点,其右孩子是这棵树的最右节点。
我们现在通过STL源码来简单剖析一下map和set中如何利用红黑树来实现各自不同的功能的

在map中有两个泛型参数,一个是Key,一个是T,也就是value。其中Key的别名是key_type,然后再将Key和T作为pair对象的一个泛型参数,将其的别名改为value_type。成员只有一棵树,该树就是红黑树,红黑树有两个泛型参数,一个是Key,一个是Value。Key就是红黑树中结点的值的类型,Value就是结点Key对应的Value值。结点中继承了一个结点类,其就相当有5个成员,颜色、父类指针、左孩子指针、右孩子指针、结点的值。

在set中只有一个泛型参数Key。在该容器中,由于使用红黑树底层必须提供两个泛型参数,set就将vkey当做value。此时传过去的红黑树的泛型参数就是相同的,都是Key。

所以两个容器的不同,起最关键的就是value类型不同,map容器底层中的红黑树的value是一个pair对象;而set容器中的的红黑树的value就是set中本身的key。在红黑树中做特殊处理,就可以获得他们的value。

以下代码就是对红黑树的一种改进,适配于map和set关联容器

#include <iostream>
#include <utility>
#include <algorithm>
using namespace std;enum COLOR
{BLACK,RED
};template <class V>
struct RBTreeNode
{RBTreeNode<V>* _parent; //父节点RBTreeNode<V>* _left; //左孩子RBTreeNode<V>* _right; //右孩子V _val;COLOR _color; //颜色RBTreeNode(const V& val = V()):_parent(nullptr), _left(nullptr), _right(nullptr), _val(val), _color(RED){}
};template <class K, class V, class KeyOfValue>
class RBTree
{public:typedef RBTreeNode<V> Node;RBTree():_header(new Node){//创建空树_header->_left = _header->_right = _header;}bool insert(const V& val){if (_header->_parent == nullptr){Node* root = new Node(val);_header->_parent = root;root->_parent = _header;_header->_left = _header->_right = root;//根节点为黑色root->_color = BLACK;return true;}Node* cur = _header->_parent;Node* parent = nullptr;KeyOfValue kov;//1.寻找到要插入的结点的位置while (cur){parent = cur;if (kov(cur->_val) == kov(val))return false;else if (kov(cur->_val) > kov(val))cur = cur->_left;elsecur = cur->_right;}//2.创建节点cur = new Node(val);if (kov(parent->_val) > kov(cur->_val))parent->_left = cur;elseparent->_right = cur;cur->_parent = parent;//3.颜色的修改或者结构的调整while (cur != _header->_parent && cur->_parent->_color == RED)//不为根且存在连续红色,则需要调整{parent = cur->_parent;Node* gfather = parent->_parent;if (gfather->_left == parent){Node* uncle = gfather->_right;//情况1.uncle存在且为红if (uncle && uncle->_color == RED){parent->_color = uncle->_color = BLACK;gfather->_color = RED;//向上追溯cur = gfather;}else{if (parent->_right == cur)//情况3{RotateL(parent);swap(cur, parent);}//情况2.uncle不存在或者uncle为黑RotateR(gfather);parent->_color = BLACK;gfather->_color = RED;break;}}else{Node* uncle = gfather->_left;if (uncle && uncle->_color == RED){parent->_color = uncle->_color = BLACK;gfather->_color = RED;//向上追溯cur = gfather;}else{if (parent->_left == cur){RotateR(parent);swap(cur, parent);}RotateL(gfather);parent->_color = BLACK;gfather->_color = RED;break;}}}//根节点为黑色_header->_parent->_color = BLACK;//更新头结点的左右指向_header->_left = leftMost();_header->_right = rightMost();return true;}void RotateL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;if (subRL)subRL->_parent = parent;if (parent == _header->_parent){_header->_parent = subR;subR->_parent = _header;}else{Node* gfather = parent->_parent;if (gfather->_left == parent)gfather->_left = subR;elsegfather->_right = subR;subR->_parent = gfather;}subR->_left = parent;parent->_parent = subR;}void RotateR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;if (subLR)subLR->_parent = parent;if (parent == _header->_parent){_header->_parent = subL;subL->_parent = _header;}else{Node* gfather = parent->_parent;if (gfather->_left == parent)gfather->_left = subL;elsegfather->_right = subL;subL->_parent = gfather;}subL->_right = parent;parent->_parent = subL;}Node* leftMost(){Node* cur = _header->_parent;while (cur && cur->_left){cur = cur->_left;}return cur;}Node* rightMost(){Node* cur = _header->_parent;while (cur && cur->_right){cur = cur->_right;}return cur;}
private:Node* _header;
};template<class K, class T>
class Map
{struct MapKeyOfValue{const K& operator()(const pair<K, T>& val){return val.first;}};
public:bool insert(const pair<K, T>& kv){return _rbt.insert(kv);}T& operator[](const K& key){bool ret = _rbt.insert(make_pair(key, T()));}private:typedef RBTree<K, pair<K, T>, MapKeyOfValue> rbt;rbt _rbt;
};template <class K>
class Set
{struct SetKeyOfValue{const K& operator()(const K& val){return val;}};public:bool insert(const K& val){return _rbt.insert(val);}private:typedef RBTree<K, K, SetKeyOfValue> rbt;rbt _rbt;
};

迭代器

我们原生的Node结点迭代器是无法实现迭代器的普通操作的,所以我们必须要对结点进行另一层的封装,重载对应的操作运算符。在该类中只有一个成员变量,就是Node结点。

对迭代器的解引用,就是获得迭代器的val值

 V& operator*(){return _node->_val;}

对迭代器的箭头操作,就是获得迭代器值的地址

 V* operator->(){return &_node->_val;}

两个迭代器的判等操作就是结点的地址是否相同

 bool operator!=(const Self& it){return _node != it._node;}

迭代器的++和- -是要符合中序遍历的有序遍历,下面我们来一起分析
迭代器的begin()位置应该是树中最小的结点,而树中最小的结点就是树的最左结点;迭代器的end()位置应该是树中最大的结点,而树中最大的结点就是树的最右结点

迭代器的++操作分为两种情况,第一种是存在右子树的情况,第二种是不存在右子树的情况

当存在右子树时,我们需要遍历到右子树的最左结点,然后访问该结点。例如我们现在的迭代器在8的位置,根据中序遍历的条件,我们应该访问的是10号结点,所以我们先走到8结点的右子树11号结点,然后遍历11的最左结点,该结点为10,也正是我们要访问的结点

     if (_node->_right){//右子树的最左结点_node = _node->_right;while (_node->_left){_node = _node->_left;}}

第二种情况是不存在右子树。当不存在右子树,我们需要向上回溯,中序遍历的遍历规则就是左孩子、根、右孩子。所以我们要判断当前节点是父节点的左孩子还是右孩子,如果是左孩子则表示父节点还未访问,此时需要访问父节点;如果是右孩子则表示父节点也访问过了,需要向上回溯,直至该结点不为父节点的右孩子。例如我们节点在5号结点的位置,需要判断该结点父节点6号结点的左边还是右边,此时5号结点再6号结点的左边,可以表示6号结点也未访问,此时将迭代器更新的父节点的位置即可。

当我们迭代器在7号结点时,7号结点时在6号结点的右边,此时需要向上回溯,让结点更新置父节点,然后父节再向上回溯至其父节点的位置,再判断当前节点的位置是否为父节点的右边,此时6号结点是8号结点的左边,表示8号结点还未访问,此时访问8号结点即可

但是我们需要考虑到一种特殊的情况,就是该树的根节点没有右孩子时

正常来说,我们对迭代器的++操作应该会移动到空的头结点的位置,但是我们再回溯的过程中会出现问题。此时因为it没有右结点,需要判断该结点是在父节点的左边还是右边,此时是在右边,就会向上回溯

这样子对迭代器的++是一个死操作,永远不会走到空的位置。所以我们需要再跟结点处做特殊的处理。当node结点的右孩子为自己的父亲时,就不用更新结点了,此时已经走到end()的了,如果还更新置parent结点,则该++操作就没有发生改变

我们现在进行测试
迭代器部分代码

template <class  V>
struct RBTreeIterator
{typedef RBTreeNode<V> Node;typedef RBTreeIterator<V> Self;Node* _node;RBTreeIterator(Node* node):_node(node){}//解引用V& operator*(){return _node->_val;}V* operator->(){return &_node->_val;}bool operator!=(const Self& it){return _node != it._node;}Self& operator++(){if (_node->_right) //存在右节点{//右子树的最左结点_node = _node->_right;while (_node->_left){_node = _node->_left;}}else //不存在右节点{Node* parent = _node->_parent;while (_node == parent->_right)//回溯{_node = parent;parent = parent->_parent;}//特殊情况:根节点没有右孩子,则不需要更新结点if (_node->_right != parent) _node = parent;}return *this;}
};

红黑树添加迭代器代码

 typedef RBTreeIterator<V> iterator;iterator begin(){return iterator(_header->_left);}iterator end(){return iterator(_header);}

map中添加红黑树迭代器代码

 typedef typename RBTree<K, pair<K, T>, MapKeyOfValue>::iterator iterator;iterator begin(){return _rbt.begin();}iterator end(){return _rbt.end();}

测试结果:

这里直接给出迭代器- -的代码,原理和++类似

在迭代器类中

 Self& operator--(){if (_node->_left){//右子树的最左结点_node = _node->_left;while (_node->_right){_node = _node->_right;}}else{Node* parent = _node->_parent;while (_node == parent->_left){_node = parent;parent = parent->_parent;}if (_node->_left != parent)_node = parent;}return *this;}

在红黑树类中添加反向迭代器用于测试

iterator rbegin(){return iterator(_header->_right);}

map中也添加反向迭代器

 iterator rbegin(){return _rbt.rbegin();}

测试:

方括号[]

插入的返回值是返回一个pair对象,所以我们在插入时的返回值都需要修改成一个pair对象,且pair对象的first为插入后的结点的迭代器,second为插入是否成功

 pair<iterator, bool> insert(const V& val){if (_header->_parent == nullptr){Node* root = new Node(val);_header->_parent = root;root->_parent = _header;_header->_left = _header->_right = root;//根节点为黑色root->_color = BLACK;return make_pair(iterator(root), true);}Node* cur = _header->_parent;Node* parent = nullptr;KeyOfValue kov;//1.寻找到要插入的结点的位置while (cur){parent = cur;if (kov(cur->_val) == kov(val))return make_pair(iterator(cur), false);else if (kov(cur->_val) > kov(val))cur = cur->_left;elsecur = cur->_right;}//2.创建节点cur = new Node(val);Node* node = cur;if (kov(parent->_val) > kov(cur->_val))parent->_left = cur;elseparent->_right = cur;cur->_parent = parent;//3.颜色的修改或者结构的调整while (cur != _header->_parent && cur->_parent->_color == RED)//不为根且存在连续红色,则需要调整{parent = cur->_parent;Node* gfather = parent->_parent;if (gfather->_left == parent){Node* uncle = gfather->_right;//情况1.uncle存在且为红if (uncle && uncle->_color == RED){parent->_color = uncle->_color = BLACK;gfather->_color = RED;//向上追溯cur = gfather;}else{if (parent->_right == cur)//情况3{RotateL(parent);swap(cur, parent);}//情况2.uncle不存在或者uncle为黑RotateR(gfather);parent->_color = BLACK;gfather->_color = RED;break;}}else{Node* uncle = gfather->_left;if (uncle && uncle->_color == RED){parent->_color = uncle->_color = BLACK;gfather->_color = RED;//向上追溯cur = gfather;}else{if (parent->_left == cur){RotateR(parent);swap(cur, parent);}RotateL(gfather);parent->_color = BLACK;gfather->_color = RED;break;}}}//根节点为黑色_header->_parent->_color = BLACK;//更新头结点的左右指向_header->_left = leftMost();_header->_right = rightMost();//return true;return make_pair(iterator(node), true);}

在map中也要修改

 pair<iterator, bool> insert(const pair<K, T>& kv){return _rbt.insert(kv);}T& operator[](const K& key){pair<iterator, bool> ret = _rbt.insert(make_pair(key, T()));//ret.first 迭代器//迭代器-> pair<k,v>对象//pair<k,v>->second,获得vreturn ret.first->second; }

测试:

C++ map的简单实现相关推荐

  1. STL快速入门学习教程之map的简单使用

    STL快速入门学习教程之map的简单使用 map是STL中的一个关联容器,它以一对一的数据进行整理(第一个数值称为关键字,且这个关键字只能在map中出现一次,第二个数值称为前关键字的值),正是由于这种 ...

  2. 工作小笔记——对MLE和MAP的简单理解

    文章目录 前言 1. 问题描述 1.1 MLE 1.2 MAP 2. 简单通信系统的例子 2.1 MLE解调 2.2 MAP解调 3. 数据拟合 3.1 MLE的推导及其与最小二乘的关系 3.2 MA ...

  3. java map缓存6_Java内存缓存-通过Map定制简单缓存

    缓存 在程序中,缓存是一个高速数据存储层,其中存储了数据子集,且通常是短暂性存储,这样日后再次请求此数据时,速度要比访问数据的主存储位置快.通过缓存,可以高效地重用之前检索或计算的数据. 为什么要用缓 ...

  4. Java内存缓存-通过Map定制简单缓存

    缓存 在程序中,缓存是一个高速数据存储层,其中存储了数据子集,且通常是短暂性存储,这样日后再次请求此数据时,速度要比访问数据的主存储位置快.通过缓存,可以高效地重用之前检索或计算的数据. 为什么要用缓 ...

  5. python lambda map reduce_简单了解python filter、map、reduce的区别

    这篇文章主要介绍了简单了解python filter.map.reduce的区别,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 python中有一些 ...

  6. google map的简单二次开发

    Google Maps API是Google自己推出编程API,可 以让全世界对Google Map有兴趣的程序设计师自 行开发基于Google Maps的服务,建立自己的地图 网站. 简单的讲就是g ...

  7. java map的应用_JAVA map的简单应用

    第一次写内容,比较简单希望大家见谅. 相信很多新码农都会使用if判断,通过验证if 进而继续编写业务流程,但是有的时候if也并不是很好用.比如,判定条件有许多的时候.下面举个例子. 联调webserv ...

  8. google map的简单应用-显示华南理工大学

    写一个简单的Google Map 的应用程序,一个显示我们学校(South China University of Technology)地图的网页 通过Google搜索获取我们学校的经度纬度 使用G ...

  9. 西北大学集训队选拔赛 F-三生三世(STL set和map的简单应用)

    F-三生三世 题目链接. 题目描述: 秦皇岛的海风轻轻地唱着歌唤醒了水上的涟漪,冬日的阳光把沙滩洒满了金黄. BD哥在沙滩上留下了一串串脚印,突然他发现了一个石碑,上面刻着"HQDB&quo ...

最新文章

  1. solaris10修改IP
  2. 重磅!2021泰晤士世界大学排名公布,清华排名首次挺进top20
  3. 手把手玩转win8开发系列课程(18)
  4. 从零写一个编译器(七):语义分析之符号表的数据结构
  5. 暑假集训中期测试 Problem D: 装箱问题2 (并查集)
  6. Bootstrap 按钮的尺寸
  7. vps没有mysql怎么用商店_如何在本地搞一个小程序的服务器之我没有vps我也很绝望呀...
  8. 微信退款参数格式错误
  9. python程序打包_python之程序打包
  10. Oracle停止数据泵,如何停止重启数据泵任务
  11. android模仿微信浮窗,Android仿微信视屏悬浮窗效果
  12. 计算机专业毕业设计流程,计算机专业毕业设计答辩流程
  13. Python L型组件填图问题(棋盘覆盖问题)
  14. java出现令牌语法错误_java – 令牌上的语法错误
  15. 卡尔曼滤波器:用R语言中的KFAS建模时间序列
  16. 使用Java和FFempeg批量转码B站缓存下来的列表视频,成MP4格式
  17. amd linux显卡驱动,AMD Radeon系列显卡催化剂驱动14.4 正式版For Linux AMD Radeon系列显卡催化剂驱动14.4 正式版 显卡驱动 超威半导体...
  18. Android 11.0 12.0TvSettings系统设置遥控器home键退不出主页面功能的修复
  19. java 文字转换成语音 代码_java文字转语音播报功能的实现方法
  20. 关于正交矩阵的二三事

热门文章

  1. unity两个项目合并 同名_表格合并,你还在复制粘贴?教你一键合并,超简单!...
  2. android代码无法访问data目录,解决Android7.1.1中无法打开/data目录的问题
  3. 计算机系统由低到高分层,下列选项列出计算机系统由低到高分层顺序中.doc
  4. ibm服务器更换主板怎么恢复系统,记号一次更换IBM X3650M4主板后RAID无法启动的解决...
  5. DispatcherServlet详解
  6. python中MySQLdb模块用法实例
  7. Spring Cloud 子项目介绍
  8. Caffe Batch Normalization推导
  9. jQuery hover事件
  10. 200个 jquery插件