关联式容器的定义
所谓关联式容器,概念上类似关联式数据库(实际上则简单许多):每项数据(元素)包含一个键值(key)和一个实值(value)。当元素被插入到关联式容器中时,容器内部数据结构(可能是RB-tree,也可能是hash-table)便依照其键值大小,以某种特定规则将这个元素放置于适当位置。关联式容器没有所谓头尾(只有最大元素和最小元素),所以不会有push_back(),push_front(),pop_back(),pop_front(),begin(),end()这样的操作。
一般而言,关联式容器的内部结构是一个balanced binary tree(平衡二叉树),以便获得良好的搜索效率。balancedbinary tree有很多种类型,包括AVL-tree、RB-tree、AA-tree,其中广泛运用于STL的是RB-tree(红黑树)。
标准的STL关联式容器分为set(集合)和map(映射类)两大类,以及这两大类的衍生体multiset(多键集合)和multimap(多键映射表)。这些容器的底层机制均以RB-tree完成(红黑树)。RB-tree也是一个独立容器,但并不开放给外界使用。
此外,SGI STL还提供了一个不在标准规格之列的关联式容器:hash table(散列表,哈希表),以及以此hash table为底层机制而完成的hash_set(散列集合)、hash_map(散列映射表)、hash_multiset(散列多键集合)、hash_multimap(散列多键映射表)。
map容器
STL的一个关联容器,它提供一对一(其中第一个可以称为关键字,每个关键字只能在map中出现一次,第二个可能称为该关键字的值)的数据 处理能力,由于这个特性,它完成有可能在我们处理一对一数据的时候,在编程上提供快速通道。map内部自建一颗红黑树(一 种非严格意义上的平衡二叉树),这颗树具有对数据自动排序的功能,所以在map内部所有的数据都是有序的,后边我们会见识到有序的好处。它的特点是增加和删除节点对迭代器的影响很小,除了那个操作节点,对其他的节点都没有什么影响。对于迭代器来说,可以修改实值,而不能修改key。key 和 value可以是任意你需要的类型,根据key值快速查找记录,查找的复杂度基本是Log(N)
基本操作:
1.构造函数:
map<int, string> mapStudent;
2.数据的插入:
mapStudent.insert(pair<int,string>(1, "student_one")); //pair数据
mapStudent.insert(map<int,string>::value_type (1, "student_one"));//用insert函数插入value_type数据
mapStudent[1] ="student_one"; //数组插入
注意:第一种和第二种在效果上是完成一样的,涉及到集合的唯一性这个概念,即当map中有这个关键字时,insert操作是插入数据不了的,怎么知道insert语句是否插入成功:
pair<map<int,string>::iterator, bool> Insert_Pair;
Insert_Pair =mapStudent.insert(map<int, string>::value_type (1,"student_one"));
if(Insert_Pair.second== true)
cout<<"InsertSuccessfully"<<endl;
else
cout<<"InsertFailure"<<endl;
但是用数组方式就不同了,它可以覆盖以前该关键字对应的值
3.map的大小:mapStudent.size();
4.数据的遍历:
map<int,string>::iterator iter;
for(iter =mapStudent.begin(); iter != mapStudent.end(); iter++)
cout<<iter->first<<''<<iter->second<<endl;//前向迭代器
map<int,string>::reverse_iterator iter;
for(iter =mapStudent.rbegin(); iter != mapStudent.rend(); iter++)
cout<<iter->first<<" "<<iter->second<<endl; //反向迭代器
for(int nindex = 1;nindex <= nSize; nindex++)
cout<<mapStudent[nindex]<<endl; //数组
5,查找并获取map中的元素(包括判定这个关键字是否在map中出现)
count函数来判定关键字是否出现,其缺点是无法定位数据出现位置,由于map的特性,一对一的映射关系,就决定了count函数的返回值只有两个,要么是0,要么是1,出现的情况,当然是返回1了
mapStudent.find(1);
用find函数来定位数据出现位置,它返回的一个迭代器,当数据出现时,它返回数据所在位置的迭代器iterator,如果map中没有要查找的数据,它返回的迭代器等于end函数返回的迭代器。传入的参数是要查找的key,
iter =mapStudent.find(1);
if(iter != mapStudent.end())
cout<<"Find, the value is"<<iter->second<<endl;
else
cout<<"Do notFind"<<endl;
lower_bound:返回要查找关键字的下界(是一个迭代器)
upper_bound:返回要查找关键字的上界(是一个迭代器)
Equal_range:返回一个pair,第一个变量是Lower_bound返回的迭代器,第二个迭代器是Upper_bound返回的迭代器,如果这两个迭代器相等的话,则说明map中不出现这个关键字,
mappair =mapStudent.equal_range(2);
if(mappair.first == mappair.second)
cout<<"Do notFind"<<endl;
else
cout<<"Find"<<endl;
6,从map中删除元素
iterator erase(iterator it);//通过一个条目对象删除
iterator erase(iterator first,iterator last)//删除一个范围
size_typeerase(const Key&key);//通过关键字删除
clear()就相当于map.erase(map.begin(),map.end());
7. swap:不是一个容器中的元素交换,而是两个容器所有元素的交换
8. 排序 :
map中的元素是自动按Key升序排序,所以不能对map用sort函数;
STL中默认是采用小于号来排序的,在一些特殊情况,比如关键字是一个结构体,涉及到排序就会出现问题,因为它没有小于号操作,insert等函数在编译的时候过不去,下面给出两个方法解决这个问题。
小于号重载
typedef structtagStudentinfo
{
int niD;
string strName;
bool operator < (tagStudentinfoconst& _A) const
{ //这个函数指定排序策略,按niD排序,如果niD相等的话,按strName排序
if(niD < _A.niD) returntrue;
if(niD == _A.niD)
return strName.compare(_A.strName)< 0;
return false;
}
}Studentinfo,*PStudentinfo; //学生信息
仿函数的应用,这个时候结构体中没有直接的小于号重载
class sort
{
public:
bool operator() (Studentinfo const &_A,Studentinfo const &_B) const
{
if(_A.niD < _B.niD)
return true;
if(_A.niD == _B.niD)
return _A.strName.compare(_B.strName)< 0;
return false;
}
};
map<Studentinfo, int, sort>mapStudent;
map中由于它内部有序,由红黑树保证,因此很多函数执行的时间复杂度都是logN的,如果用map函数可以实现的功能,而STL Algorithm也可以完成该功能,建议用map自带函数,效率高一些。由于map的每个数据对应红黑树上的一个节点,这个节点在不保存数据时,是占用16个字节的,一个父节点指针,左右孩子指针,还有一个枚举值(标示红黑的,相当于平衡二叉树中的平衡因子),很费内存。
map关联容器
map :关联数组;保存关键字-值对;数据的存放是有序的
multimap:关键字可以重复出现的map
unordered_map:用哈希函数组织的map;容器中的数据存放是无序的
unordered_multimap:哈希组织的map;关键字可以重复出现
类型map和multimap定义在头文件map中,unordered_map定义在头文件unordered_map中。
所有有序关联容器数据是按关键字的字典顺序进行存储存放的。
multimap容器
和map容器类似,区别在于map容器中的关键字必须是唯一的,对于一个给定的关键字,只能有一个元素的关键字等于它。而multimap对此没有限制,允许多个元素具有相同的关键字,map与multimap很多操作都一样.
multimap与map使用不同的地方:
1.添加元素: 由于multimap中容器的关键字不必唯一,可以向multimap中插入多个关键字相同的元素。
authors.insert({"Barth","sot-weedfactor"});
authors.insert({"Barth","Lostin the Funhouse"});
2.下标访问: map是支持下标访问的,但是由于multimap中的一个关键字可能对应多个值,所以multimap并不支持下标操作。
3.查找元素: 如果multimap中有多个元素具有相同的关键字,则这些关键字在容器中会相邻存储,可以通过这一特性,将关键字对应的多个值全部找出来。
intnumbers=authors.count(search_item);
autoit=authors.find(search_item);
while(numbers)
{
cout<<iter->second<<endl; ++it;numbers--;
}
set容器
set容器中的每个元素只包含一个关键字,其实就是一个集合,用来存储同类型的元素。
1.set容器的定义与初始化:
由于set是有序的关联容器,容器内部会按字典顺序自动为元素进行排序,如果我们需要将数据按顺序存储起来,使用set容器是一个非常不错的选择。
set<string>a1={"fengxin","666"};
set<string>a2=a1;
2.添加与删除元素: 同map容器类似。
set<string>a; //empty set
a.insert("fengxin"); // 插入一个元素
a.emplace("123"); //插入
a.erase("123"); //删除关键字为123的元素
3.遍历元素: 同map容器类似。用迭代器进行遍历set容器。需要注意的是,同不能改变一个map的关键字一样,一个set中的关键字也是const的。所以,我们只能用一个set迭代器读取元素的值,但不能修改。
set<int>iset={0,1,2,3,4,5,6,7,8,9};
auto it=iset.begin();
if(it!=iset.end())
{
*it=10; //错误:set中关键字是只读的
cout<<*it<<endl;
}
4.查找元素: 由于set中存储的只有关键字,所以set容器并不支持下标操作。 同样使用find函数进行关键字的查找,函数返回指向关键字的迭代器。
set<int>iset={0,1,2,3,4,5,6,7,8,9};
autoit=iset.find(6);
cout<<*it<endl;
set关联容器
set : 关键字即值,即只保存关键字的容器
multiset : 关键字可重复出现的set
unordered_set: 无序的set
unordered_multiset:关键字可以重复出现的无序的set
set和multiset定义在头文件set中,unordered_set定义在头文件unordered_set中。
multiset容器: set和multiset容器区别同map与multimap容器
容器比较
| vector | deque | list | set | multiset | map | multimap |
名称 | 向量容器 | 双向队列容器 | 列表容器 | 集合 | 多重集合 | 映射 | 多重映射 |
内部数 据结构 | 连续存储的数组形式(一端开口的组) | 连续或分段连续存储数组(两端 开口的数组) | 双向环状链表 | 红黑树(平衡检索二叉树) | 红黑树 | 红黑树 | 红黑树 |
头文件 | #include <vector> | #include <deque> | #include <list> | #include <set> | #include <set> | #include <map> | #include <map> |
操作元素的方式 | 下标运算符:[0](可以用迭代器,但插入删除操作时会失效) | 下标运算符或迭代器 | 只能用迭代器(不断用变量值来递推新值,相当于指针),不支持使用下标运算符 | 迭代器 | 迭代器 | 迭代器 | 迭代器 |
插入删除操作迭代器是否失效 | 插入和删除元素都会使迭代器失效 | 插入任何元素都会使迭代器失效。删除头和尾元素,指向被删除节点迭代器失效,而删除中间元素会使所有迭代器失效 | 插入,迭代器不会失效。删除,指向被删除节点迭代器失效 | 插入,迭代器不会失效。删除,指向被删除节点迭代器失效 | 插入,迭代器不会失效。删除,指向被删除节点迭代器失效 | 插入,迭代器不会失效。删除,指向被删除节点迭代器失效 | 插入,迭代器不会失效。删除,指向被删除节点迭代器失效 |
容器特点比较以及选择
| vector | deque | list | set | multiset | map | multimap |
名称 | 向量容器 | 双向队列容器 | 列表容器 | 集合 | 多重集合 | 映射 | 多重映射 |
特点 | 增加和获取元素效率 很高,插入和删除的 效率很低
| 增加和获取元素效率 较高,插入和删除的 效率较高
| 增加和获取元素效率 很低,插入和删除的 效率很高 | 1.键(关键字)和值(数据)相等(就是模版只有一个参数,键和值合起来) 2.键唯一 3.元素默认按升序排列 | 1.键和值相等 2.键可以不唯一 3.元素默认按升序排列 | 1.键和值分开(模版有两个参数,前面是键后面是值) 2.键唯一 3.元素默认按键的升序排列 | 1.键和值分开 2.键可以不唯一 3.元素默认按键的升序排列 |
定义容器 | vector<string> book(50); | deque<string> book(50); | list<string> book; | set<string> book; | multiset<string> book; | map<int,string> book; | multimap<int,string> book; |
无序关联容器
unordered_map与unordered_set都是无序的关联容器有序关联容器中的关键字是有序排列的,所以要求关键字可以进行<运算符比较或满足自定义的比较操作。无序关联容器不是使用比较运算符来组织元素,而是使用一个哈希函数和关键字类型的==运算符。
无序容器可以使用上述所有的与有序容器相同的操作,由于无序容器在存储上组织为桶,每个桶保存零个或多个元素,容器的性能依赖于哈希函数的质量和桶的数量和大小,因此无序容器多了一些哈希函数和桶相关的操作。
1.桶接口
m.bucket_count() 正在使用的桶的数目
m.max_bucket_count() 容器能容纳的最多的桶的数量
m.bucket_size(n) 第n个桶中有多少个元素
m.bucket(k) 关键字为k的元素在哪个桶
2.桶迭代
local_iterator 可以用来访问桶中元素的迭代器类型
const_local_iterator 桶迭代器的const版本
m.begin(n)、m.end(n) 桶n的首元素迭代器和尾后迭代器
m.cbegin(n)、m.cend(n) 与前两个函数类似,但返回const_local_iterator
3.哈希策略
//每个桶的平均元素数量,返回float值
m.load_factor()
//m试图维护的平均桶大小,返回float值,要求创建的新桶的load_factor<=max_load_factor
m.max_load_factor()
//重新存储,使得bucket_count>=n,且bucket_count>size/max_load_factor
m.rehash(n)
//重新存储,使得m可以保存n个元素且不必rehash
m.reserve(n)