c++ map、multimap基本用法是什么,下面这个代码看不懂
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了c++ map、multimap基本用法是什么,下面这个代码看不懂相关的知识,希望对你有一定的参考价值。
出自钱能《c++程序设计与教程》
//这个程序是要统计一篇文章的段落数,并按段落单词数进行排序输出
#include <iostream>
#include <fstream>
#include <sstream>
#include <vector>
#include <map>
using namespace std;
typedef multimap<int,int> Mmap;
int main()
ifstream in ("abc.txt");
vector<string> abc;
Mmap nums;
int n=0;
for(string s;getline(in,s);)
istringstream sin(s);
int num=0;
for(string t;sin >> t;num++);
if(num)
nums.insert(Mmap::value_type(num,n++)); //这里尤其看不懂
abc.push_back(s);
for(Mmap::iterator it=nums.begin();it != nums.end();++it)
cout <<abc[it->second]<<endl;
return 0;
map 和 multimap 的头文件都是 <map>?
这二者有何区别?基本用法是什么?给我举几个简单的例子吧!
map<char,int> m;
m.insert(make_pair('0',10));//插入数据,make_pair这个东西就要看自己体会了,实在不知道怎么解释
m.insert(make_pair('1',11));
m.insert(make_pair('2',12));
下面从map中读取数据,直接把key当做下标就可以得到val,只有哈希表能做这样的事情,其他如vector,list等都不行,这是哈希表最明显的特征
cout<<m['1']<<endl;输出11
cout<<m['4']<<endl;输出0
map还有两个特点
默认情况下插入数据的时候会自动按key排序
key不能重复,重复插入无效
map和multimap唯一的区别就在于2,multimap可以插入key相同的数据,用一个例子解释
#include "stdafx.h"
#include <map>
using namespace std;
void showmap(map<int,char> mp)
printf("\n");
for(map<int,char>::iterator iter=mp.begin();iter!=mp.end();++iter)
printf("%d-%c\n",iter->first,iter->second);
printf("\n");
void showmultimap(multimap<int,char> mmp)
printf("\n");
for(multimap<int,char>::iterator iter=mmp.begin();iter!=mmp.end();++iter)
printf("%d-%c\n",iter->first,iter->second);
printf("\n");
int _tmain(int argc, _TCHAR* argv[])
map<int,char> mp;
mp.insert(make_pair<int,char>(0,48));
mp.insert(make_pair<int,char>(1,49));
mp.insert(make_pair<int,char>(2,50));
mp.insert(make_pair<int,char>(3,51));
mp.insert(make_pair<int,char>(1,52));
showmap(mp);
multimap<int,char> mmp;
mmp.insert(make_pair<int,char>(0,48));
mmp.insert(make_pair<int,char>(1,49));
mmp.insert(make_pair<int,char>(2,50));
mmp.insert(make_pair<int,char>(3,51));
mmp.insert(make_pair<int,char>(1,52));
showmultimap(mmp);
getchar();
return 0;
运行结果:
0-0
1-1
2-2
3-3
0-0
1-1
1-4
2-2
3-3
对于map,key是唯一的,如果已经存在key了,再次插入想同key无效
multimap的key可以有多个追问
(1)看到一半 这个看不懂:
map::iterator
(2)还有上面我的代码里有那个Mmap::value_type 这个也看不懂
能帮我解释下吗 我会再加分的!
好吧第一个问题我自行百度解决了 第二个百度找不到啊
iterator就是游标,stl里面的容器都有这个东西,举例说明:
for( map::iterator iter=m.begin(); iter!=m.end();++iter )
//这是遍历map,其他容器也是一样的语句,从begin到end这个好理解吧,++就是指向下一个数据
游标本身是一个指针,表示遍历过程中当前指向的那个数据,类型跟容器的类型一样
coutsecond();
nums.insert(Mmap::value_type(num,n++));
Mmap::value_type跟make_pair一样,都是把括号里面的东西转换成容易能接受的数据类型
typedef multimap Mmap;可以知道这个容器是,可以往里面插入(1,2) (2,3)这样的数据对,但是nums.insert(1,2); 这样是不行的,insert只接受一个参数,所以用make_pair把两个参数变成一个参数,具体我也不知道。
只要记住容器是什么类型就插什么数据对
map m1;
m1.insert(make_pair(1,'A'));
或者
m1.insert(map::value_type(1,'A'));
两种写法一样的,喜欢怎么写怎么写
包含了这个头文件,就可以使用map这种数据结构;
#include<iostream>
#include<vector>
#include<list>
#include<string>
#include<map>
#include<set>
#include<algorithm>
using namespace std;
int main()
map<char,int> mymap;
map<char,int>::iterator it;
mymap['b'] = 100;
mymap['a'] = 200;
mymap['c'] = 300;
for(it=mymap.begin();it!=mymap.end();++it)
cout<<(*it).first<<"=>"<<(*it).second<<endl;
return 0;
追问
map::iterator it;
cout"<<(*it).second<<endl;
这两行看不懂。。还有 multimap呢?
C++进阶第二十篇——map和set(map和set的用法+multimap+multiset+map和set代码实现)
⭐️今天我要给大家介绍两个新的容器,它们都是关联式容器——map和set,我会先介绍它们的使用方法,然后带大家用上一篇博客中的红黑树封装出map和set。
⭐️博客代码已上传至gitee:https://gitee.com/byte-binxin/cpp-class-code
目录
🌏关联式容器
关联式容器也是用来存储数据的,与序列式容器(如vector、list等)不同的是,其里面存储的是<key, value>结构的键值对,在数据检索时比序列式容器效率更高。今天要介绍的的四种容器是树形关联式容器:map、set、multimap和multiset。它们的底层都是用红黑树来实现的,容器中的元素是一个有序的序列。
🌏键值对
键值对: 用来表示具有一一对应关系的一种结构,该结构中一般只包含两个成员变量key和value,key代表键值,value表示与key对应的信息。
STL中键值对定义如下:
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)
;
一般的两种方式创建键值对对象:
第一种: pair<T1, T2>(x, y) 使用构造函数的方式构造一个匿名对象
第二种: make_pair(x, y) 是一个函数模板,其中返回的是一个pair的匿名对象
实例演示:
void test()
// pair<T1, T2>(T1(), T2()) 通过构造函数构造一个匿名对象
// make_pair(T1() , T2()) 是一个模板函数,返回的是pair的匿名对象,用起来更方便
pair<int, int>(1, 1);
make_pair(1, 1);
🌏set
🌲set的介绍
总结几点:
- set是按照一定次序存储元素的容器
- 在set中,元素的value也标识它(value就是key,类型为T),并且每个value必须是唯一的。set中的元素不能在容器中修改(元素总是const),但是可以从容器中插入或删除它们。
- 在内部,set中的元素总是按照其内部比较对象(类型比较)所指示的特定严格弱排序准则进行排序。
- set容器通过key访问单个元素的速度通常比unordered_set容器慢,但它们允许根据顺序对子集进行直接迭代。
- set在底层是用红黑树实现的。
🌲set的使用
🍯set的几个构造函数
- 构造函数: set (const Compare& comp = Compare(), const Allocator& =Allocator() ); 构造空的set容器
- 拷贝构造: set (const set& x);
🍯set的迭代器
和之前几个容器一样,有正向迭代器和反向迭代器,还有const迭代器。这里用法也和之前的类似,不过多介绍。下面会给大家演示。
🍯set的大小和容量
- empty: 判断set是否为空
- size: 返回set中元素的个数
🍯set的插入和删除
- insert: pair<iterator,bool> insert (const value_type& val); 插入元素,返回值是键值对,其中如果第二个参数为true,那么第一个参数是插入元素的迭代器的位置,为false的话,第一个参数就是已经存在元素的迭代器的位置
- erase: void erase (iterator position); 删除position位置的元素
🍯非成员函数
这里只介绍find一个。
find 查找某个元素。这里find的时间复杂度为O(logN),比算法中的find(时间复杂是O(N))更高效,所以set容器一般室友自己的find进行查找。
实例演示:
实例1 插入、删除、查找和迭代器遍历
void test_set1()
set<int> s;
s.insert(5);
s.insert(1);
s.insert(6);
s.insert(3);
s.insert(6);
s.insert(s.begin(), 10);
set<int>::iterator pos = s.find(15);// 底层是搜索二叉树,时间复杂度是O(logN)
// set<int>::iterator pos = find(s.begin(), s.end(), 3);// 遍历查找,时间复杂度是O(N)
if (pos != s.end())
// cout << *pos << endl;
s.erase(pos);// 没有会报错
//s.erase(1); // 没找到不会报错
set<int>::iterator it = s.begin();
while (it != s.end())
cout << *it << " ";
++it;
cout << endl;
for (auto e : s)
cout << e << " ";
cout << endl;
代码运行结果如下:
实例2 下面是对算法中的find和set中的find进行效率比较的小测试
void test_set2()
srand((size_t)time(nullptr));
set<int> s;
for (size_t i = 0; i < 10000; ++i)
s.insert(rand());
cout << "个数:" << s.size() << endl;
int begin1 = clock();
for (auto e : s)
s.find(e);
int end1 = clock();
int begin2 = clock();
for (auto e : s)
find(s.begin(), s.end(), e);
int end2 = clock();
cout << "用时1:" << end1 - begin1 << "ms" << endl;
cout << "用时2:" << end2 - begin2 << "ms" << endl;
代码运行结果如下:
🌏map
🌲map的介绍
总结以下几点:
- map是关联容器,它按照特定的次序(按照key来比较)存储由键值key和值value组合而成的元素。
- 在内部,map中的元素总是按照键值key进行比较排序的。
- map中通过键值访问单个元素的速度通常比unordered_map容器慢,但map允许根据顺序对元素进行直接迭代(即对map中的元素进行迭代时,可以得到一个有序的序列)。
- map支持下标访问符,支持operator[],即在[]中放入key,就可以找到与key对应的value。
- map通常被实现为二叉搜索树(更准确的说:平衡二叉搜索树(红黑树))
🌲map的用法
🍯map的几个构造函数
- 构造函数: map() 构造一个空的map容器
- 拷贝构造: map(const map& m);
🍯map的迭代器
和set是类似的,不过多介绍,后面有实例演示。
🍯大小和容量
- empty 判断绒是否为空
- size 返回容器中元素个数
🍯插入和删除
- insert: pair<iterator,bool> insert (const value_type& x ); 返回的是一个键值对,和set的原理一样
- erase: void erase (iterator position); 在pos删除元素
🍯operator[](重点)
operator[]函数的定义如下:
mapped_type& operator[] (const key_type& k)
return (*((this->insert(make_pair(k,mapped_type()))).first)).second;
其中,mapped_type是KV模型中V的类型,也就是返回value值得引用。我们可以对这个value进行修改。
分析:((this->insert(make_pair(k,mapped_type()))).first这是一个迭代器,迭代器指向键值对中的第二个元素就是value。所以operato[]的底层是用到了插入,同时可以对value进行修改和访问。
总结: operator[]的三个用处:插入、修改和访问。
实例演示:
实例1 用map统计水果个数,以下用了3种方式,同时还对operator的几种作用进行了说明
void test_map2()
map<string, int> countMap;
string fruitArray[] = "西瓜","桃子","香蕉","桃子","苹果","西瓜", "香蕉","苹果", "香蕉","西瓜","桃子", "西瓜", "西瓜","桃子",
"桃子", "桃子", "西瓜","桃子","香蕉","桃子","苹果","西瓜" ;
// 方法一
//for (auto& e : fruitArray)
//
// map<string, int>::iterator ret = countMap.find(e);
// if (ret != countMap.end())// 找到了,说明容器里有,第二个参数加1即可
//
// ++ret->second;
//
// else
//
// // 没有就插入,第二个参数记为1
// countMap.insert(make_pair(e, 1));
//
//
// 方法二
//for (auto& e : fruitArray)
//
//
// // countMap无此元素,pair的第一个参数返回新的迭代器,第二个参数返回true
// // countMap有此元素,pair的第一个参数返回旧的迭代器,第二个参数返回false
// pair<map<string, int>::iterator, bool> ret = countMap.insert(make_pair(e, 1));
// // 插入失败,只需要++value即可
// if (ret.second == false)
//
// ++ret.first->second;
//
//
// 方法三
for (auto& e : fruitArray)
// mapped_type& operator[] (const key_type& k) ;
// mapped_type& operator[] (const key_type& k) return (*((this->insert(make_pair(k,mapped_type()))).first)).second;
// ((this->insert(make_pair(k,mapped_type()))).first 迭代器
// (*( (this->insert(make_pair(k,mapped_type()))).first )).second 返回value的值的引用 operator[]的原型
countMap[e]++;// 有插入、查找和修改的功能 返回value的值的引用
countMap["梨子"];// 插入
countMap["梨子"] = 5;// 修改
cout << countMap["梨子"] << endl;// 查找 一般不会用 operator[] 来进行查找,因为没找到会进行插入
countMap["哈密瓜"] = 3;// 插入+修改
for (auto& e : countMap)
cout << e.first << ":" << e.second << endl;
代码运行结果如下:
实例2 测试map的插入、删除和迭代器的使用
void test_map1()
map<int, int> m;
// 键值对
// pair<T1, T2>(T1(), T2()) 通过构造函数构造一个匿名对象
// make_pair(T1() , T2()) 是一个模板函数,返回的是pair的匿名对象,用起来更方便
//m.insert(pair<int, int>(1, 1));
m.insert(make_pair(1, 1));
m.insert(pair<int, int>(2, 2));
m.insert(pair<int, int>(3, 3));
m.insert(pair<int, int>(4, 4));
map<int, int>::iterator it = m.begin();
while (it != m.end())
// *it 返回 值得引用
cout << (*it).first << ":" << (*it).second << endl;
// it-> 返回 值的地址 -> 解引用访问两个元素
// cout << it->first << ":" << it->second << endl;
++it;
// e是自定义类型,传引用防止有拷贝构造发生
for (auto& e : m)
cout << e.first << ":" << e.second << endl;
代码运行结果如下:
🌏multiset
🌲介绍
总结几点:
- multiset是按照特定顺序存储元素的容器,其中元素是可以重复的。
- 底层是红黑树,和set的特点基本类似,只是multiset可以存放多个相同的值。
🌲用法
与set的接口基本相似,直接上演示。
实例演示:
void test_multiset()
multiset<int> ms;
// multiset 和 set 的接口基本一致,multiset可以插入重复的
ms.insert(1);
ms.insert(5);
ms.insert(3);
ms.insert(2);
ms.insert(3);
multiset<int>::iterator pos = ms.find(3);// 返回的是第一个3
cout << *pos << endl;
++pos;
cout << *pos << endl;
++pos;
cout << *pos << endl;
++pos;
for (auto e : ms)
cout << e << " ";
cout << endl;
代码运行结果如下:
🌏multimap
🌲介绍
总结几点:
- multimaps是关联式容器,它按照特定的顺序,存储由key和value映射成的键值对<key, value>,其中多个键值对之间的key是可以重复的。
- 底层也是红黑树,和map的性质基本类似
🌲用法
实例演示:
void test_multimap()
// multimap 和 map 的区别:可以有不同的key
// 不支持operator[] 因为有多个key时,不知道返回哪个key对应的value的引用
multimap<int, int> mm;
mm.insert(make_pair(1, 1));
mm.insert(make_pair(1, 2));
mm.insert(make_pair(1, 3));
mm.insert(make_pair(2, 1));
mm.insert(make_pair(2, 2));
for (auto& e : mm)
cout << e.first << ":" << e.second << endl;
代码运行结果如下:
🌏用一颗红黑树封装出map和set
🌲对红黑树进行改造
这里是我上一篇关于红黑树的博客——红黑树
这里红黑树完整代码——红黑树完整代码
大概框架:
template<class K, class V>
class RBTree
typedef RBTreeNode<K, V> Node;
private:
Node* _root = nullptr;
;
这里的红黑树是一个KV模型,我们要用这个红黑树同时封装出map和set两个容器,直接使用这棵红黑树显然是不行的,set属于是K模型的容器,我们要做怎样的改造才能够同时封装出这两个容器呢?
这里我们参考STL源码的处理方式,下面是源码的部分截图:
可以看出这里,红黑树的第一个类模板参数和之前是一样的,但是第二个参数value和之前是不一样的,这里的直接把value存放在节点里面,通过map和set构造红黑树可以看出value存的是pair<K, V>或K,对于map而言,value存的是pair<K, V>;对于set而言,value存的是K。所以这里的红黑树暂时可以这样改造:
template<class K, class T>
class RBTree
typedef RBTreeNode<T> Node;// 根据T的类型判断是map还是set 可能是pair<K, V>或K
public:
private:
Node* _root = nullptr;
;
同时,我们还会发现,上面的红黑树的类模板中有第三个参数是什么呢?
为了获取value中的key值,我们可以让map和set各自传一个仿函数过来,以便获得各自的key值。
两个仿函数如下:
template<class K, class V>
class map
struct MAPOFV
const K& operator()(const pair<K, V>& kv)
return kv.first;
;
;
template<class K>
class set
struct SETOFV
const K& operator()(const K& key)
return key;
;
;
第四个类模板参数是一个空间配置器,这里也不实现了,我们实现主体内容即可。后面会介绍空间配置器相关内容。
迭代器的实现
其中operato++就是通过非递归中序遍历的方式走一遍红黑树,走到空就结束
template<class T, class Ptr, class Ref>
struct __rbtree_iterator
typedef __rbtree_iterator<T, Ptr, Ref> Self;
typedef RBTreeNode<T> Node;
Node* _node;
__rbtree_iterator(Node* node)
:_node(node)
// 返回值(data)的地址
Ptr operator->()
return &_node->_data;
// 返回值(data)的引用
Ref operator*()
return _node->_data;
Self& operator++()
// 1.先判断右子树是否为空,不为空就去右子树找最左节点
// 2.右子树为空,去找孩子是其左孩子的祖先
Node* cur = _node;
if (cur->_right)
cur = cur->_right;
while (cur->_left)
cur = cur->_left;
else
Node* parent = cur->_parent;
while (parent && parent->_right == cur)
cur = parent;
parent = parent->_parent;
cur = parent;
_node = cur;
return *this;
Self& operator--()
// 1.先判断左子树是否为空,不为空就去左子树找最右节点
// 2.右子树为空,去找孩子是其右孩子的祖先
Node* cur = _node;
if (cur->_left)
cur = cur->_left;
while (cur->_right)
cur = curc++中multimap的问题
c++11中,为啥可以用const_iterator删除map/multimap中的元素
C++ STL|深入理解关联容器multimap和map及其查找操作