摘要:set,multiset,map,multimap
前言
1. 容器
- 序列式容器:只存储数据,数据之间无关联关系。例如,vector、list、deque、……
- 关联式容器:不仅存储数据,且数据之间有关联关系。例如,map、set、…
2. 键对值
用来表示具有一一对应关系的一种结构,该结构中一般只包含两个成员变量 key 和 value,key 代表键值,value 表示与 key 对应的信息。如:现在要建立一个英汉互译的字典,那该字典中必然有英文单词与其对应的中文含义,而且,英文单词与其中文含义是一一对应的关系,即通过某英文单词,在词典中找到与其对应的中文含义。
SGI-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){} };
本文将介绍 set、multiset、map、multimap 。
1. set
set - C++ Reference (cplusplus.com)
set 是一个 key 模型的搜索二叉树。因此,set 对于数据具有排序(搜索二叉树的特点) + 去重(不允许重复值)的作用。
注意:set 内存储的数据不可被修改(BST都不支持修改key)
1)insert
//single element (1)
pair<iterator,bool> insert (const value_type& val);//with hint (2)
iterator insert (iterator position, const value_type& val);//range (3)
template <class InputIterator>
void insert (InputIterator first, InputIterator last);
通过文档可以看到 insert 有 3 个重载。
当使用 pair<iterator,bool> insert (const value_type& val); 插入指定值的结点,函数返回的是一个 pair<iterator,bool> 的对象。该 pair 的 first 为迭代器,指向新插入的元素(插入成功 second 值为 true)或已有的等效元素(插入失败 second 值为 false)
使用示例:
#include<set>
#include<map>
#include<vector>
#include<iostream>
void test_set()
{
std::set<int> s1;
std::vector<int> vt = { 4,2,6,1,8,9,5,3,2,4 };
for (auto e : vt)
{
auto tmp = s1.insert(e);
std::cout << e << " " << tmp.second << std::endl;
}
}
int main()
{
test_set();
return 0;
}
2)erase & find & count
(1) | void erase (iterator position); ps. position 必须是存在的,否则会报错 |
---|---|
(2) | size_type erase (const value_type& val); ps.val 是否 是 存在的值都可以 |
(3) | void erase (iterator first, iterator last); |
由于 iterator position 不存在而报错的举例:
void test_set()
{
std::set<int> s1;
std::vector<int> vt = { 4,2,6,1,8,9,5,3,2,4 };
for (auto e : vt)
{
auto tmp = s1.insert(e);
std::cout << e << " " << tmp.second << std::endl;
}
std::set<int>::iterator it2 = s1.find(7);//没找都会返回s1.end()
s1.erase(it2);//这样就会导致删除错误
//因此,如果通过find函数找到结点再erase,那么使用erase之间要进行判断
}
- find 与 count 的区别:
iterator find (const value_type& val) const; 👉 find 返回类型是 iterator,判断是否 find 成功需要取到这个返回值,并判断是否与 std::set::end 相等。即 find 是都成功判断起来比较麻烦。
size_type count (const value_type& val) const; 👉 (这里 size_type 参看文档可知是 size_t。)count 返回类型为 size_t,成功返回1,失败返回0,比较方便判断。
3)lower_bound & upper_bound
iterator lower_bound (const value_type& val) const;
Return value
An iterator to the the first element in the container which is not considered to go before* val, or set::end if all elements are considered to go before val.
Member types iterator and const_iterator are bidirectional iterator types pointing to elements.*在这句话中,"go before" 表示在顺序上位于指定值 val 之前的意思。换句话说,如果元素被认为在指定值 val 之前,即比 val 小或者按照容器自身的排序规则排在 val 之前,那么这些元素就被视为"go before val"。
iterator upper_bound (const value_type& val) const;
Return value
An iterator to the the first element in the container which is considered to go after* val, or set::end if no elements are considered to go after val.
Member types iterator and const_iterator are bidirectional iterator types pointing to elements.*类比上面的 "go before",这句话中的 "go after" 就很好理解了。
void test_set2()
{
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); // ^
// itloew将指向第一个不位于30之间的元素,即>=30,即30
itup = myset.upper_bound(60); // ^
// itup将指向第一个位于60之后的元素,即>60,即70
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;
}
对于上述代码,itlow = myset.lower_bound(30); == itlow = myset.lower_bound(25); 即这两句话得到的itlow是相等的。>=30即30;>=25即30.
而对于 myset.erase(itlow, itup); 即删除 [ 30, 60 ] 这个闭区间的所有值。
4)equal_range
pair<iterator,iterator> equal_range (const value_type& val) const;Return value
The function returns a pair, whose member pair::first is the lower bound of the range (the same as lower_bound), and pair::second is the upper bound (the same as upper_bound).
如上,即 pair::first 为 按顺序第一个 >= val 的结点的 iterator,pair::second 为 按顺序第一个 > val 的结点的 iterator.
(ps.这个函数对 set 容器来说用处不大)
2. multiset
multiset - C++ Reference (cplusplus.com)
相比与 set,multiset 允许重复值。因此,multiset 对于数据仅具有排序的作用。multiset 与 set 很相似,以下简化表达,且只讲解一些存在差异的地方。
①对于重复值,插入到右树还是左树都可以。因为插入之后导致搜索二叉树不平衡会旋转,图例如下;
②如果有重复值,find(val) 会返回中序遍历序列的第一个值为 val 的 iterator;
③equal_range(val) → 返回值 pair::first >= val;pair::second > val → [ first, second ) 在这个区间内,则找到了所有重复的 val 值结点;
④count(val) 会统计值为 val 的结点个数;
⑤erase(val) 删除所有值为 val 的结点,并返回删除结点的个数(return size_t)。
3. map
map - C++ Reference (cplusplus.com)
map 是一个 KV 模型的搜索二叉树。KV 即 key and value,value 为 key 的映射值。key 不可被修改,value 可被修改。根据下表,KV的 K 即key_type,V即 mapped_type。KV 整体即 value_type。
(前面自己实现的 KV 模型的BST的 key 与 value 是分开的,而 map 的处理是将两者融合进 pair对象中。)
member type | definition |
---|---|
key_type | The first template parameter (Key) |
mapped_type | The second template parameter (T) |
value_type | pair<const key_type,mapped_type> |
1)insert
single element (1) | pair<iterator,bool> insert (const value_type& val); |
---|---|
with hint (2) | iterator insert (iterator position, const value_type& val); |
range (3) | template <class InputIterator> void insert (InputIterator first, InputIterator last); |
value_type → pair<const key_type,mapped_type> → pair<const Key,T>
如下代码,Key → string,T → string.
void test_map()
{
std::map<std::string, std::string> dict;
dict.insert(std::pair<std::string, std::string>("sort", "排序"));//方式一:匿名对象
std::pair<std::string, std::string> pr("test", "测试");//方式二
dict.insert(pr);
pr = { "cruel","残忍的" };
dict.insert(pr);
auto it = dict.begin();
while (it != dict.end())
{
std::cout << it->first << ":" << it->second << " ";
++it;
}
std::cout << std::endl;
}
-
std::make_pair
对于 map 的 insert ,以下介绍一种更常用的写法。
std::make_pair:(function)<utility>
template <class T1, class T2> pair<T1,T2> make_pair (T1 x, T2 y);
//插入更常用的写法↓ 方式三:make_pair()
dict.insert(std::make_pair("autumn","秋天"));
2)用 map 统计次(个)数
思路:对于一组给定的数据,实例化出相应的 map 对象,在 map<Type, int> 中依次查找给定的数据,该数据未在其(map实例化的对象)中则插入数据,若有则对其数量(int)++.
示例代码如下:
void test_Count()
{
std::vector<std::string> vstr = { "apple","banana","grap","apple","cherry","cherry","grap","cherry","lemon","grap" };
std::map<std::string, int> Count;
for (auto str : vstr)
{
if (Count.find(str) == Count.end())//没找到
Count.insert(std::make_pair(str, 1));
else//找到了
++Count.find(str)->second;
}
auto it = Count.begin();
while (it != Count.end())
{
std::cout << it->first << ":" << it->second << std::endl;
++it;
}
}
-
⭐ std::map::operator[]
mapped_type& operator[] (const key_type& k);
Access element
If k matches the key of an element in the container, the function returns a reference to its mapped value.
If k does not match the key of any element in the container, the function inserts a new element with that key and returns a reference to its mapped value. Notice that this always increases the container size by one, even if no mapped value is assigned to the element (the element is constructed using its default constructor).
A similar member function, map::at, has the same behavior when an element with the key exists, but throws an exception when it does not.
A call to this function is equivalent to:(*((this->insert(make_pair(k,mapped_type()))).first)).second
注意该函数的返回值:
Member type mapped_type is the type of the mapped values in the container, defined in map as an alias of its second template parameter (T)
分析:
- 如上,通过阅读文档可知,[key] → 该操作有两种情况:①key已经存在,则返回key的映射值(mapped value)的引用;②key不存在,则插入key,并返回这个key的映射值的引用。
- A call to this function is equivalent to: (*((this->insert(make_pair( k,mapped_type() ))).first)).second
逐步拆解这句代码👇
通过使用这个函数,下面进一步优化上面统计次(个)数的代码:
void test_Count2()
{
std::vector<std::string> vstr = { "apple","banana","grap","apple","cherry","cherry","grap","cherry","lemon","grap" };
std::map<std::string, int> Count;
for (auto str : vstr)
{
++Count[str];//⭐
}
auto it = Count.begin();
while (it != Count.end())
{
std::cout << it->first << ":" << it->second << std::endl;
++it;
}
}
4. multimap
multimap - C++ Reference (cplusplus.com)
- 相比于 map,multimap 允许重复值。
- 不支持 operator[]
END