关联容器
关联容器和顺序容器有着根本的不同:关联容器中的元素是按关键字来保持和访问的。与之相对,顺序容器中的元素是按它们在容器中的位置来顺序保存和访问的。和顺序容器一样的是,关联容器也是模板
关联容器类型:
按关键字有序保存元素
map 关联数组:保存关键字-值对
set 关键字即值,即只保存关键字的容器
multimap 关键字可以重复出现的 map
mutiset 关键字可以重复出现的 set
无序集合
unordered_map 用哈希函数组织的 map
unordered_set 用哈希函数组织的 set
unordered_multimap 哈希组织的 map:关键字可以重复出现
unordered_multimap 哈希组织的 set:关键字可以重复出现
使用map:
#include<bits/stdc++.h>
using namespace std;
int main(){
map<string,size_t> mp;
istream_iterator<string> in(cin),eof;
string words;
while(in!=eof)mp[*in++]++;
for(const auto &x:mp)cout<<x.first<<" "<<x.second<<endl;
ostream_iterator<string> os(cout,"\n");//使用输出迭代器
for(const auto &x:mp)os=x.first;
return 0;
}
注意:若 word 在 map 中则对应值加一,若不在则创建一个关键字为 word 值为 0 的元素并将值加一
map 容器(关联容器)的 key 是 const 的。
使用set
#include<bits/stdc++.h>
using namespace std;
int main(){
set<string> exclude = {"the", "but", "and", "or", "an", "a"};
istream_iterator<string> in(cin),eof;
ostream_iterator<string> os(cout," ");
string s;
while(in!=eof) if(exclude.find(*in++)!=exclude.end()) os="yes";
return 0;
}
关联容器的迭代器都是双向的
set和map初始化
#include<bits/stdc++.h>
using namespace std;
int main(){
//关联容器初始化
map<string,int> a={{"jfk", 1},{"jfskl", 1},{"wei", 2},{"jflsdfd", 11},{"fjks", 12},};//列表初始化
map<string,int> b(a.begin(),a.end());
// map<char*,int> c=a;//不同类型的不能拷贝赋值,所以报错
map<string,int> d=a;//类型相同的可以拷贝
d=b;//拷贝赋值
//set和map类似
set<int> a1={1,2,3,4,5,9};
set<int> b1(a1.begin(),a1.end());
set<int> c1=a1;
return 0;
}
注意:关键字可重复的 map/set,无序 map/set 等这 8 种关联容器的初始化方式完全相同
支持使用列表初始化
类型必须完全相同的容器对象之间才可以拷贝赋值
类型必须完全相同的容器对象之间才可以使用一对范围迭代器构造初始化
使用关键字类型的比较函数:
#include<bits/stdc++.h>
using namespace std;
class Node{
friend bool cmp(const Node &a,const Node&b);
friend ostream& operator<<(ostream&a,const Node&);
private:
int x,y;
public :
Node(int a,int b):x(a),y(b){};
Node(int a):Node(a,0){};
Node():Node(0,0){};
~Node(){};
};
bool cmp(const Node &a ,const Node &b){ return a.x<b.x;}
ostream& operator<<(ostream&os,const Node &a){os<<a.x<<" "<<a.y<<endl;}
int main(){
set<Node,decltype(cmp)*> a(cmp);注意,当用decltype来获得一个函数指针类型时,必须加上一个*来指出我们要使用一个给定函数类型的指针
a.insert(Node(1,2));a.insert(Node(2,3));
for(auto x:a)cout<<"set:"<<x;
//此处定义map类型对象我们要提供三个类型:关键字类型,值类型,比较类型(一种函数指针类型)
map<Node,int,decltype(cmp)*> mp(cmp);注意,当用decltype来获得一个函数指针类型时,必须加上一个*来指出我们要使用一个给定函数类型的指针
mp={{Node(1,3),2},{Node(2,6),4}};
for(const auto &indx : mp)cout << indx.first << indx.second << endl;//在类里面重载运算符。
return 0;
}
当然可以重载小于运算符
#include<bits/stdc++.h>
using namespace std;
class Node{
friend ostream& operator<<(ostream&a,const Node&);
friend bool operator<(const Node&, const Node&);
private:
int x,y;
public :
Node(int a,int b):x(a),y(b){};
Node(int a):Node(a,0){};
Node():Node(0,0){};
~Node(){};
};
ostream& operator<<(ostream&os,const Node &a){os<<a.x<<" "<<a.y<<endl;}
bool operator<(const Node&a, const Node&b){a.x<b.x;}
int main(){
set<Node> a;
return 0;
}
关联容器额外的类型别名
#include<bits/stdc++.h>
using namespace std;
int main(){
//key_type关键字类型,
set<string>s;
set<string>::key_type s1;//s1是一个string类型
map<string,int>::key_type s2;
cin>>s2;cout<<s2<<endl;
//mapped_type
map<string,int>::mapped_type d1;//d1是一个int类型,mapped只和映射类容器有关
cin>>d1;cout<<d1<<endl;
//value_type
set<string>::value_type s3;//就是他的关键字类型
cin>>s3;cout<<s3<<endl;
map<string,int>::value_type d2;//是一个pair类型
cin>>d2.second;
cout<<sizeof(d2.second)<<" "<<d2.second;
}
遍历关联容器
#include<bits/stdc++.h>
using namespace std;
int main(){
//set的迭代器是const的。
set<int> st={1,2,4,5,6,7};
set<int>::iterator it=st.begin();
//(*it)++;//报错,因为set的迭代器是const的。
//map的迭代器不是const的。
map<int,int> mp={{1,1},{2,2},{3,3},{4,4}};
map<int,int>::iterator itmp=mp.begin();
while(itmp!=mp.end())cout<<itmp->first<<" "<<itmp->second++<<endl,itmp++;//可以修改第二关键字,
itmp=mp.begin();
while(itmp!=mp.end())cout<<itmp->first<<" "<<itmp->second++<<endl,itmp++;
return 0;
}
关联容器和算法:
由于关键字是 const 的特性意味着我们不能将关联容器传递给修改或重排元素的算法,我们通常不对关联容器使用泛型算法。而且关联容器使用容器自定义的算法通常会比使用泛型算法更快。
在实际编程中,如果我们真要对一个关联容器使用算法,要么是将它当作一个源序列,要么当作一个目的的位置。例如,可以使用泛型 copy 算法将元素匆匆一个关联容器拷贝到另一个序列。类似的,可以调用 inserter 将一个插入器绑定到一个关联容器。通过使用 inserter,我们可以将关联容器当作一个目的位置来调用另一个算法:
向关联容器添加算法
#include<bits/stdc++.h>
using namespace std;
int main(){
map<int,int> mp;
mp.insert({{1,3},{2,4}});
mp.insert(pair<int,int>(6,8));
mp.insert(make_pair(5,7));
mp.insert(map<int,int>::value_type(9,10));
set<int> st;
st.insert({1,3,4});
st.insert(st.begin(),4);
st.insert(st.begin(),st.end());//一个迭代器序列
return 0;
}
注意:insert 有两个版本,分别接受一对迭代器,或是一个初始化器列表
对于非 multi 的关联容器,对于一个给定的关键字,只有第一个带此关键字的元素才被插入到容器中
对一个 map 进行 insert 操作时,元素类型必须是 pair。通常若是没用一个现成的 pair 对象,可以在 insert 参数列表中创建一个 pair
map.insert的返回值
在map中insert一个pair,返回的是一个pair<key,bool>,key是插入值的关键字,bool表示是否插入成功
#include<bits/stdc++.h>
using namespace std;
int main(){
map<int,int> mp;
mp.insert({1,1});
auto p=mp.insert({1,2});
if(p.second)cout<<"插入成功";//已经有了这个关键字,插入失败。
else cout<<"false";
return 0;
}
insert返回值递增
#include<bits/stdc++.h>
using namespace std;
int main(){
//展开递增语句,单词计数程序
map<string,int> mp;
string s;
while(cin>>s){
auto ret=mp.insert({s,1});
//insert返回一个pair,它的位置是指定关键字的位置,second是一个bool值,指出是否插入成功
//ret.first->解引用这个位置,然后指向second,然后++加一
++ret.first->second;//++((ret.first)->second);
}
return 0;
}
删除元素:
从关联容器删除元素:
c.erase(k) | 从 c 中删除每个关键字为 k 的元素。返回一个 size_type 值,指出删除的元素的数量 |
---|---|
c.erase§ | 从 c 中删除迭代器 p 指定的元素。p 必须指向 c 中一个真实元素,不能等于 c.end()。返回一个指向 p 之后元素的迭代器 |
c.erase(b, e) | 删除迭代器对 b 和 e 所表示范围(左闭右开)中的元素。返回 e |
#include<bits/stdc++.h>
using namespace std;
int main(){
map<int,int> mp={{1,1},{2,2},{3,3},{4,4}};
set<int> st={1,2,3,4};
//通过关键字进行删除
mp.erase(1);cout<<mp.size()<<endl;
st.erase(1);cout<<st.size()<<endl;
//通过迭代器删除
mp.erase(mp.begin());cout<<mp.size()<<endl;
st.erase(st.begin());cout<<st.size()<<endl;
//通过范围迭代器删除
mp.erase(mp.begin(),mp.end());cout<<mp.size()<<endl;
st.erase(st.begin(),st.end());cout<<st.size()<<endl;
return 0;
}
map 的下标操作与一般下标操作的不同:
如果关键字不在 map 中,会以该关键字创建一个元素插入到 map 中,其关联值将进行值初始化
通常情况下,解引用一个迭代器所返回的类型与下标运算返回的类型是一样的。但对 map 则不然:当对一个 map 进行下标操作时,会获得一个 mapped_type 对象,但当解引用一个 map 迭代器时,会得到一个 value_type 对象
#include<bits/stdc++.h>
using namespace std;
int main(){
map<int,int>mp={{1,1},{2,2},{3,3},{4,4}};
try{
cout<<mp.at(1)<<endl;
}catch(out_of_range err){
cout<<err.what();
return 0;
}
cout<<mp[2]<<endl;
try{
cout<<mp.at(5)<<endl;
}catch(out_of_range err){
cout<<err.what()<<"越界了"<<endl;
return 0;
}
return 0;
}
访问元素:
在一个关联容器中查找元素的操作:
lower_bound 和 upper_bound 不适用于无序容器
下标和 at 只适用于非 const 的 map 和 unordered_map
c.find(k) | 返回一个迭代器,指向第一个关键字为 k 的元素,若 k 不在容器中则返回尾后迭代器 |
---|---|
c.count(k) | 返回关键字等于 k 的元素的数量。对于不允许元素重复的容器返回值永远是 0 或 1 |
c.lower_bound(k) | 返回一个迭代器,指向第一个关键字不小于 k 的元素 |
c.upper_bound(k) | 返回一个迭代器,指向第一个关键字大于 k 的元素 |
c.equal_range(k) | 返回一个迭代器 pair,表示关键字等于 k 的元素的范围。若 k 不存在,pair 的两个成员均等于 c.end() |
对 map 使用 find 代替下标操作:由于下标操作如果查找的关键字不在容器中会被插入,这不一定符合我们的预期。如果仅仅是在 map 容器中查找元素,我们应该使用 find 操作
在 multimap 或 multiset 中查找元素:
注意:和 map 以及 set 一样,multimap 和 multiset 都是按照关键字升序排列的。因此关键字相同的元素会相邻存储
#include<bits/stdc++.h>
using namespace std;
int main(){
multimap<int,int >mp={{1, 1}, {3, 3}, {3,2}, {2, 1}, {1, 1}, {2, 2}};
int cnt=mp.count(3);
auto it1=mp.find(3);
while(cnt--){
cout<<it1->first<<" "<<it1->second<<endl;
it1++;
}
multiset<int>st={2,4,5,1,3,6,3,3};
cnt=st.count(3);
auto it=st.find(3);
while(cnt--) cout<<*it++<<" ";
return 0;
}
用 equal_range 函数重写上面的代码:
#include<bits/stdc++.h>
using namespace std;
int main(){
multimap<int,int >mp={{1, 1}, {3, 3}, {3,2}, {2, 1}, {1, 1}, {2, 2}};
int cnt=mp.count(3);
auto it1=mp.equal_range(3);
while(it1.first!=it1.second){
cout<<it1.first->first<<" "<<it1.first->second<<endl;
it1.first++;
}
multiset<int>st={2,4,5,1,3,6,3,3};
cnt=st.count(3);
auto it=st.equal_range(3);
while(it.first!=it.second){
cout<<*it.first<<" ";
it.first++;
}
return 0;
}
使用undered_map:
#include<bits/stdc++.h>
using namespace std;
int main(){
string s;
unordered_map<string,int>mp;
while(cin>>s&&s!="aa"){
auto it=mp.find(s);
it->second++;
}//使用find代替下标
}
使用 unordered_set:
#include<bits/stdc++.h>
using namespace std;
int main(){
unordered_set<string> exclude = {"the", "but", "and", "or", "an", "a"};
string s;
while(cin>>s)
if(exclude.find(s)==exclude.end())//如果不在容器里面
exclude.insert(s);
cout<<exclude.size();
}
注意:无序容器中关键字相同的元素也是相邻存储的
可以发现,除了无序容器提供了特有的管理桶函数外有序和无序版本的关联容器用法基本一致
管理桶:
无序容器在存储上组织为一组桶,每个桶保存零个或多个元素。无序容器使用一个哈希函数将元素映射到桶。为了访问一个元素,容器首先计算元素的哈希值,它指出应该搜索哪个桶。容器将具有一个特定哈希值的所有元素保存在相同的桶中。如果容器允许重复关键字,所有具有相同关键字的元素也都会在同一个桶中。实际上这就是链地址法哈希。
无序容器提供了一组管理桶的函数,这些成员函数允许我们查询容器的状态以及在必要时强制容器重组
无序容器管理操作:
桶接口:
c.bucket_count() 正在使用的桶的数目
c.max_bucket_count() 容器能容纳的最多桶的数目
c.bucket_size(n) 第 n 个桶中有多少元素
c.bucket(k) 关键字为 k 的元素在哪个桶中
桶迭代
local_iterator 可以用来访问桶中元素的迭代器类型
const_local_iterator 桶迭代器的 const 版本
c.begin(n), c.end(n) 桶 n 的首元素迭代器和尾后迭代器
c.cbegin(n), c.cend(n) 与前两个函数类似,但返回 const_local_iterator
哈希策略
c.load_factor() 每个桶的平均元素数量,返回 float 值
c.max_load_factor() c 试图维护的平均桶大小,返回 float 值。c 会在需要时添加新的桶,以使得
load_factor <= max_load_factor
c.rehash(n) 重组存储,使得 bucket_count >= n且 bucket_count > size / max_load_factor
c.reserve(n) 重组存储,使得 c 可以保存 n 个元素且不必 rehash
无序容器对关键字类型的要求:
默认情况下,无序容器使用关键字类型的 == 来比较元素,它们还使用一个 hash<key_type> 类型的对象来生成每个元素的哈希值。标准库为内置类型(包括指针)提供了 hash 模板。还为一些标准库类型,包括 string 和智能指针定义了 hash。因此,我们可以直接定义关键字是内置类型(包括指针类型)、string 还是智能指针类型的无序容器。
但是,我们不能直接定义关键字类型为自定义类型的无序容器。和前面用自定义类型做有序容器关键字类似,我们可以通过自己提供 hash 函数和类型比较函数来使用自定义类型做无序容器关键字:
#include<bits/stdc++.h>
using namespace std;
class Node{
friend ostream& operator<<(ostream&,const Node&);
private :
int x,y;
public :
Node(int a,int b):x(a),y(b){}
Node(int a):Node(a,0){}
Node():Node(0){}
~Node(){};
int get_id()const {return x;}
};
ostream& operator<<(ostream&os,const Node& a){
os<<a.x<<" "<<a.y<<"\n";
return os;
}
bool eq(const Node&a,const Node&b){return a.get_id()==b.get_id();}
size_t hasher(const Node &it){ return hash<int>()(it.get_id());}
int main(){
unordered_set<Node,decltype(hasher)*,decltype(eq)*> a(40,hasher,eq);//三个函数是桶大小,哈希函数指针,比较函数指针
a.insert(Node(1,1));
a.insert(Node(2,2));
for(auto x:a)cout<<x<<endl;//使用自己的哈希函数
cout<<endl;
unordered_map<Node ,int ,decltype(hasher)*,decltype(eq)*> b(40,hasher,eq);
b = {{Node(1, 2), 1}, {Node(2, 4), 11}};
for(auto x:b)cout<<x.first<<endl;
}
重载==运算符实现以上功能
#include<bits/stdc++.h>
using namespace std;
class Node{
friend ostream& operator<<(ostream&,const Node&);
friend bool operator==(const Node &a,const Node &b){return a.get_id()==b.get_id();}
private :
int x,y;
public :
Node(int a,int b):x(a),y(b){}
Node(int a):Node(a,0){}
Node():Node(0){}
~Node(){};
int get_id()const {return x;}
};
ostream& operator<<(ostream&os,const Node& a){
os<<a.x<<" "<<a.y<<"\n";
return os;
}
//bool eq(const Node&a,const Node&b){return a.get_id()==b.get_id();}
//size_t hasher(const Node &it){
// return hash<int>(it.get_id());
//}
size_t hasher(const Node &it){
return hash<int>()(it.get_id());
}
int main(){
unordered_set<Node,decltype(hasher)*> a(40,hasher);//三个函数是桶大小,哈希函数指针,比较函数指针
a.insert(Node(1,1));
a.insert(Node(2,2));
for(auto x:a)cout<<x<<endl;//使用自己的哈希函数
cout<<endl;
unordered_map<Node ,int ,decltype(hasher)*> b(40,hasher);
b = {{Node(1, 2), 1}, {Node(2, 4), 11}};
for(auto x:b)cout<<x.first<<endl;
}