1.引言
不同于之前介绍过的string、vector、list、deque、等容器,它们在逻辑结构上是线性的,并且两个位置存储的值之间没有紧密的关联,比如说交换或修改一下,还是原来的容器结构;今天要介绍的map和set在逻辑结构上是非线性的。容器可以分为两种:序列式容器和关联式容器
- 序列式容器:序列式容器也叫做顺序容器,其在逻辑结构上是线性的,两个位置存储的值之间一般没有紧密的关联关系,比如说交换一下或修改某个值,不影响容器,还是序列式的容器;序列式容器中的元素是按他们在容器中的存储位置来顺序保存和访问的。如string、vector、list、deque等。
- 关联式容器:关联式容器不同于序列式容器,关联式容器逻辑结构通常是非线性结构,两个位置有紧密的关联关系,交换或者修改(修改对于key/value的场景可以修改value)都会破坏原来的存储结构;关联式容器中的元素是按关键字来保存和访问的。如map/set和unordered_map和unordered_set。
2.set的使用
2.1set类
- T就是存储的类型
- Compare是仿函数,默认是小于比较,如果不支持或者说想自己控制比较的逻辑,可以通过仿函传给第二个参数
- set底层存储数据的内存是从空间配置器申请的,需要的时候可以自己实现内存池传递给第三个参数
template < class T, // set::key_type/value_typeclass Compare = less<T>, // set::key_compare/value_compareclass Alloc = allocator<T> // set::allocator_type> class set;
set底层是用红黑树实现的,增删查效率是O(logN),迭代器遍历走的是搜索树的中序,所以是有序的;并且set是不支持插入相同值的,也就是说set中的值都是唯一的,multiset允许插入相同值。
2.2set的构造和迭代器
set的迭代器支持正向和反向迭代遍历,遍历默认按照升序的顺序,因为底层是二叉搜索树,迭代器遍历走的是中序;支持使用范围for,要注意:set的iterator和const_iterator都不支持迭代器修改数据,修改了就破坏了底层的结构了。
set<int> s0 = { 6,2,4,5,1,9 };// initializer_list构造vector<string> v = { "hello", "welcome" ,"to" ,"programing" ,"world" };set<string> s1(v.begin(), v.end());// 迭代器区间构造set<int>::iterator i0 = s0.begin();while (i0 != s0.end()){// error C3892: “i0”: 不能给常量赋值//*i0 = 1;cout << *i0 << " ";i0++;}cout << "\n";for (auto a : s1){cout << a << " ";}cout << "\n";
2.3set的增删查
// 单个数据插入,如果已经存在则插入失败
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的个数
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;
实列:
set<int,greater<int>> s = { 1,6,2,8,4};s.insert(3);s.insert({ 5,7,9,2});// 2插入失败for (auto a : s){cout << a << " ";}cout << "\n";int x;cin >> x;int ret_erase = s.erase(x);if (ret_erase == 0){cout << "无此数据" << endl;}else{cout << "删除成功" << endl;for (auto a : s){cout << a << " ";}cout << "\n";}cin >> x;set<int>::iterator ret_find = s.find(x);if (ret_find != s.end())// 不加以检查直接删除的话,遇到不存在的值可能会导致程序崩溃{s.erase(ret_find);cout << "删除成功" << endl;for (auto a : s){cout << a << " ";}cout << "\n";}elsecout << "无此数据" << endl;set<string> sr= { "abandon","apple","cat" ,"dog" ,"erase","insert" ,"sotr" };string str;cin >> str;//auto ret = find(sr.begin(),sr.end(),str); // 算法库的查找是一个一个查找,时间复杂度是O(N)auto ret = sr.find(str);// set自己的查找是通过比较查找,时间复杂度是O(logN)if (ret != sr.end())cout << str << "在" << endl;elsecout << str << "不在" << endl;// 还可以通过count实现间接查找(str不在的话就是0个)cin >> str;if (sr.count(str))cout << str << "在" << endl;elsecout << str << "不在" << endl;
2.4set的lower_bound和upper_bound
srand(time(nullptr));set<int> s;s.insert({ 15,20,21 });for (int i = 0; i < 10; i++){s.insert(rand() % 21 + 1);}for (auto a : s){cout << a << " ";}cout << "\n";// [lower,upper)// lower是返回第一个大于等于15的迭代器// upper是返回第一个大于20的迭代器set<int>::iterator lower = s.lower_bound(15);set<int>::iterator upper = s.upper_bound(20);// 在C++中传递迭代器的时候都是左闭右开,所以这里会把 [15,21) 的都删除s.erase(lower, upper);for (auto a : s){cout << a << " ";}cout << "\n";
2.5multiset和set
multiset<int> s = { 3,5,2,0,3,2,0,1,2,1,0,2 };// multiset不会去重,但是还是能排序for (auto a : s){cout << a << " ";}cout << "\n";// 不同于set,x可能会存在多个,multiset.find查找中序的第一个xint x;cin >> x;auto ret = s.find(x);while (ret != s.end() && *ret == x){cout << *ret << " ";ret++;}cout << "\n";// multiset会返回x的实际个数cout << "有" << s.count(x) << "个" << x << endl;// multiset会删除所有的xs.erase(x);for (auto a : s){cout << a << " ";}cout << "\n";
3.map的使用
3.1map类
map是key/value的场景,声明如下:
- Key就是底层key关键字类型,T是底层value的类型
- 同样的,默认要求支持小于比较,如果不支持或者说想自己控制比较的逻辑,可以通过仿函传给第二个参数
- map的底层存储数据结构的内存也是从空间配置器申请的
template < class Key, // map::key_typeclass T, // map::mapped_typeclass Compare = less<Key>, // map::key_compareclass Alloc = allocator<pair<const Key,T> > // map::allocator_type> class map;
map底层是红黑树,增删查改效率是O(logN),迭代器走的也是中序,是按照key的顺序遍历的;同样也有multimap,才能插入相等值。
3.2pair类型
map底层的红黑树中的结点数据是一个个的pair<Key,T>,所以先来看一下pair,结构如下:
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));}
pair的简单使用:
pair<string, string> p0("hello", "world");cout << p0.first << " " << p0.second << endl;
3.3map的构造
// empty (1) 无参默认构造
explicit map(const key_compare& comp = key_compare(),const allocator_type& alloc = allocator_type());
// 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());
// 迭代器是一个双向迭代器
iterator->a bidirectional iterator to const value_type
// 正向迭代器
iterator begin();
iterator end();
// 反向迭代器
reverse_iterator rbegin();
reverse_iterator rend();
3.4map的增删查
Member types
key_type -> The first template parameter (Key)
mapped_type -> The second template parameter (T)
value_type -> pair<const key_type,mapped_type>
// 单个数据插入,如果已经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<string, string> dict = { {"key","钥匙"},{"lamp","灯"},{"mountain","山"},{"notebook","笔记本"},{"ocean","海洋"},{"pen","笔"}};auto it = dict.begin();while (it != dict.end()){cout << it->first << "的中文是:" << it->second << endl;// 实际上是下面通过调用operator->的返回值当作指针使用// 第一个->是迭代器运算符重载,返回pair*,第二个箭头是结构指针解引用取pair数据//cout << it.operator->()->first << "的中文是:" << it.operator->()->second << endl;++it;}cout << "=========================" << endl;// 最后一种最方便pair<string, string> p = { "book","书" };dict.insert(p);//插入一个pairdict.insert(pair<string, string>("mirror", "镜子"));//匿名对象pairdict.insert(make_pair("flower", "花朵"));//通过make_pair插入dict.insert({ "umbrella", "伞" });//隐式类型类型转换插入for (auto e : dict){cout << e.first << "的中文是:" << e.second << endl;}string str;while (cin >> str){auto ret = dict.find(str);if (ret!= dict.end())cout << str << "的中文是" << ret->second << endl;elsecout << "未收录此单词" << endl;}
3.5map的修改
map支持修改mapped_value数据(也就是value),不支持修改key数据。
map的修改方式有两种,一种是iterator遍历或者查找时返回所在的iterator修改,另一种也是非常重要的一个修改接口就是operator[ ],operator[ ]不同于以前所学习的,不仅仅是简单的访问,map的operator[ ]不仅仅支持修改,还支持插入数据和查找数据,是一个多功能复合接口。
map对find的声明如下:
// map中的成员变量
Member types
key_type->The first template parameter(Key)
mapped_type->The second template parameter(T)
value_type->pair<const key_type, mapped_type>iterator find(const key_type& k);
pair<iterator, bool> insert(const value_type& val);
mapped_type& operator[] (const key_type& k)
{pair<iterator, bool> ret = insert({ k, mapped_type() });iterator it = ret.first;return it->second;
}
1、find查找k的时候,返回k所在的迭代器,没有找到就返回end(),如果找到了可以通过iterator修改对应的mapped_type值。
2、insert插入一个pair<key,T>对象,返回的也是一个pair对象,但是返回对象分如下情况
- 如果key已经在map中,插入失败,first是key所在结点的迭代器,second是false
- 如果key不在map中,插入成功,first是新插入的key所在的结点的迭代器,second是true
也就是说,无论插入成功还是失败,返回pair<iterator,bool>对象的first都会指向key所在的迭代器(只是一个是已存在的,另一个是新插入的)
3、operator[k]的时候,也分为两种情况
- k不在map中,insert会插入k和mapped_type默认值,同时返回结点中存储的mapped_type值的引用,这样就能通过引用修改值,所以说具备插入+修改的功能
- k在map中,insert会插入失败,但是insert返回的pair对象中的first是指向key结点的迭代器,返回值同时返回[ ]返回结点中存储的mapped_type的值的引用,所以具备查找+修改的功能(operator[k]的时候,要注意有副作用:比如说不在map中会导致pair的second是mapped_value默认值)
3.6map的迭代器和operator[ ]
string array[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜","苹果", "香蕉", "苹果", "香蕉" , "香蕉", "苹果", "香蕉" , "香蕉", "苹果", "香蕉" };
map<string, int>count;
for (const auto& e : array)
{auto ret = count.find(e);// 通过find返回的iterator实现计数if (ret == count.end()){count.insert({ e,1 });}else{ret->second++;}
}
for (const auto& e : array)
{// 通过[]巧妙统计次数count[e]++;
}
for (const auto e : count)
{cout << e.first << ":" << e.second << endl;
}
cout << "\n";