C++ 第九节——map/set(用法+底层原理+模拟实现)
有了前面红黑树的底子,我们这一节的任务就比较轻松了。
关于Map和Set是什么东西,我们来借助网络文献进行解释。
首先,我们需要知道的是,Map和Set的底层都是红黑树。
即是一种平衡的二叉搜索树,也就是二叉平衡搜索树。
而set就是我们前面说到的Key模型,而map就是<K,V>模型。
我们接下来将一边对比,一边介绍。
set和map的介绍
先来看set:
通过查阅文档有关set的声明,我们可以发现:
这里的T就是我们所说的Key,就是key_type或者说是value_type,即比较大小,比较的就是这些东西。
而后的Compare,就是比较的方式。其是一个仿函数,就是告诉set,我的比较方式是怎样的。这里可参照链表的有关叙述,这里就不再赘述。
紧接着,就着下面的接口模型,我们可以简单地创建一个set然后遍历它。
这里的都是一些成员函数,没有什么好说的了。
像这些函数,我们早就已经用烂掉了。类比着前面的一些容器,我们在这里也就略过,具体我们可以看下面的用法实例。
我们再来看一下find函数吧:
它的意思已经很明确了也。
就是说,找到了那个位置,就返回那个位置的迭代器。否则,就返回set::end()。
void test_set()
{set<int> s; //创建s.insert(3); s.insert(4);s.insert(8);s.insert(10);s.insert(1);s.insert(26);s.insert(7);s.insert(2);s.insert(3); //插入set<int>::iterator is = s.begin(); //定义迭代器while (is != s.end()){cout << *is << " "; //循环打印is++;}cout << endl;for (auto e : s) //另外一种打印方式——用范围for打印{cout << e << " ";}//排序+去重
}void test_set1()
{set<string> ss;ss.insert("hello"); //不断插入字符串ss.insert("map");ss.insert("set");ss.insert("string");ss.insert("wrong");set<string>::iterator it = ss.find("wron"); //查找,找到了则返回相应位置的迭代器,找不到 //就返回ss.end()if (it == ss.end()){cout << "没有找到" << endl;}else{cout << "找到了" << endl;}
}int main()
{test_set();test_set1();return 0;
}
打印出来的结果如上图。
同样的道理,我们再来看看map
map和set其实已经非常像了,它就是多了一个参数。(就是我们上面说烂掉的K,V模型,在存储着key的值的时候,每一个Key的值后面还跟着一个Value)
如上图所示,其在后面跟着的实际还有着一个T。也就是我们所说的value。
它的用法与set略有一点不同的是,我们需要用pair来进行插入。而关于pair是什么,我们在上一节就已经提到过,在这里,我们再来说一遍:
就是说,pair本质上也是一个类,它存储着两个类型的值,它的用处和好处就是当返回一个pair的时候,就相当于返回了两个值。
所以pair就相当于了一个打包的功能。我将两个值打包在一块,然后一块返回。(不过它本质上还是一个类)
当然,我们也可以用make_pair来去实现
make_pair本质上是对pair类型的一个再封装。
可以看到,一个make_pair的返回值,实际上就是一个pair。
下面的代码蕴藏了很多东西,其中包括很多内容的不同写法。各位可以细细品味一下。
void test_map1()
{map<int, double> m; //K V模型m.insert(pair<int, double>(1, 1.1));//调用pair的构造函数,构造一个匿名对象m.insert(pair<int, double>(2, 2.1));m.insert(pair<int, double>(5, 3.4));m.insert(make_pair(2, 2.2)); //调用函数模板,构造对象,好处是不需要声明pair的参数, //让函数去自己推演,用起来更方便
// key相同就会插入失败map<int, double>::iterator it = m.begin();while (it != m.end()){cout << (*it).first<<":"<<(*it).second << endl;cout << it->first << ":" << it -> second << endl;it++;}cout << endl;
}void test_map2()
{typedef map<string, string> DICT;typedef pair<string, string> D_KV;DICT dict;dict.insert(D_KV("hello", "你好"));dict.insert(D_KV("fine", "我好"));dict.insert(D_KV("nice", "很好"));dict.insert(D_KV("no", "不好"));dict.insert(D_KV("OK", "好"));DICT::iterator it = dict.find("hello");if (it != dict.end()){cout << it->second << endl;string& str = it->second;str.insert(0, "{");str += "}";cout << str << endl;}
}void test_map3()
{//也可以从文件当中去读!!文件操作!!!string arr[] = { "苹果","苹果" ,"苹果", "西瓜","梨子","西瓜","榴莲","香蕉","香蕉","苹果" };map<string, int> mp;/*for (const auto& str : arr){map<string, int>::iterator it = mp.find(str);if (it != mp.end()){it->second++;}else{mp.insert(pair<string, int>(str, 1));}}*///统计次数的方式1/*for (const auto& e : arr){pair<map<string, int>::iterator, bool> ret = mp.insert(pair<string, int>(e, 1));if (ret.second == false){ret.first->second++;}}*///统计方式2// 下面是统计方式3for (const auto& e : arr){mp[e]++; //[]的返回值为mapped_value,第一个返回值为一个iterator的迭代器,第二个为一个bool类型的值。}for (const auto& e : mp){cout << e.first << ":" << e.second << endl;}map<string, string> dic;dic.insert(make_pair("insert", "插入"));dic["left"]; //插入dic["left"] = "左边"; //修改dic["right"] = "右边"; //插入+修改dic["left"] = "左边、剩余";//mapped_value& operator[](const key_value& K)//{ return (this->insert(make_pair(K,mapped_value())).first).second}
}void test_map4()
{string arr[] = { "苹果","苹果" ,"苹果", "西瓜","梨子","西瓜","榴莲","香蕉","香蕉","苹果" };map<string, int> mp;for (const auto& e : arr){mp[e]++; //[]的返回值为mapped_value,第一个返回值为一个iterator的迭代器,第二个为一个bool类型的值。}//假设需要对水果次序排序vector<map<string, int>::iterator> vp; //我们用迭代器的目的,就是为了减少拷贝,深拷贝很浪费时间map<string, int>::iterator it = mp.begin();while (it != mp.end()){vp.push_back(it);it++;}sort(vp.begin(), vp.end(), Comp());//也可以直接利用map排序 存在拷贝问题 拷贝了pair数据map<int, string,greater<int>> mmp;for (const auto& e : mp){mmp.insert(make_pair(e.second, e.first));}set<map<string, int>::iterator, Comp> SortSet; //也可以利用set去排while (it != mp.end()){SortSet.insert(it);it++;}typedef map<string, int>::iterator M_IT;priority_queue<M_IT, vector<M_IT>, Comp> pq;while (it != mp.end()){pq.push(it);it++;}
}void test_map5()
{map<string, string> dict;dict.insert(make_pair("left", "左边"));dict.insert(make_pair("left", "还是左边"));multimap<string, string> mdict;mdict.insert(make_pair("left", "左边")); //允许键值冗余;不存在[]mdict.insert(make_pair("left", "还是左边"));}
关于map的实际应用价值,其中有一个就是用来字典查找。
就如上面的例子所示,通过Key的值来去查找,如果找到了的话,我们就可以将其pair->second打印出来。
有关map和set的用法的内容,就暂时到这里了。还是比较简单的
我们下面来看其模拟实现:
set和map的模拟实现:
我们用一个红黑树同时实现map和set两种容器的封装。
我们这次重点来关注其是怎么实现这样的封装以及有关迭代器的实现的知识。关于红黑树的一些知识,本节就已经不再是重点关注的对象了。
废话少说,我们直接上代码。还是那句话,一切尽在代码中。实际上,这里的迭代器的模拟和链表还是非常的像的。
BRTree.h(我们复用上一节的红黑树的代码)
#pragma once
#include<iostream>
using namespace std;
enum Colour
{RED,BLACK,
};
template<class T> //这里的模板统一改成T,即要么是K,要么是pair<K,V>
struct RBTreeNode
{RBTreeNode<T>* _left;RBTreeNode<T>* _right;RBTreeNode<T>* _parent;T _data;Colour _col; //给出个参考颜色(用于标识红或者黑)RBTreeNode(const T& x) //红黑树结点的构造函数:_left(nullptr),_right(nullptr),_parent(nullptr),_data(x),_col(RED){}};template<class T, class Ref, class Ptr> //T T& T*
struct __TreeIterator
{typedef Ref reference;typedef Ptr pointer;typedef RBTreeNode<T> Node;typedef __TreeIterator<T, Ref, Ptr> Self;Node* _node;__TreeIterator(Node* node):_node(node){}Ref operator*(){return _node->_data;}Ptr operator->(){return &_node->_data;}bool operator != (const Self& s) const{return _node != s._node;}bool operator==(const Self& s) const{return !(_node != s._node);}Self operator++() //前置++{if (_node->_right){Node* left = _node->_right;while (left->_left){left = left->_left;}_node = left;}else{Node* cur = _node;Node* parent = cur->_parent;while (parent && cur == parent->_right){cur = cur->_parent;parent = parent->_parent;}_node = parent;}return *this;}Self operator--(){if (_node->_left)//根结点的左子树不为空{Node* right = _node->_left;//那么就去找左子树的最右结点while (right->_right){right = right->_right;}_node = right;}else{Node* cur = _node;Node* parent = cur->_parent;while (parent && cur == parent->_left){cur = cur->_parent;parent = parent->_parent;}_node = parent;}return *this;}
};//迭代器适配器
template <class Iterator>
struct ReverseIterator
{typedef typename Iterator::reference Ref;typedef typename Iterator::pointer Ptr;typedef ReverseIterator<Iterator> Self;//迭代器萃取?ReverseIterator(Iterator it):_it(it){}Ref operator*(){return *_it;}Ptr operator->(){return _it.operator->();//?}Self& operator--(){++_it;return *this;}Self& operator++(){--_it;return *this;}bool operator !=(const Self& s) const{return !(_it == s._it);}Iterator _it;
};template<class K, class T ,class KeyOfT>
class RBTree
{typedef RBTreeNode<T> Node;
public:typedef __TreeIterator<T, T&, T*> iterator;typedef __TreeIterator<T, const T&, const T*> const_iterator;typedef ReverseIterator<iterator> reverse_iterator;reverse_iterator rbegin(){Node* right = _root;while (right && right->_right){right = right->_right;}return reverse_iterator(iterator(right));}reverse_iterator rend(){return reverse_iterator(iterator(nullptr));}iterator begin(){Node* left = _root;while (left && left->_left){left = left->_left;}return iterator(left);}iterator end(){return iterator(nullptr);}RBTree():_root(nullptr) //构造函数初始化{}//拷贝构造和operator自己实现void Destroy(Node* root)//销毁函数{if (root == nullptr)return;Destroy(root->_left); //通过不断递归来去实现,类比二叉搜索树Destroy(root->_right);delete root;}~RBTree() //析构函数——去调用销毁函数{Destroy(_root);}Node* Find(const K& key) //查找(类比搜索二叉树){KeyOfT oft;Node* cur = _root;while (cur){if (oft(cur->_data) > key){cur = cur->_left;}else if (oft(cur->_data) < key){cur = cur->_right;}else{return cur;}}return nullptr;}pair<iterator ,bool> insert(const T& data)//插入{KeyOfT oft;if (_root == nullptr) {_root = new Node(data);_root->_col = BLACK;return make_pair(_root, true);}Node* parent = nullptr;Node* cur = _root;while (cur){if (oft(cur->_data) < oft(data))//如果要实现mutimap 和 mutiset ,那就是(oft(cur->_data) <= oft(data)){parent = cur;cur = cur->_right;}else if(oft(cur->_data) > oft(data)){parent = cur;cur = cur->_left;}else{return make_pair(iterator(_root), false);}}Node* newnode = new Node(data);newnode->_col = RED; //标记为红if (oft(parent->_data) < oft(data)){parent->_right = newnode;newnode->_parent = parent;}else{parent->_left = newnode;newnode->_parent = parent;}cur = newnode; //前面的和搜索二叉树的插入完全一样,就标记了一下颜色。这里不再过多赘述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 //情况2+3:叔叔不存在或者叔叔存在且为黑{//情况2:单旋if (cur == parent->_left) //如果孩子是父亲的左孩子{RotateR(grandfather); //右单旋grandfather->_col = RED; //再变色parent->_col = BLACK;}else//情况3:双旋{RotateL(parent);RotateR(grandfather);cur->_col = BLACK; //最终变的还是祖父的颜色和父亲的颜色grandfather->_col = RED; //祖父的颜色变成红,父亲的颜色变成黑}break;}}else //parent == grandparent -> _right 情况是相同的{Node* uncle = grandfather->_left;if (uncle && uncle->_col == RED)//情况1{uncle->_col = BLACK;grandfather->_col = RED;cur = grandfather;parent = cur->_parent;}else//情况2+3{if (cur == parent->_right){RotateL(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else //cur为父亲的左{//双旋RotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}//插入结束break;}}}_root->_col = BLACK;return make_pair(iterator(newnode), true);}//剩余的就不解释了,和搜索二叉树、AVLTree都是一样的道理//只不过这里的旋转就不需要再调节平衡因子了。void RotateLR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;RotateL(parent->_left);RotateR(parent);}void RotateRL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;RotateR(parent->_right);RotateL(parent);}void RotateL(Node* parent) //左单旋{Node* subR = parent->_right;Node* subRL = subR->_left;Node* parentparent = parent->_parent;parent->_right = subRL;if (subRL){subRL->_parent = parent;} //两个一组subR->_left = parent;parent->_parent = subR;//两个一组//连接主体,找到组织if (parent == _root){_root = subR;subR->_parent = nullptr;}else{if (parentparent->_left == parent){parentparent->_left = subR;}else{parentparent->_right = subR;}subR->_parent = parentparent;}}void RotateR(Node* parent) //右单旋{Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;if (subLR){subLR->_parent = parent;}subL->_right = parent;Node* parentparent = parent->_parent;parent->_parent = subL;if (parent == _root){_root = subL;_root->_parent = nullptr;}else{if (parentparent->_left == parent){parentparent->_left = subL;}else{parentparent->_right = subL;}subL->_parent = parentparent;}}bool _CheckBlance(Node* root, int blackNum, int count){if (root == nullptr){if (count != blackNum){cout << "黑色节点数量不等" << endl;}return true;}if (root->_col == RED && root->_parent->_col == RED){cout << "存在连续的红色结点" << endl;return false;}if (root->_col == BLACK){count++;}return _CheckBlance(root->_left,blackNum ,count)&& _CheckBlance(root->_right, blackNum, count);}bool CheckBlance(){if (_root == nullptr){return true;}if (_root->_col == RED){cout << "根结点是红色的"<<endl;return false;}//找最左路径做黑色结点的参考值int blackNum = 0;Node* left = _root;while (left){if (left->_col == BLACK){blackNum++;}left = left->_left;}int count = 0;return _CheckBlance(_root, blackNum, count);}
private:Node* _root;
};
map.h
#pragma once
#include"BRTree.h"
namespace jxwd
{template<class K, class V>class map{public:struct MapKeyOfT{const K& operator()(const pair<const K, V>& kv){return kv.first;}};typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::iterator iterator;typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::reverse_iterator reverse_iterator;iterator begin(){return _t.begin();}iterator end(){return _t.end();}reverse_iterator rbegin(){return _t.rbegin();}reverse_iterator rend(){return _t.rend();}pair<iterator, bool> insert(const pair<const K, V>& kv){return _t.insert(kv);}pair<iterator, bool> insert(const pair<K, V>& kv){return _t.insert(kv);}V& operator[](const K& key){pair<iterator, bool> ret = insert(make_pair(key, V()));return ret.first->second;}private:RBTree<K, pair<const K, V>, MapKeyOfT> _t;};
}
set.h
#pragma once
#include "BRTree.h"
namespace jxwd
{template<class K>class set{struct SetKeyOfT{const K& operator()(const K& key){return key;}};public:typedef typename RBTree<K, K, SetKeyOfT>::iterator iterator;typedef typename RBTree<K, K, SetKeyOfT>::reverse_iterator reverse_iterator;reverse_iterator rbegin(){return _t.rbegin();}reverse_iterator rend(){return _t.rend();}bool insert(const K& k){_t.insert(k);return true;}iterator end(){return _t.end();}iterator begin(){return _t.begin();}private:RBTree<K, K ,SetKeyOfT> _t;};
}
test.cpp
#include"set.h"
#include"map.h"int main()
{jxwd::map<int, int> t;t.insert(make_pair(3, 3));t.insert(make_pair(2, 2));t.insert(make_pair(1, 1));t.insert(make_pair(0, 7));t.insert(make_pair(6, 2));t[2] = 7;jxwd::map<int, int>::reverse_iterator it = t.rbegin();while (it != t.rend()){cout << (*it).first << ":" << (*it).second << endl;cout << it->first<< ":" << it->second << endl;++it;}jxwd::set<int> tt;tt.insert(3);tt.insert(1);tt.insert(2);tt.insert(4);jxwd::set<int>::reverse_iterator sit = tt.rbegin();while (sit != tt.rend()){cout << *sit << endl;++sit;}cout << endl;;return 0;
}
最后的代码运行截图:
真的是懒得打字了
C++ 第九节——map/set(用法+底层原理+模拟实现)相关推荐
- Linq的底层原理探讨
前言 有一篇文章ABP-引入SqlSugar很多人都在催促我,出下一章因为工作忙一直没写.现在开第二节课Linq的底层原理探讨.一起探讨完,看看有没有引起SqlSugar的新思路. 这文章叫linq的 ...
- java map原理_Java HashMap底层原理分析
前两天面试的时候,被面试官问到HashMap底层原理,之前只会用,底层实现完全没看过,这两天补了补功课,写篇文章记录一下,好记性不如烂笔头啊,毕竟这年头脑子它记不住东西了哈哈哈.好了,言归正传,今天我 ...
- java Map及其实现类的底层原理
文章目录 一.Map接口及其多个实现类的对比 二.Map中存储的key-value特点 三.HashMap在JDK7中的底层原理 四.HashMap在JDK8中的底层原理 五.HashMap在JDK7 ...
- 从底层原理出发详解红黑树在Linux内核中的3种经典用法,让你知其所以然
从底层原理出发详解红黑树在Linux内核中的3种经典用法,让你知其所以然丨进程管理|内存管理|sk_buff|B树|B+树 视频讲解如下,点击观看: 从底层原理出发详解红黑树在Linux内核中的3种经 ...
- Go map的底层原理(存储、扩容)
Go map的底层原理 map的实现原理 map的底层结构 map的扩容机制 map的实现原理 数组+链表.拉链法 map的底层结构 hmap 哈希表 hmap是Go map的底层实现,每个hmap内 ...
- Golang底层原理学习笔记(一)
LCY~~Golang底层原理学习笔记 1 源码调试 go源代码地址:GitHub - golang/go: The Go programming language 1.1 源码编译 现在的go语言大 ...
- 深度学习Spring5底层原理(黑马学习随笔)
学习随笔简介 跟随着黑马满老师的<黑马程序员Spring视频教程,全面深度讲解spring5底层原理>学习,视频教程地址:黑马程序员Spring视频教程,全面深度讲解spring5底层原理 ...
- 深究Java中的RMI底层原理
原博客地址:http://blog.csdn.net/sinat_34596644/article/details/52599688 前言:随着一个系统被用户认可,业务量.请求量不断上升,那么单机系统 ...
- HashMap底层原理(当你put,get时内部会发生什么呢?)
HashMap底层原理解析(一) 接触过HashMap的小伙伴都会经常使用put和get这些方法,那接下来就对HashMap的内部存储进行详解.(以初学者的角度进行分析)-(小白篇) 当程序试图将多个 ...
最新文章
- 如何提高模型性能?这四大方法值得尝试 | CSDN 博文精选
- 深度解析 Lucene 轻量级全文索引实现原理
- C/C++ 如何劫持别人家的命令||函数||程序(只能对于window而言)
- bzoj 2653 洛谷 P2839 [国家集训队] middle
- 如何通过使用 64 位版本 Windows 查看系统注册表 WOW6432Node
- hash值为负_java – HashCode给出负值
- ionic4 组件的使用(二)
- 7-3 sdut-求两个整数之和(I)
- Lua中强大的元方法__index详解
- Python 读取数据
- select设置高度的兼容问题
- gtk_init参数传递过程(草稿)
- PostgreSQL在何处处理 sql查询之二十九
- 布谷鸟算法浅谈与简单应用
- 如何查询linux服务器的网卡,linux怎么查看网卡硬件信息
- navicat12.1.18破解 亲测
- 制作svg格式矢量图
- display tearing小结
- 二分法的算法及应用场景(只更新了一种)
- 自己写的一个LOL云顶智之奕小工具
热门文章
- Matlab中switch, case, otherwise语句
- 创新,有时是不经意间开放的花朵——访2013 CCF青年科学家奖获得者朱军
- 【STM32F407】第8章 ThreadX NetXDUO之TCP服务器
- 手动实现C++容器vector的clear操作
- redis使用队列进行抢购活动(秒杀)
- 10组团队项目-Alpha冲刺-1/6
- Spread for WPF-Silverlight 新功能使用指南
- 为什么Python这么火
- px,in,mm,pt,dp,dip,sp 之间的换算公式以及区别
- 11 岁编程,21 岁开发 Linux 系统,这就是顶尖程序员的样子!