C++基础(10. map_set 的使用)
序列式容器和关联式容器:
序列式容器:string、vector、list、deque、array、forward_list等
逻辑结构为线性序列的数据结构,两个位置存储的值之间⼀般没有紧密的关联关系,⽐如交换⼀下,他依旧是序列式容器。顺序容器中的元素是按他们在容器中的存储位置来顺序保存和访问的。
关联式容器:有map/set系列和unordered_map/unordered_set系列
与序列式容器不同的是,关联式容器逻辑结构通常是⾮线性结构, 两个位置有紧密的关联关系,交换⼀下,他的存储结构就被破坏了。顺序容器中的元素是按关键字来保存和访问的。
本章节讲解的 map 和 set 底层是红⿊树,红⿊树是⼀颗平衡⼆叉搜索树。
set是key搜索场景的结构,
map是key/value搜索场景的结构。
set系列的使用:
set和multiset参考⽂档:
https://legacy.cplusplus.com/reference/set/
set类的介绍:
template < class T, // set::key_type/value_type
class Compare = less<T>, // set::key_compare/value_compare
class Alloc = allocator<T> // set::allocator_type
> class set;
- set的声明如上,T就是set底层关键字的类型
- set默认要求T⽀持⼩于⽐较,如果不⽀持或者想按⾃⼰的需求⾛可以⾃⾏实现仿函数传给第⼆个模版参数
- set底层存储数据的内存是从空间配置器申请的,如果需要可以⾃⼰实现内存池,传给第三个参数。
- ⼀般情况下,我们都不需要传后两个模版参数。
- set底层是⽤红⿊树实现,增删查效率是O(logN) ,迭代器遍历是⾛的搜索树的中序,所以是有序的。
- 前⾯部分我们已经学习了vector/list等容器的使⽤,STL容器接⼝设计,⾼度相似。
功能:
- 唯一性:
set
中不允许重复元素。 - 自动排序:元素会根据升序自动排序。
- 高效操作:插入、删除和查找操作的时间复杂度为 O(logn)O(logn)。
set
的基本使用:
set的构造:
set的构造我们关注以下⼏个接⼝即可。
- set的⽀持正向和反向迭代遍历,遍历默认按升序顺序
- 因为底层是⼆叉搜索树,迭代器遍历⾛的中序
- ⽀持迭代器就意味着⽀持范围for
- set的iterator和const_iterator都不⽀持迭代器修改数据,修改关键字数据,破坏了底层搜索树的结构。
set<int> mySet; // 创建一个空的set
set<int> mySet2 = {1, 2, 3, 4}; // 使用初始化列表创建set
set (const set& x); // 拷⻉构造
set (InputIterator first, InputIterator last,
const key_compare& comp = key_compare(),
const allocator_type& = allocator_type() ); //迭代器区间构造
set的增删查
// 单个数据插⼊,如果已经存在则插⼊失败
pair<iterator,bool>
insert(const value_type& val);
// 列表插⼊,已经在容器中存在的值不会插⼊
void insert (initializer_list<value_type> il);
// 迭代器区间插⼊,已经在容器中存在的值不会插⼊
template <class InputIterator>
void insert (InputIterator first, InputIterator last);
// 查找val,返回val所在的迭代器,没有找到返回end()
iterator find (const value_type& val);
// 查找val,返回Val的个数(如果是set那么只为0或1)
size_type count (const value_type& val) const;
// 删除⼀个迭代器位置的值
iterator erase (const_iterator position);
// 删除val,val不存在返回0,存在返回1
size_type erase (const value_type& val);
// 删除⼀段迭代器区间的值
iterator erase (const_iterator first, const_iterator last);
// 返回⼤于等val位置的迭代器
iterator lower_bound (const value_type& val) const;
// 返回⼤于val位置的迭代器
iterator upper_bound (const value_type& val) const;
迭代器:iterator
// 正向迭代器
iterator begin();
iterator end();
// 反向迭代器
reverse_iterator rbegin();
reverse_iterator rend();
检查元素是否存在:count()
由于集容器中的所有元素都是唯一的,因此该函数只能返回 1(如果找到元素)或 0(没找到)。
if (mySet.count(5) > 0)
{
std::cout << "5 is in the set." << std::endl;
}
遍历元素:范围for
for (const auto it: mySet)
{
std::cout << it << " ";
}
大小和清空:size(),clear()
std::cout << "Size: " << mySet.size() << std::endl;
mySet.clear(); // 清空set
自定义排序:
可以通过提供自定义比较函数来改变元素的排序方式。
- way1:
struct CustomCompare
{
bool operator()(int a, int b) const
{
return a > b; // 降序排列
}
};
set<int, CustomCompare> mySet; // 创建一个降序排列的set
- way2:
使用greater<>, less<>
set<int, greater<int>> mySet; // 创建一个降序排列的set
set<int, less<int>> mySet; // 创建一个升序排列的set
multiset
的基本使用:
- multiset的使用方法与set基本一致,与set的主要区别点在于multiset⽀持值冗余
- multiset允许重复:
multiset
可以存储重复元素。(不支持去重)
map系列的使用:
map和multimap参考⽂档:
https://legacy.cplusplus.com/reference/map/
map类的介绍:
template < class Key, // map::key_type
class T, // map::mapped_type
class Compare = less<Key>, // map::key_compare
class Alloc = allocator<pair<const Key,T> > //map::allocator_type
> class map;
- map的声明如上,Key就是map底层关键字的类型,T是map底层value的类型
- set默认要求Key⽀持⼩于⽐较,如果不⽀持或者需要的话可以⾃⾏实现仿函数传给第⼆个模版参数
- map底层存储数据的内存是从空间配置器申请的。
- ⼀般情况下,我们都不需要传后两个模版参数。
- map底层是⽤红⿊树实现,增删查改效率是 O(logN) ,迭代器遍历是⾛的中序,所以是按key有序顺序遍历的。
pair类型介绍:
map底层的红⿊树节点中的数据,使⽤pair<Key, T>存储键值对数据。
typedef pair<const Key, T> value_type;
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)
{}
template<class U, class V>
pair (const pair<U,V>& pr): first(pr.first), second(pr.second)
{}
};
template <class T1,class T2>
inline pair<T1,T2> make_pair (T1 x, T2 y)
{
return ( pair<T1,T2>(x,y) );
}
功能
- 唯一键:
map
中的每个键都是唯一的。 - 自动排序:键会根据升序自动排序。
- 高效操作:插入、删除和查找操作的时间复杂度为 O(logn)O(logn)。
map的基本使用:
map的构造:
map的构造我们关注以下⼏个接⼝即可。
- map的⽀持正向和反向迭代遍历,遍历默认按key的升序顺序
- 因为底层是⼆叉搜索树,迭代器遍历⾛的中序
- ⽀持迭代器就意味着⽀持范围for
- map⽀持修改value数据,不⽀持修改key数据,修改关键字数据,破坏了底层搜索树的结构
// empty (1) ⽆参默认构造
explicit map (const key_compare& comp = key_compare(),
const allocator_type& alloc = allocator_type());
map<int, std::string> myMap; // 创建一个空的map
// range (2) 迭代器区间构造
template <class InputIterator>
map (InputIterator first, InputIterator last,
const key_compare& comp = key_compare(),
const allocator_type& = allocator_type());
// copy (3) 拷⻉构造
map (const map& x);
// initializer list (5) initializer 列表构造
map (initializer_list<value_type> il,
const key_compare& comp = key_compare(),
const allocator_type& alloc = allocator_type());
map<int, std::string> myMap2 = {{1, "one"}, {2, "two"}, {3, "three"}}; // 使用初始化列表创建map
map的增删查:
- map增接⼝,插⼊的pair键值对数据,跟set所有不同
- 查和删的接⼝只⽤关键字key跟set是完全类似的,不过find返回iterator,不仅仅可以确认key在不在,还找到key映射的value,同时通过迭代还可以修改value
// 单个数据插⼊,如果已经key存在则插⼊失败,key存在相等value不相等也会插⼊失败
pair<iterator,bool> insert (const value_type& val);
// 列表插⼊,已经在容器中存在的值不会插⼊
void insert (initializer_list<value_type> il);
// 迭代器区间插⼊,已经在容器中存在的值不会插⼊
template <class InputIterator>
void insert (InputIterator first, InputIterator last);
// 查找k,返回k所在的迭代器,没有找到返回end()
iterator find (const key_type& k);
// 查找k,返回k的个数
size_type count (const key_type& k) const;
// 删除⼀个迭代器位置的值
iterator erase (const_iterator position);
// 删除k,k存在返回0,存在返回1
size_type erase (const key_type& k);
// 删除⼀段迭代器区间的值
iterator erase (const_iterator first, const_iterator last);
// 返回⼤于等k位置的迭代器
iterator lower_bound (const key_type& k);
// 返回⼤于k位置的迭代器
const_iterator lower_bound (const key_type& k) const;
map的数据修改:
- map⽀持修改mapped_type 数据,不⽀持修改key数据
- map第⼀个⽀持修改的⽅式时通过迭代器,迭代器遍历时或者find返回key所在的iterator修改,重要的修改接口operator[ ],不仅仅⽀持修改,还⽀持插⼊数据和查找数据,是⼀个多功能复合接⼝
- map这⾥把我们传统说的value值,给的是T类型,typedef为 mapped_type。⽽value_type是红⿊树结点中存储的pair键值对值。⽇常使⽤我们还是习惯将这⾥的T映射值叫做value。
// 查找k,返回k所在的迭代器,没有找到返回end(),如果找到了通过iterator可以修改key对应的
mapped_type值
iterator find (const key_type& k);
// insert插⼊⼀个pair<key, T>对象
----1、如果key已经在map中,插⼊失败,则返回⼀个pair<iterator,bool>对象,返回pair对象
first是key所在结点的迭代器,second是false
----2、如果key不在在map中,插⼊成功,则返回⼀个pair<iterator,bool>对象,返回pair对象
first是新插⼊key所在结点的迭代器,second是true
====也就是说⽆论插⼊成功还是失败,返回pair<iterator,bool>对象的first都会指向key所在的迭
代器
====那么也就意味着insert插⼊失败时充当了查找的功能,正是因为这⼀点,insert可以⽤来实现
operator[]
====需要注意的是这⾥有两个pair,不要混淆了,⼀个是map底层红⿊树节点中存的pair<key, T>,另
⼀个是insert返回值pair<iterator,bool>
myMap.insert({1, "one"}); // 插入键值对 (1, "one")
myMap[2] = "two"; // 使用下标操作符插入键值对 (2, "two")
检查元素是否存在:
if (myMap.count(2) > 0)
{
std::cout << "Key 2 exists." << std::endl; // 检查键为2的元素是否存在
}
遍历元素:
for (const auto& pair : myMap)
{
std::cout << pair.first << ": " << pair.second << std::endl; // 输出所有键值对
}
大小和清空:
std::cout << "Size: " << myMap.size() << std::endl; // 输出map的大小
myMap.clear(); // 清空map
multimap:
- multimap和map的使⽤基本完全类似
- 主要区别点在于multimap⽀持关键值key冗余,那么insert/find/count/erase都围绕着⽀持关键值key冗余有所差异
- 这⾥跟set和multiset完全⼀样,⽐如 find时,有多个key,返回中序第⼀个。其次就是multimap不⽀持[ ],因为⽀持key冗余,[ ]就只能⽀持插⼊了,不能⽀持修改。