关联式容器
vector、list、deque等,这些容器统称为序列式容器,因为其底层为线性序列的数据结构,里面存储的是元素本身。
关联式容器也是用来存储数据的,与序列式容器不同的是,其里面存储的是结构的键值对,在数据检索时比序列式容器效率更高
键值对:
用来表示具有一一对应关系的一种结构,该结构中一般只包含两个成员变量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)
{}
};
树形结构的关联式容器:
STL总共实现了两种不同结构的管理式容器:树型结构与哈希结构。树型结构的关联式容器主要有四种:set、map、multiset、multimap。这四种容器的共同点是:使用平衡二叉搜索树(红黑树)作为其底层,容器中的元素是一个有序的序列。
set
介绍
使用平衡二叉搜索树(红黑树)作为其底层。
- set是按照一定次序存储元素的容器
- 在set中,元素的value也标识它(value就是key,类型为T),并且每个value必须是唯一的。set中的元素不能在容器中修改(元素总是const),但是可以从容器中插入或删除它们。
- 在内部,set中的元素总是按照其内部比较对象(类型比较)所指示的特定严格弱排序准则进行排序。
- set容器通过key访问单个元素的速度通常比unordered_set容器慢,但它们允许根据顺序对子集进行直接迭代。
- set在底层是用二叉搜索树(红黑树)实现的。
- set没有[]重载。
注意:
- 与map/multimap不同,map/multimap中存储的是真正的键值对<key, value>,set中只放value,但在底层实际存放的是由<key, key>构成的键值对。
- set中插入元素时,只需要插入value即可,不需要构造键值对。
- set中的元素不可以重复(因此可以使用set进行去重)。
- 使用set的迭代器遍历set中的元素,可以得到有序序列
- set中的元素默认按照小于来比较
- set中查找某个元素,时间复杂度为:㏒₂N(实际上是二叉搜索树的高度次)
- set中的元素不允许修改
- set中的底层使用二叉搜索树(红黑树)来实现。
set的模板参数:
class T 是 set 中存放元素的类型,实际在底层存储 <Key,Key> 的键值对
class Compare = less 是仿函数,使用缺省值即可,有需要再传参,set 中元素默认按照小于来比较
class Alloc = allocator 是空间配置器
使用
构造函数
set提供的构造函数有:第一个空构造和 第二个迭代器区间构造,一般都使用空构造,第三个是拷贝构造函数
void test_set1()
{
set<int> s1;//空构造
set<int> s2(s1.begin(), s1.end());//区间构造
set<int> s3(s1);//拷贝构造
}
赋值重载
s3 = s1;//赋值重载
Modifiers
void test_set1()
{
set<int> s;
s.insert(1); // 插入元素
s.insert(2);
s.insert(3);
s.insert(4);
s.insert(7);
s.insert(5);
s.insert(6);
s.erase(4);
s.erase(40); // 没有该元素,删除也不会报错
}
迭代器
// 排序加去重
for (auto& i : s) // 范围for循环
{
//*it = 2; // 不能修改
cout << i << " ";
}
cout << endl;
set<int>::iterator it = s.begin(); // 迭代器
while (it != s.end())
{
cout << *it << " ";
it++;
}
cout << endl;
Capacity
max_size 返回可以容纳的元素最大数
Operations
find:
set<int>::iterator pos = s.find(3); // 没找到返回end() O(㏒₂N) set自己的
set<int>::iterator pos = find(s.begin(),s.end(), 3); // 没找到返回end() O(N)
count:
std::set<int> myset;
// set some initial values:
for (int i = 1; i < 5; ++i) myset.insert(i * 3); // set: 3 6 9 12
for (int i = 0; i < 10; ++i)
{
std::cout << i;
if (myset.count(i) != 0)
std::cout << " is an element of myset.\n";
else
std::cout << " is not an element of myset.\n";
}
lower_bound(30) : 返回小于等于30的第一个迭代器
upper_bound(60):返回大于等于60的第一个迭代器
std::set<int> myset;
std::set<int>::iterator itlow, itup;
for (int i = 1; i < 10; i++) myset.insert(i * 10); // 10 20 30 40 50 60 70 80 90
itlow = myset.lower_bound(30); // ^
itup = myset.upper_bound(60); // ^
// 从itlow删除到itup
myset.erase(itlow, itup); // 10 20 70 80 90
std::cout << "myset contains:";
for (std::set<int>::iterator it = myset.begin(); it != myset.end(); ++it)
std::cout << ' ' << *it;
std::cout << '\n';
equal_range:
multiset
multiset的使用与set一致,最大区别:set去重+排序,multiset元素可重复+排序
multiset查找的元素是相同元素的时候,返回的是遍历中序遇到相同的第一个元素的迭代器
// 跟set的区别允许键值冗余
// 查找的是第一个出现的
multiset<int> ms;
ms.insert(1);
ms.insert(1);
ms.insert(1);
ms.insert(1);
ms.insert(1);
ms.insert(1);
for (auto& i : ms)
{
cout << i << " ";
}cout << endl;
与set一样的头文件
#include<set>
map
介绍
- map是关联容器,它按照特定的次序(按照key来比较)存储由键值key和值value组合而成的元素。
- 在map中,键值key通常用于排序和唯一地标识元素,而值value中存储与此键值key关联的内容。键值key和值value的类型可能不同,并且在map的内部,key与value通过成员类型value_type绑定在一起,为其取别名称为pair: typedef pair<const key, T value_type>;
- 在内部,map中的元素总是按照键值key进行比较排序的
- map中通过键值访问单个元素的速度通常比unordered_map容器慢,但map允许根据顺序对元素进行直接迭代(即对map中的元素进行迭代时,可以得到一个有序的序列)。
- map支持下标访问符,即在[]中放入key,就可以找到与key对应的value。
- map通常被实现为二叉搜索树(更准确的说:平衡二叉搜索树(红黑树))。
map的模板参数
class Key 是键值对中 key 的类型
class T 是键值对中 value 的类型
class Compare = less 是仿函数,,使用缺省值即可,有需要再传参,默认按照小于来比较
class Alloc = allocator > 是空间配置器,现在不用理会
map的成员类型
key_type 是第一个模板参数(Key)
mapped_type 是第二个模板参数(T)
value_type 是 pair 进行 typedef 得到的
key_compare 是第三个模板参数(Compare),比较的是 key_type(Key),缺省值为 less
value_compare是用于比较元素的嵌套函数类
注意:
pair(value_type )才是map存的值,mapped_type是pair::second的模板类型。
使用
构造函数
void Test_Map()
{
//KV模型,通过 Key查找Value
//map<Key, Value>
map<int, int> m1;//空构造
map<int, int> m2(m1.begin(), m1.end());//区间构造
map<int, int> m3(m1);//拷贝构造
}
赋值重载
m3 = m1;
Modifiers
insert:
void test_map1()
{
map<int, int> m;
//m.insert(1, 1); // 编译出错
m.insert(pair<int, int>(1, 1));
m.insert(pair<int, int>(3, 3));
m.insert(pair<int, int>(2, 2)); // pair的构造函数,构造一个匿名对象
m.insert(make_pair(4, 4)); // 函数模板构造一个pair对象
}
make_pair 的定义:
erase:
m.erase(2);
m.erase(6); // 没找到删除也不会报错
迭代器
map<int, int>::iterator it = m.begin();
while (it != m.end())
{
cout << (*it).first << ":" << (*it).second << endl;
cout << it->first << ":" << it->second << endl;
it++;
}
cout << endl;
for (auto& i : m)
{
cout << i.first << ":" << i.second << endl;
}
cout << endl;
Capacity
Operations
find
map<string, int>::iterator ret = countMap.find("西瓜");// 没找到返回end() O(㏒₂N)
[ ] 的重载(重点)
set没有[ ] 的用法
首先做一道题 统计水果出现的次数
第一种方法
string strArr[] = { "西瓜","西瓜","桃子","桃子","柚子","桃子","黄瓜","西瓜","西瓜" };
map<string, int> countMap;
// 1、
for (auto& str : strArr)
{
map<string, int>::iterator ret = countMap.find(str); // 没找到就插入
if (ret == countMap.end())
{
countMap.insert(make_pair(str, 1));
}
else
{
ret->second++;
}
}
for (auto& pair : countMap)
{
cout << pair.first << ":" << pair.second<<endl;
}
第二种方法:
我们得了解insert的返回值
第一个方法的返回值解释
单个元素版本 (1) 返回一个pair,其成员 pair::first 设置为指向新插入的元素或映射中具有等效键的元素的迭代器。如果插入了新元素,则对中的pair::second设置为 true;如果等效键已存在即插入失败,则设置为 false。
所以
string strArr[] = { "西瓜","西瓜","桃子","桃子","柚子","桃子","黄瓜","西瓜","西瓜" };
map<string, int> countMap;
//2、
for (auto& str : strArr)
{
// 1.如果水果没在map中:则插入成功
//2.如果水果已经在map中,插入失败,通过返回值拿到水果所在的节点迭代器,++次数
pair<map<string, int>::iterator, bool> ret = countMap.insert(make_pair(str,1));// insert的返回值
if (ret.second == false)
{
ret.first->second++;
}
}
for (auto& pair : countMap)
{
cout << pair.first << ":" << pair.second << endl;
}
第三种方法:
string strArr[] = { "西瓜","西瓜","桃子","桃子","柚子","桃子","黄瓜","西瓜","西瓜" };
map<string, int> countMap;
//3、
for (auto& str : strArr)
{
// 1、如果水果不在map中,则operator[]会插入pair<str,0(即是int() )>,返回映射对象(次数)的引用进行了++
// 2、如果水果在map中,则operator[]返回水果对应的映射对象(次数)的引用,对它++。.
countMap[str]++;
}
for (auto& pair : countMap)
{
cout << pair.first << ":" << pair.second << endl;
}
更加简洁,为什么可以这样呢?
看完文档发现使用 [ ] 相当于调用 :
( *( (this->insert(make_pair(k,mapped_type()) ) ).first))
即
countMap[ str ] 时,都会先调用 insert, 不管 str 有没有插进去,都会返回对应str的迭代器
还可以这样用
countMap["香蕉"]; // 插入
countMap["香蕉"] = 1; // 修改
cout << countMap["香蕉"] << endl; //查找
countMap["哈密瓜"] = 5; // 插入 + 修改
multimap
multimap的使用与map一致,最大区别:map去重+排序,multimap元素可重复+排序
multimap查找的元素是相同元素的时候,返回的是遍历中序遇到相同的第一个元素的迭代器
multimap是不支持[]重载的
但也可以实现统计水果出现次数与第一种方法一样
头文件与map一样
#include<map>