关联容器小结

关联容器和顺序容器的不同

关联容器和顺序容器的根本不同之处在于,关联容器中的元素是按关键字来保存和访问的(比如map和set),而顺序容器中的元素是按照在容器中的位置来顺序保存和访问的(比如vector和string)。

顺序容器有

vector
deque
list
forward_list
array
string

关联容器有

按照关键字有序保存
map
set
multimap
multiset
无序保存
unordered_map
unordered_set
unordered_multimap
unordered_multiset

关联容器不支持和位置相关的操作,因为是按关键字顺序存储的,关联容器的迭代器都是双向的。

对于有序关联容器中的关键字类型要求

对与有序关联容器而言,关键字类型必须定义元素比较的方法(这一点尤其重要),默认时,使用关键字类型的<运算符来比较两个关键字。即虽然可以向容器中保存自己定义的类或者结构体,但是如果这些类和结构体中没有定义<运算符的话,则该行为就是不合法的。

但是可以向算法提供一个自己定义的比较操作(通常是一个函数)或者对<进行重载来完成比较操作,唯一需要注意的就是,所提供的操作必须在关键字类型上严格弱序(可以看作小于等于)

严格弱序具有反自反性(不能同时小于等于对方),也具有传递性(k1<=k2,k2<=k3则有k1<=k3)

严格弱序上的等价关系是任何一个都不小于等于另一个,这个等价关系也具有传递性。

在使用自己定义的操作时,必须要提供该操作类型(可以使用decltype来获得函数指针类型),比如定义一个multiset

multiset<type,decltype(CompareFunction)*>name(CompareFunction);

关联容器额外的类型别名

类型别名 含义
key_type 该容器的关键字类型
mapped_type 关键字关联的类型,只适用于map
value_type 对于set而言和key_type相同;对于map,为pair<const key_type,mapped_type>

关联容器和算法

实际使用算法时,关联容器只能是一个源序列或者目的序列。
比如:

	multiset<string>c{ "1","2","1" };
	vector<string>v;
	copy(c.begin(), c.end(), back_inserter(v));//v为目的序列,c为源序列
	multiset<string>c;
	vector<string>v{ "1","2","1" };
    copy(v.begin(), v.end(), inserter(c,c.end())); //c为目的序列,v为源序列

关联容器的操作及其返回值

插入元素

insert向容器中插入一个元素,不存在则什么也不做。emplace也是插入元素,但是只有当关键字不在容器时才插入。

insert(value_type);  //value_type对于set就是关键字,对于map而言就是一个pair
insert(args);		//arges时一个初始化列表
insert(begin,end);	//insert也可以接受一对迭代器begin,end;
insert(p,args);		//从容器的p位置开始搜索新元素args的插入位置

emplace(value_type);//当关键字不在容器时才插入
emplace(p,args);//从容器的p位置开始搜索新元素args的插入位置

对于插入一个元素的insert(value_type)emplace(value_type)会返回一个pair< value_type::iterator,bool >,

pair的first时一个指向给定插入元素的迭代器,pair的second是一个表示是否插入成功的布尔值;

对于insert(args)insert(begin,end),返回一个void;

对于insert(p,args)emplace(p,args),返回一个迭代器,指向给定元素。

删除元素

关联容器有三个版本的erase操作,分别接受一个关键字,一个迭代器和一对迭代器。

访问和查找元素

1.下标(有序无序可用)

对于map而言,可以使用下标或者一个at函数来对容器中的元素进行访问,比如m[key]或者m.at(key)这样的操作。
对于下标而言,如果该关键字在容器中不存在,那么就会被当作一个新元素加入到容器中去。at函数没有这样的作用,它只会对参数进行检查,如果不存在就抛出一个out_of_range的异常。

而对于set而言,是不能用下标进行访问的。

2.find或者count(有序无序可用)

对于map和set都有自带的find和count版本,find会返回一个迭代器,指向第一个关键字为参数的元素,但如果该关键字不存在,就返回尾后迭代器。
count会返回容器中符合要求的关键字的数量,不存在则为0;

3.lower_bound和upper_bound(有序版本)

lower_bound(k)会返回一个迭代器,指向第一个关键字不小于k的元素(即<k的上界,>=k的下界)。如果不存在则返回一个不影响排序的关键字的插入位置(即如果要将这个不存在的关键字插入到容器中时要放入的位置)
upper_bound(k)会返回一个迭代器,指向第一个关键字大于k的元素,即>k的下界,<=k的下界。如果不存在也会返回一个不影响排序的关键字的插入位置
比如:

multimap<string,string>words;
string k="Test";
auto b=words.lower_bound(k),e=words.upper_bound(k);
for(;b!=e;++b)cout<<b->second<<endl;

如果调用lower_bound(k)的话,如果容器中存在关键字k的话,b就会匹配到第一个k所在的元素位置,e则会指向最后一个k之后的位置,否则b和e都会指向一个相同位置,即不影响排序的关键字的插入位置
所以对于同样的参数调用,如果lower_bound和upper_bound指向的迭代器不相同,那么两个就是一个包含所有关键字k的迭代器范围[b,e)

4.equal_range函数(有序版本)

对于上面的版本,用equal_range函数可以改写为:

multimap<string,string>words;
string k="Test";
auto pos=words.equal_range(k);
for(;pos.first!=pos.second;++pos.first)cout<<pos.first->second<<endl;

//pos.first和words.lower_bound(k)相等
//pos.second和words.upper_bound(k)相等

equal_range接受一个关键字,返回一个pair,包含两个迭代器。当关键字存在时,两个迭代器就是刚才用lower_bound和upper_bound得到的迭代器。当不存在时,也返回不影响排序的关键字的插入位置

无序容器

概念和性能

无序容器使用一个哈希函数和关键字类型的==运算符来组织元素,而不是<运算符。
无序容器在存储上的组织形式为一组桶,利用哈希函数将具有一个相同哈希值的元素保存在相同的桶中(即使是重复版本的无序容器也是一样),所以无序容器的性能取决于哈希函数的性能和桶的数量和大小。

无序容器对关键字类型的要求

对于内置的类型比如int,以及标准库类型比如string,标准库都提供了默认的hash模板,所以可以直接定义,比如:

unordered_map<string, int>words;

但是对于自定义的类型,就需要提供hash模板或者提供函数来替代==运算符和哈希函数(类似重载)。
一种特别的情况时,如果关键字是一个已经自定义了==运算符的类,则只需要提供哈希函数即可。

原文地址:https://www.cnblogs.com/FlyerBird/p/11808668.html