关联式容器
也是用来存储数据的,与序列式容器不同的是,其
里面存储的是
<key, value>
结构的键值对,在数据检索时比序列式容器效率更高
。
用来表示具有一一对应关系的一种结构,该结构中一般只包含两个成员变量
key
和
value
,
key
代表键值,
value
表示与
key
对应的信息
。比如:现在要建立一个英汉互译的字典,那该字典中必然有英文单词与其对应的中文含义,而且,英文单词与其中文含义是一一对应的关系,即通过该应该单词,在词典中就可以找到与
其对应的中文含义。
键值对代码示例:
#include<iostream>
#include<string>
using namespace std;
//template<class _Ty1,class _Ty2>
//struct KeyValue
//{
// _Ty1 first;
// _Ty2 second;
//};
void test01()
{
//值对
pair<int, char>p1; //first second
cout << p1.first << " " << p1.second << endl;
pair<int, int>p2(1, 100);
cout << p2.first << " " << p2.second << endl;
pair<int, string>p3 = make_pair(10, "lalala");//make_pair的使用
cout << p3.first<<" " << p3.second << endl;
}
void main()
{
test01();
}
根据应用场景的不桶,
STL
总共实现了两种不同结构的管理式容器:树型结构与哈希结构。
树型结构的关联式容器主要有四种:
map
、
set
、
multimap
、
multiset
。这四种容器的共同点是:使用平衡搜索树
(
即红黑树
)
作为其底层结果,容器中的元素是一个有序的序列。
map容器:
1. map
是关联容器,它按照特定的次序
(
按照
key
来比较
)
存储由键值
key
和值
value
组合而成的元素。
2.
在
map
中,键值
key
通常用于排序和惟一地标识元素,而值
value
中存储与此键值
key
关联的内容。键值
key
和值
value
的类型可能不同,并且在
map
的内部,
key
与
value
通过成员类型
value_type
绑定在一起,
为其取别名称为
pair:
typedef pair value_type;
3.
在内部,
map
中的元素总是按照键值
key
进行比较排序的。
4. map
中通过键值访问单个元素的速度通常比
unordered_map
容器慢,但
map
允许根据顺序对元素进行
直接迭代
(
即对
map
中的元素进行迭代时,可以得到一个有序的序列
)
。
5. map
支持下标访问符,即在
[]
中放入
key
,就可以找到与
key
对应的
value
。
6. map
通常被实现为二叉搜索树
(
更准确的说:平衡二叉搜索树
(
红黑树
)(key值符合红黑树(也是二叉搜索树))中序访问红黑树)。
![](https://img-blog.csdnimg.cn/8a113cbbfe27408a8844258a227fa8fe.png)
key:
键值对中
key
的类型
T
: 键值对中
value
的类型
Compare:
比较器的类型,
map
中的元素是按照
key
来比较的,缺省情况下按照小于来比较,一般情况
下
(
内置类型元素
)
该参数不需要传递,如果无法比较时
(
自定义类型
)
,需要用户自己显式传递比较规则
(
一般情况下按照函数指针或者仿函数来传递)。
Alloc
:通过空间配置器来申请底层空间,不需要用户传递,除非用户不想使用标准库提供的空间配置器注意:在使用
map
时,需要包含头文件。
![](https://img-blog.csdnimg.cn/83fc7bdaeef74732999efab03152c460.png)
注意:在元素访问时,有一个与operator[]类似的操作at()(该函数不常用)函数,都是通过key找到与key对应的value然后返回其引用,不同的是:当key不存在时,operator[]用默认value与key构造键值对 然后插入,返回该默认value,at()函数直接抛异常。
1. map
中的的元素是键值对
2. map
中的
key
是唯一的,并且不能修改
3.
默认按照小于的方式对
key
进行比较
4. map
中的元素如果用迭代器去遍历,可以得到一个有序的序列
5. map
的底层为平衡搜索树
(
红黑树
)
,查找效率比较高
6.
支持
[]
操作符,
operator[]
中实际进行插入查找。
map接口示例:
#include<iostream>
#include<map>
using namespace std;
void test01()
{
pair<int, int>v1(1, 100);
pair<int, int>v2(2, 200);
pair<int, int>v3(3, 300);
map<int, int>m1;
m1.insert(v1);
m1.insert(v2);
m1.insert(v3);
}
void test02()
{
pair<int, int>va[] = { {3,100},{6,600},{2,200},{7,700},{8,800} };
map<int, int>m2;
int n = sizeof(va) / sizeof(va[0]);
for (int i = 0; i < n; ++i)
m2.insert(va[i]);//map 按照key值排序
map<int, int>::iterator it = m2.begin();
while (it != m2.end())
{
cout << it->first << " " << it->second << endl;
it++;
}
cout << "---------------------" << endl;
for (const auto& e : m2)
cout << e.first << " " << e.second << endl;
cout << "---------------------" << endl;
for (int i = 0; i < m2.size(); ++i)//有的key没有value
{
cout << m2[i] << endl;
}
cout << "---------------------" << endl;
m2[3] = 300;//修改value
m2[1] = 100;//插入新的
for (const auto& e : m2)
cout << e.first << " " << e.second << endl;
cout << "---------------------" << endl;
}
//vector的.at会检查下标越界,[]不检查
void test03()
{
map<int, int>m3{ {1,100},{5,500},{3,300},{6,600} ,{2,200} };
map<int, string>mp3{ {3,"abc"},{1,"xyz"},{5,"opq"},{4,"lmn"},{20,"hjk"} };
mp3.insert(make_pair(8, "hym"));
auto it=mp3.erase(mp3.begin());//返回下一个数据
//auto it = mp3.erase(5);//返回删除元素的个数
//auto it = mp3.erase(mp3.begin(),mp3.end());//返回end()
cout << it->first << " " << it->second << endl;
for (const auto& e : mp3)
{
cout << e.first << " " << e.second << endl;
}
}
void test04()//迭代器失效
{
map<int, string>mp4{ {3,"abc"},{1,"xyz"},{5,"opq"},{4,"lmn"},{20,"hjk"} };
mp4.insert(make_pair(8, "hym"));
auto pos = mp4.begin();
pos=mp4.erase(pos);
cout << pos->first << " " << pos->second << endl;
}
void test05()
{
map<int, string>mp5{ {3,"abc"},{1,"xyz"},{5,"opq"},{4,"lmn"},{20,"hjk"} };
mp5.insert(make_pair(8, "hym"));
auto pos = mp5.find(50);//找到返回迭代器,找不到返回end
}
void test06()
{
map<int, string>mp{ {3,"abc"},{1,"xyz"},{5,"opq"},{4,"lmn"},{20,"hjk"},{3,"lll"} };//key不允许重复值,value允许
cout << mp.count(3) << endl;//为1
multimap<int, string>mmp{ {3,"abc"},{1,"xyz"},{5,"opq"},{4,"lmn"},{20,"hjk"},{3,"lll"} };
cout << mmp.count(3) << endl;//为2
}
void main()
{
test06();
}
multimap:
1. Multimaps
是关联式容器,它按照特定的顺序,存储由
key
和
value
映射成的键值对
<key, value>
,其中多个键值对之间的
key
是可以重复的。
2.
在
multimap
中,通常按照
key
排序和惟一地标识元素,而映射的
value
存储与
key
关联的内容。
key
和value
的类型可能不同,通过
multimap
内部的成员类型
value_type
组合在一起,
value_type
是组合
key
和
value
的键值对
:
typedef pair<const Key, T> value_type
;
3.
在内部,
multimap
中的元素总是通过其内部比较对象,按照指定的特定严格弱排序标准对
key
进行排序的。
4. multimap
通过
key
访问单个元素的速度通常比
unordered_multimap
容器慢,但是使用迭代器直接遍历
multimap
中的元素可以得到关于
key
有序的序列。
5. multimap
在底层用二叉搜索树
(
红黑树
)
来实现。
注意:multimap和map的唯一不同就是:map中的key是唯一的,而multimap中key是可以重复的。multimap中的接口可以参考map,功能都是类似的。
1. multimap中的key是可以重复的。
2. multimap中的元素默认将key按照小于来比较。
3. multimap中没有重载operator[]操作,不然存在二义性 !!!
4. 使用时与map包含的头文件相同。
multimap接口示例:
#include<iostream>
#include<functional>
#include<set>
#include<map>
using namespace std;
//重点:树形结构唯一会造成迭代器失效的情况就是删除一个迭代器对应的节点再对它操作
重点 key值一定是不能修改的,不然破坏树形结构
void test01()
{
multimap<int, string>mmp{ {3,"abc"},{1,"xyz"},{5,"opq"},{4,"lmn"},{20,"hjk"},{3,"fff"} };
mmp.insert(make_pair(8, "hym"));
size_t count = mmp.erase(3);
cout << count << endl;
}
void test02()
{
multimap<int, string>mmp{ {3,"abc"},{1,"xyz"},{5,"opq"},{4,"lmn"},{20,"hjk"},{3,"fff"} };
mmp.insert(make_pair(8, "hym"));
auto pos = ++mmp.begin();
auto it = mmp.erase(pos);
cout << it->first << " " << it->second << endl;
}
void main()
{
test02();
}
set容器:
1. set
是按照一定次序存储元素的容器
2.
在
set
中,元素的
value
也标识它
(value
就是
key
,类型为
T)
,并且每个
value
必须是唯一的。
set
中的元素
不能在容器中修改
(
元素总是
const)
,但是可以从容器中插入或删除它们。
3.
在内部,
set
中的元素总是按照其内部比较对象
(
类型比较
)
所指示的特定严格弱排序准则进行排序。
4. set
容器通过
key
访问单个元素的速度通常比
unordered_set
容器慢,但它们允许根据顺序对子集进行直接迭代。
5. set
在底层是用二叉搜索树
(
红黑树
)
实现的。
注意:
1. 与map/multimap不同,map/multimap中存储的是真正的键值对<key, value>,set中只放value,但在底层实际存放的是由<value, value>构成的键值对。
2. set中插入元素时,只需要插入value即可,不需要构造键值对。
3. set中的元素不可以重复(因此可以使用set进行去重)。
4. 使用set的迭代器遍历set中的元素,可以得到有序序列
5. set中的元素默认按照小于来比较 。
6. set中查找某个元素,时间复杂度为: log2N
7. set中的元素不允许修改(为什么?) (修改会破坏底层树形结构)
8. set中的底层使用二叉搜索树(红黑树)来实现。
![](https://img-blog.csdnimg.cn/1a4f4d6b4b8e49fc86f8c574fbd7bac9.png)
T: set中存放元素的类型,实际在底层存储<value, value>的键值对。 Compare:set中元素默认按照小于来比较 Allocset中元素空间的管理方式,使用STL提供的空间配置器管理。
#include<iostream>
#include<functional>
#include<set>
using namespace std;
class Test
{
public:
Test(int a, int b) :m_a(a), m_b(b) {}
private:
int m_a;
int m_b;
};
void test01()
{
set<int>s{ 5,6,3,2,8,9,1,0,4,7};
set<int>::iterator it = s.begin();
s.insert(6);//不能重复
while (it != s.end())
{
cout << *it << " ";
++it;
}
cout << endl;
}
void test02()
{
//set<int, string>s{ {1,"abc"},{2,"xyz"} };//不能这样写,不能俩类型参数
//set<int, string>s;
set<int,greater<int>>s{ 5,6,3,2,8,9,1,0,4,7 };
set<int>::iterator it = s.begin();
s.insert(6);//不能重复
while (it != s.end())
{
cout << *it << " ";
++it;
}
cout << endl;
}
void test03()
{
pair<int, string>v1{ 1,"xyz" };
pair<int, string>v2{ 2,"abc" };
set<pair<int, string>>s;
s.insert(v1);
s.insert(v2);
for (const auto& e : s)
cout << e.first << " " << e.second << endl;
for (set<pair<int, string>>::iterator it = s.begin(); it != s.end(); it++)
{
cout << it->first << " " << it->second << endl;
}
}
void test04()
{
Test t1(0, 0);
Test t2(1, 1);
Test t3(2, 2);
set<Test>s1;
}
void main()
{
test03();
}
multiset:
1. multiset
是按照特定顺序存储元素的容器,其中元素是可以重复的。
2.
在
multiset
中,元素的
value
也会识别它
(
因为
multiset
中本身存储的就是
<value, value>
组成的键值对,因此
value
本身就是
key
,
key
就是
value
,类型为
T). multiset
元素的值不能在容器中进行修改
(
因为元素
总是
const
的
)
,但可以从容器中插入或删除。
3.
在内部,
multiset
中的元素总是按照其内部比较规则
(
类型比较
)
所指示的特定严格弱排序准则进行排
序。
4. multiset
容器通过
key
访问单个元素的速度通常比
unordered_multiset
容器慢,但当使用迭代器遍历时会得到一个有序序列。
5. multiset
底层结构为二叉搜索树
(
红黑树
)。
注意:
1. multiset中再底层中存储的是<value, value>的键值对
2. mtltiset的插入接口中只需要插入即可
3. 与set的区别是,multiset中的元素可以重复,set是中value是唯一的
4. 使用迭代器对multiset中的元素进行遍历,可以得到有序的序列
5. multiset中的元素不能修改
6. 在multiset中找某个元素,时间复杂度为 log2N
7. multiset的作用:可以对元素进行排序
#include<iostream>
#include<functional>
#include<set>
#include<map>
using namespace std;
//重点:树形结构唯一会造成迭代器失效的情况就是删除一个迭代器对应的节点再对它操作
重点 key值一定是不能修改的,不然破坏树形结构
void test01()
{
multimap<int, string>mmp{ {3,"abc"},{1,"xyz"},{5,"opq"},{4,"lmn"},{20,"hjk"},{3,"fff"} };
mmp.insert(make_pair(8, "hym"));
size_t count = mmp.erase(3);
cout << count << endl;
}
void test02()
{
multimap<int, string>mmp{ {3,"abc"},{1,"xyz"},{5,"opq"},{4,"lmn"},{20,"hjk"},{3,"fff"} };
mmp.insert(make_pair(8, "hym"));
auto pos = ++mmp.begin();
auto it = mmp.erase(pos);
cout << it->first << " " << it->second << endl;
}
void main()
{
test02();
}
在C++98中,STL提供了底层为红黑树结构的一系列关联式容器,在查询时效率可达到 log2N
,即最差情况下需要比较红黑树的高度次,当树中的节点非常多时,查询效率也不理想。最好的查询是,进行很少的比较次 数就能够将元素找到,因此在C++11中,STL又提供了4个unordered系列的关联式容器,这四个容器与红黑 树结构的关联式容器使用方式基本类似,只是其底层结构不同。
unoredered_map、unordered_set、unordered_multimap及unordered_multiset底层为哈希表,unordered表示不排序,multi表示数据可以重复。
1. unordered_map是存储<key, value>键值对的关联式容器,其允许通过keys快速的索引到与其对应的value。
2. 在unordered_map中,键值通常用于惟一地标识元素,而映射值是一个对象,其内容与此键关联。键和映射值的类型可能不同。
3. 在内部,unordered_map没有对<kye, value>按照任何特定的顺序排序, 为了能在常数范围内找到key所对应的value,unordered_map将相同哈希值的键值对放在相同的桶中。
4. unordered_map容器通过key访问单个元素要比map快,但它通常在遍历元素子集的范围迭代方面效率较低。
5. unordered_maps实现了直接访问操作符(operator[]),它允许使用key作为参数直接访问value。
6. 它的迭代器至少是前向迭代器。
![](https://img-blog.csdnimg.cn/e0fe241351f54247aeff1c193f0fe756.png)
#include<iostream>
#include<unordered_map>
using namespace std;
//unordered_map又叫散列
//不排序(底层为hash表),不重复
void test01()
{
unordered_map<int, string>mmp{ {3,"abc"},{1,"xyz"},{5,"opq"},{4,"lmn"},{20,"hjk"},{3,"fff"} };
mmp.insert(make_pair(8, "hym"));
for (const auto& e : mmp)
{
cout << e.first << " " << e.second << endl;
}
}
//不排序但是允许重复
void test02()
{
unordered_multimap<int, string>mmp{ {3,"abc"},{1,"xyz"},{5,"opq"},{4,"lmn"},{20,"hjk"},{3,"fff"} };
mmp.insert(make_pair(8, "hym"));
for (const auto& e : mmp)
{
cout << e.first << " " << e.second << endl;
}
}
void main()
{
test02();
}
unordered_set可以查阅官方文档(cplusplus.com)进行学习
#include<iostream>
#include<functional>
#include<unordered_set>
using namespace std;
//unoredred_set不排序,不重复
void test01()
{
unordered_set<int>s{ 5,6,3,2,8,9,1,0,4,7,1,5,8};
unordered_set<int>::iterator it = s.begin();
s.insert(6);//不能重复
while (it != s.end())
{
cout << *it << " ";
++it;
}
cout << endl;
}
//允许重复不排序
void test02()
{
unordered_multiset<int>s{ 5,6,3,2,8,9,1,0,4,7,1,5,8 };
unordered_multiset<int>::iterator it = s.begin();
s.insert(6);
while (it != s.end())
{
cout << *it << " ";
++it;
}
cout << endl;
}
void main()
{
test02();
}
hash_map和unordered_map,hash_set和unordered_set,可能具体的哈希表实现不同,但是几乎等价。
#define _SILENCE_STDEXT_HASH_DEPRECATION_WARNINGS
#include<iostream>
#include<unordered_map>
#include<hash_map>
using namespace std;
//unordered_map又叫散列 等价于hash_map
//不排序(底层为hash表),不重复
void test01()
{
hash_map<int, string>mmp{ {3,"abc"},{1,"xyz"},{5,"opq"},{4,"lmn"},{20,"hjk"},{3,"fff"} };
mmp.insert(make_pair(8, "hym"));
for (const auto& e : mmp)
{
cout << e.first << " " << e.second << endl;
}
}
//不排序但是允许重复 等价于 hash_multimap
void test02()
{
unordered_multimap<int, string>mmp{ {3,"abc"},{1,"xyz"},{5,"opq"},{4,"lmn"},{20,"hjk"},{3,"fff"} };
mmp.insert(make_pair(8, "hym"));
for (const auto& e : mmp)
{
cout << e.first << " " << e.second << endl;
}
}
void main()
{
test01();
}
#define _SILENCE_STDEXT_HASH_DEPRECATION_WARNINGS
#include<iostream>
#include<functional>
#include<unordered_set>
#include<hash_set>
using namespace std;
//unoredred_set不排序,不重复 等价于 hash_set
void test01()
{
hash_set<int>s{ 5,6,3,2,8,9,1,0,4,7,1,5,8 };
hash_set<int>::iterator it = s.begin();
s.insert(6);//不能重复
while (it != s.end())
{
cout << *it << " ";
++it;
}
cout << endl;
}
//允许重复不排序 等价于 hash_multiset
void test02()
{
hash_multiset<int>s{ 5,6,3,2,8,9,1,0,4,7,1,5,8 };
hash_multiset<int>::iterator it = s.begin();
s.insert(6);
while (it != s.end())
{
cout << *it << " ";
++it;
}
cout << endl;
}
void main()
{
test01();
}