详解map、set、multimap、multiset的使用

news2024/11/22 6:43:07

作者阿润菜菜
📖专栏C++


目录

  • 前言
  • set、multiset的使用
    • 1. set
    • 2. multiset
    • 3. 什么时候应该使用multiset而不是set
  • map、multimap的使用
    • 1.map
    • 2.multimap
    • 3.什么时候应该使用multimap而不是map


前言

map、set、multimap、multiset是C++ STL中的四种关联容器,它们内部都使用了红黑树这种高效的平衡检索二叉树来存储数据。它们的区别和用法如下:

  • map是一种键值对容器,它可以根据键来快速查找、插入和删除值,它的键是唯一的,不能重复。
  • multimap也是一种键值对容器,但它允许键重复,也就是说一个键可以对应多个值。
  • set是一种只存储值的容器,它可以快速查找、插入和删除值,它的值是唯一的,不能重复。在底层实际存放的是由<value, value>构成的键值对。(排序+去重)
  • multiset也是一种只存储值的容器,但它允许值重复,也就是说一个容器中可以有多个相同的值。

这四种容器都可以保证元素按照一定的顺序排列,通常是按照键或者值的升序排列。如果想要改变排序规则,可以自定义比较函数或者比较类。

这四种容器的时间复杂度如下:

  • 插入:O(log n)
  • 查找:O(log n)
  • 删除:O(log n)

如果想要更高效的时间复杂度,可以使用哈希表实现的unordered_map, unordered_set, unordered_multimap, unordered_multiset(后期介绍)。它们的时间复杂度如下:

  • 插入:O(1)期望,O(n)最坏
  • 查找:O(1)期望,O(n)最坏
  • 删除:O(1)期望,O(n)最坏
    但是哈希表实现的容器不能保证元素有序,并且需要提供合适的哈希函数和相等判断函数。

那什么是键值对呢?
键值对实际也是一个类,类里面对key和value数据进行了封装,key表示关键码,value表示与之对应的值,下面是SGI对于pair键值对结构体的定义:

struct pair{}有两个构造函数,一个是无参的利用T1和T2类型的默认构造函数初始化出来的键值对,一个是T1和T2作为参数的构造函数
另外pair还重载了两个运算符,用于键值对的等于和小于比较,小于比较时,优先比较first,如果first恰好满足x<y,则返回true。如果first之间相等,则比较失败返回false。如果first是x>y,那就继续比较second。
为了方便构造键值对,pair又另写了成员函数make_pair(),这个函数其实也是调用了构造函数,但在构造键值对的时候,省下我们自己去传T1和T2的类型了。

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 T1, class T2>
inline bool operator==(const pair<T1, T2>& x, const pair<T1, T2>& y) { 
  return x.first == y.first && x.second == y.second; 
}

template <class T1, class T2>
inline bool operator<(const pair<T1, T2>& x, const pair<T1, T2>& y) { 
  return x.first < y.first || (!(y.first < x.first) && x.second < y.second); 
}

template <class T1, class T2>
inline pair<T1, T2> make_pair(const T1& x, const T2& y) {
  return pair<T1, T2>(x, y);
}

set、multiset的使用

1. set

  • set是一种只存储值的容器,它可以快速查找、插入和删除值,它的值是唯一的,不能重复。
  • set内部使用红黑树这种高效的平衡检索二叉树来存储数据,因此它可以保证元素按照一定的顺序排列,通常是按照值的升序排列。
  • set的时间复杂度为O(log n),其中n是set中元素的个数。

所以set的特点是:不允许元素被修改,不允许元素有重复,那么它的作用就是排序+去重

链接set

set的使用方法如下:

  • 要使用set,需要添加set头文件,即#include<set>。除此之外,还需要在头文件下面加上一句:“using namespace std;”。
  • set的定义:可以使用set<类型> 变量名;来定义一个空的set对象,也可以使用set<类型> 变量名(初始值);来定义一个带有初始值的set对象。例如:
    • set<int> s1; // 定义一个空的set对象,存储int类型的元素
    • set<string> s2({"hello", "world"}); // 定义一个带有初始值的set对象,存储string类型的元素
  • set的插入:可以使用insert()方法来向set中插入一个元素,该方法返回一个pair对象,其中第一个元素是一个指向插入位置的迭代器,第二个元素是一个布尔值,表示插入是否成功。如果插入的元素已经存在于set中,则插入失败。例如:
    • s1.insert(1); // 向s1中插入1
    • s1.insert(2); // 向s1中插入2
    • auto p = s1.insert(2); // 再次向s1中插入2
    • cout << *p.first << " " << p.second << endl; // 输出 2 0,表示插入失败
  • set的删除:可以使用erase()方法来从set中删除一个元素或者一个范围内的元素,该方法返回一个整数,表示删除了多少个元素。如果删除的元素不存在于set中,则返回0。例如:
    • s2.erase("hello"); // 从s2中删除"hello"
    • s2.erase("hi"); // 从s2中删除"hi"
    • cout << s2.erase("world") << endl; // 输出 1,表示删除成功
    • cout << s2.erase("hi") << endl; // 输出 0,表示删除失败
  • set的查找:可以使用find()方法来查找set中是否存在某个元素,该方法返回一个指向找到元素的迭代器,如果没有找到,则返回end()。也可以使用count()方法来统计set中某个元素出现的次数,该方法返回一个整数,表示出现的次数。由于set中不允许重复元素,所以该方法只能返回0或者1。例如:
    • auto it = s1.find(1); // 查找s1中是否有1
    • if (it != s1.end()) cout << *it << endl; // 如果找到了,则输出1
    • cout << s1.count(2) << endl; // 输出 1,表示s1中有2
    • cout << s1.count(3) << endl; // 输出 0,表示s1中没有3
  • set的遍历:可以使用迭代器来遍历set中的所有元素,也可以使用范围for循环来遍历。由于set是有序的,所以遍历时会按照从小到大或者从大到小(取决于比较函数)的顺序输出元素。例如:
    • for (auto it = s1.begin(); it != s1.end(); ++it) cout << *it << " "; // 使用迭代器遍历s1,并输出每个元素
    • for (auto x : s2) cout << x << " "; // 使用范围for循环遍历s2,并输出每个元素
      代码示例:
#include <iostream>
#include <set>
using namespace std;

int main() {
  set<int> s1; // 定义一个空的set对象,存储int类型的元素
  set<string> s2({"hello", "world"}); // 定义一个带有初始值的set对象,存储string类型的元素

  s1.insert(1); // 向s1中插入1
  s1.insert(2); // 向s1中插入2
  auto p = s1.insert(2); // 再次向s1中插入2
  cout << *p.first << " " << p.second << endl; // 输出 2 0,表示插入失败

  s2.erase("hello"); // 从s2中删除"hello"
  s2.erase("hi"); // 从s2中删除"hi"
  cout << s2.erase("world") << endl; // 输出 1,表示删除成功
  cout << s2.erase("hi") << endl; // 输出 0,表示删除失败

  auto it = s1.find(1); // 查找s1中是否有1
  if (it != s1.end()) cout << *it << endl; // 如果找到了,则输出1
  cout << s1.count(2) << endl; // 输出 1,表示s1中有2
  cout << s1.count(3) << endl; // 输出 0,表示s1中没有3

  for (auto it = s1.begin(); it != s1.end(); ++it) cout << *it << " "; // 使用迭代器遍历s1,并输出每个元素
  cout << endl;
  
  for (auto x : s2) cout << x << " "; // 使用范围for循环遍历s2,并输出每个元素
  cout << endl;

  return 0;
}

2. multiset

  • multiset是一种只存储值的容器,它可以快速查找、插入和删除值,它允许值重复,也就是说一个容器中可以有多个相同的值。
  • multiset内部使用红黑树这种高效的平衡检索二叉树来存储数据,因此它可以保证元素按照一定的顺序排列,通常是按照值的升序排列。
  • multiset的时间复杂度为O(log n),其中n是multiset中元素的个数。

multiset相比于set,它可以存在重复的元素

multiset的使用方法和set基本相同:

  • 要使用multiset,需要添加set头文件,即#include<set>。除此之外,还需要在头文件下面加上一句:“using namespace std;”。
  • multiset的定义:可以使用multiset<类型> 变量名;来定义一个空的multiset对象,也可以使用multiset<类型> 变量名(初始值);来定义一个带有初始值的multiset对象。也可以指定一个比较函数对象来自定义元素的排序规则。例如:
    • multiset<int> ms1; // 定义一个空的multiset对象,存储int类型的元素,按照默认的升序排列
    • multiset<string> ms2({"hello", "world"}); // 定义一个带有初始值的multiset对象,存储string类型的元素,按照默认的升序排列
    • multiset<int, greater<int>> ms3; // 定义一个空的multiset对象,存储int类型的元素,按照降序排列
  • multiset的插入:可以使用insert()方法或者emplace()方法(C++11)来向multiset中插入一个元素,这两个方法都返回一个指向插入位置的迭代器。如果插入的元素已经存在于multiset中,则插入到已有元素之后。也可以使用emplace_hint()方法(C++11)来向multiset中插入一个元素,并提供一个提示位置作为参数,该方法返回一个指向插入位置的迭代器。如果提示位置是正确的或者接近正确的,则该方法比insert()或者emplace()更高效。例如:
    • ms1.insert(1); // 向ms1中插入1
    • ms1.insert(2); // 向ms1中插入2
    • ms1.insert(2); // 再次向ms1中插入2
    • auto it = ms1.insert(3); // 向ms1中插入3,并获取插入位置
    • cout << *it << endl; // 输出 3
    • ms2.emplace("hi"); // 向ms2中原位构造"hi"
    • ms2.emplace_hint(ms2.begin(), "bye"); // 向ms2中原位构造"bye",并提供一个提示位置
  • multiset的删除:可以使用erase()方法来从multiset中删除一个元素或者一个范围内的元素,该方法返回一个整数,表示删除了多少个元素。如果删除的元素不存在于multiset中,则返回0。也可以使用clear()方法来清空multiset中的所有元素。例如:
    • ms2.erase("hello"); // 从ms2中删除"hello"
    • ms2.erase("hi"); // 从ms2中删除"hi"
    • cout << ms2.erase("world") << endl; // 输出 1,表示删除成功
    • cout << ms2.erase("hi") << endl; // 输出 0,表示删除失败
    • ms2.clear(); // 清空ms2中的所有元素
  • multiset的查找:可以使用find()方法来查找multiset中是否存在某个元素,该方法返回一个指向找到元素的迭代器,如果没有找到,则返回end()。也可以使用count()方法来统计multiset中某个元素出现的次数,该方法返回一个整数,表示出现的次数。还可以使用lower_bound()方法和upper_bound()方法来获取某个元素在multiset中的边界位置,或者使用equal_range()方法来获取某个元素在multiset中的范围。例如:
    • auto it = ms1.find(1); // 查找ms1中是否有1
    • if (it != ms1.end()) cout << *it << endl; // 如果找到了,则输出1
    • cout << ms1.count(2) << endl; // 输出 2,表示ms1中有两个2
    • cout << ms1.count(3) << endl; // 输出 1,表示ms1中有一个3
    • auto lb = ms1.lower_bound(2); // 获取2在ms1中的下界位置
    • auto ub = ms1.upper_bound(2); // 获取2在ms1中的上界位置
    • cout << *lb << " " << *ub << endl; // 输出 2 3
    • auto p = ms1.equal_range(2); // 获取2在ms1中的范围
    • cout << *p.first << " " << *p.second << endl; // 输出 2 3
  • multiset的遍历:可以使用迭代器来遍历multiset中的所有元素,也可以使用范围for循环来遍历。由于multiset是有序的,所以遍历时会按照从小到大或者从大到小(取决于比较函数)的顺序输出元素。例如:
    • for (auto it = ms3.begin(); it != ms3.end(); ++it) cout << *it << " "; // 使用迭代器遍历ms3,并输出每个元素
    • for (auto x : ms3) cout << x << " "; // 使用范围for循环遍历ms3,并输出每个元素
      代码示例:
#include <iostream>
#include <set>
using namespace std;

int main() {
  multiset<int> ms1; // 定义一个空的multiset对象,存储int类型的元素,按照默认的升序排列
  multiset<string> ms2({"hello", "world"}); // 定义一个带有初始值的multiset对象,存储string类型的元素,按照默认的升序排列
  multiset<int, greater<int>> ms3; // 定义一个空的multiset对象,存储int类型的元素,按照降序排列

  ms1.insert(1); // 向ms1中插入1
  ms1.insert(2); // 向ms1中插入2
  ms1.insert(2); // 再次向ms1中插入2
  auto it = ms1.insert(3); // 向ms1中插入3,并获取插入位置
  cout << *it << endl; // 输出 3

  ms2.emplace("hi"); // 向ms2中原位构造"hi"
  ms2.emplace_hint(ms2.begin(), "bye"); // 向ms2中原位构造"bye",并提供一个提示位置

  ms2.erase("hello"); // 从ms2中删除"hello"
  ms2.erase("hi"); // 从ms2中删除"hi"
  cout << ms2.erase("world") << endl; // 输出 1,表示删除成功
  cout << ms2.erase("hi") << endl; // 输出 0,表示删除失败
  ms2.clear(); // 清空ms2中所有元素

  auto it = ms1.find(1); // 查找ms1中是否有1
  if (it != ms1.end()) cout << *it << endl; // 如果找到了,则输出1
  cout << ms1.count(2) << endl; // 输出 2,表示s有两个个
  cout << s.count(3) << endl; // 输出 0 表示s没有

  
  auto lb = s.lower_bound(4); 
   auto ub = s.upper_bound(6);
   cout<<*lb<<" "<<*ub<<endl;
   auto p=s.equal_range(5);
   cout<<*p.first<<" "<<*p.second<<endl;

3. 什么时候应该使用multiset而不是set

先看看二者有什么区别:

  • multiset允许元素重复,而set不允许元素重复
  • multiset的插入操作总是成功,而set的插入操作可能失败,如果插入的元素已经存在于set中
  • multiset的count()方法可能返回大于1的值,而set的count()方法只能返回0或者1。

set的作用:排序+去重,所以

  • 当你需要存储一些可以重复的元素,并且需要按照一定的顺序来访问它们时,你可以使用multiset。例如,你可以使用multiset来存储一些词汇,并且按照字典序来遍历它们,同时也可以统计每个词汇出现的次数。
  • 当你需要对一些元素进行合并或者分割操作时,你可以使用multiset。例如,你可以使用multiset来实现一个优先队列,每次从multiset中取出最小或者最大的元素,并且可以将两个或者多个元素合并成一个新的元素再插入到multiset中。
  • 当你需要使用一些set不提供的功能时,你可以使用multiset。例如,你可以使用multiset的extract()方法和merge()方法来将一个multiset中的结点转移到另一个multiset中,而不需要重新分配内存或者复制元素。

map、multimap的使用

1.map

map存储的是key和value组成的键值对元素结构体,在比较时按照key来进行比较下面源码我们可以看到key依旧是不允许被修改的,但其对应的value是可以被修改的

在这里插入图片描述
map中比较时比较的是key类型,但我们可以通过key找到value,这里多说一句,无论是map还是set,他们的迭代器走的都是中序的顺序。
在这里插入图片描述

map的使用
先说说两个注意点:

  1. map的迭代器访问这里有讲究,由于其迭代器指向的是由key和value组成的键值对,那* it该获得哪个key和value的哪个值呢?set的迭代器指向的就只有key,所以* it直接返回key值即可。
    对于map来说,*it拿到的是pair的对象,所以我们还需要再加一个.操作符才能访问pair对象里面的first和second值,但这样写起来有点麻烦,所以map的迭代器也重载了操作符,→重载的函数会返回迭代器指向对象的地址,所以还应该再加个→访问first或second才对,但是编译器在这里做了优化,省略了一个箭头。
  2. 在map这里,如果我们用语法糖遍历map时,最好采用const引用,因为map中存的是键值对pair,不用引用就会发生赋值,会调用pair的赋值重载函数,代价比较大,为了提升效率建议采用const引用。(语法糖遍历拿到的值其实就是*it,在map这里就是pair对象,键值对)

链接map
另外一种非常骚的操作就是利用map的[ ]来统计次数,在了解了insert的返回值之后,再来理解map的[ ]调用成本就会低很多了,实际上[ ]带来的作用包括插入,查找,修改的功能,直接一个[ ]运算符重载包揽map的三个重要功能。

在这里插入图片描述
在这里插入图片描述
对于map和set在插入后都会返回一个键值对:
键值对的first是迭代器,其指向的是新插入键值对,若新插入键值对的key已经存在,则返回已经存在的key对应的键值对的迭代器,这是first的变化规则。
键值对的second是bool值,如果插入的key已经存在,则bool为false,否则bool为true

在这里插入图片描述
在这里插入图片描述

map的使用方法如下:

  • 要使用map,需要添加map头文件,即#include<map>。除此之外,还需要在头文件下面加上一句:“using namespace std;”。
  • map的定义:可以使用map<键类型, 值类型> 变量名;来定义一个空的map对象,也可以使用map<键类型, 值类型> 变量名(初始值);来定义一个带有初始值的map对象。也可以指定一个比较函数对象来自定义键的排序规则。例如:
    • map<int, string> m1; // 定义一个空的map对象,存储int类型的键和string类型的值,按照默认的升序排列
    • map<int, string> m2({{1, "one"}, {2, "two"}}); // 定义一个带有初始值的map对象,存储int类型的键和string类型的值,按照默认的升序排列
    • map<int, string, greater<int>> m3; // 定义一个空的map对象,存储int类型的键和string类型的值,按照降序排列
  • map的插入:可以使用insert()方法或者emplace()方法(C++11)来向map中插入一个键值对,这两个方法都返回一个pair对象,其中first是一个指向插入位置的迭代器,second是一个布尔值,表示插入是否成功。如果插入的键已经存在于map中,则插入失败。也可以使用emplace_hint()方法(C++11)来向map中插入一个键值对,并提供一个提示位置作为参数,该方法返回一个指向插入位置的迭代器。如果提示位置是正确的或者接近正确的,则该方法比insert()或者emplace()更高效。还可以使用下标运算符[]或者at()方法来向map中插入或修改一个键值对,但这两种方式有所不同:下标运算符如果键不存在,则会创建一个新的键值对并返回默认值;而at()方法如果键不存在,则会抛出异常。例如:
    • m1.insert(pair<int, string>(1, "one")); // 向m1中插入{1, "one"}
    • m1.insert(make_pair(2, "two")); // 向m1中插入{2, "two"}
    • auto p = m1.insert({3, "three"}); // 向m1中插入{3, "three"},并获取返回值
    • cout << p.first->first << " " << p.first->second << endl; // 输出 3 three
    • cout << p.second << endl; // 输出 true
    • p = m1.insert({3, "san"}); // 尝试向m1中插入{3, "san"}
    • cout << p.second << endl; // 输出 false
    • m2.emplace(3, "three"); // 向m2中原位构造{3, "three"}
    • m2.emplace_hint(m2.begin(), 4, "four"); // 向m2中原位构造{4, "four"},并提供一个提示位置
    • m3[5] = "five"; // 向m3中插入或修改{5, "five"}
    • m3[6] = "six"; // 向m3中插入或修改{6, "six"}
    • cout << m3[7] << endl; // 输出 空字符串,并向m3中插入{7, ""}
    • cout << m3.at(8) << endl; // 抛出异常,并不会向m3中插入{8, ""}
  • map的删除:可以使用erase()方法来从map中删除一个键值对或者一个范围内的键值对,该方法返回一个整数,表示删除了多少个键值对。如果删除的键不存在于map中,则返回0。也可以使用clear()方法来清空map中的所有键值对。例如:
    • m1.erase(1); // 从m1中删除键为1的键值对
    • cout << m1.erase(4) << endl; // 输出 0,表示删除失败
    • auto it = m1.begin(); // 获取m1的起始迭代器
    • it++; // it指向第二个元素
    • m1.erase(it); // 从m1中删除it所指向的元素
    • it = m1.begin(); // 重新获取m1的起始迭代器
    • auto it2 = m1.end(); // 获取m1的结束迭代器
    • it2--; // it2指向最后一个元素
    • m1.erase(it, it2); // 从m1中删除[it, it2)范围内的元素
    • m2.clear(); // 清空m2中所有元素
  • map的查找:可以使用find()方法来查找map中是否存在某个键,并返回一个指向该键所在位置的迭代器,如果没有找到,则返回end()。也可以使用count()方法来统计map中某个键出现的次数,该方法返回一个整数,表示出现的次数(只能是0或者1)。还可以使用lower_bound()方法和upper_bound()方法来获取某个键在map中的边界位置,或者使用equal_range()方法来获取某个键在map中的范围。例如:
    • auto it = m2.find(3); // 查找m2中是否有键为3的元素
    • if (it != m2.end()) cout << it->first << " " << it->second << endl; // 如果找到了,则输出 3 three
    • cout << m2.count(4) << endl; // 输出 1,表示m2中有键为4的元素
    • cout << m2.count(5) << endl; // 输出 0,表示m2中没有键为5的元素
    • auto lb = m3.lower_bound(5); // 获取5在m3中的下界位置
    • auto ub = m3.upper_bound(5); // 获取5在m3中的上界位置
    • cout << lb->first << " " << lb->second << endl; // 输出 5 five
    • cout << ub->first << " " << ub->second << endl; // 输出 6 six
    • auto p = m3.equal_range(6); // 获取6在m3中的范围
    • cout << p.first->first << " " << p.first->second << endl; // 输出 6 six
    • cout << p.second->first << " " << p.second->second << endl; // 输出 7 空字符串

代码示例:

#include <iostream>
#include <map>
using namespace std;

int main() {
  map<int, string> m1; // 定义一个空的map对象,存储int类型的键和string类型的值,按照默认的升序排列
  map<int, string> m2({{1, "one"}, {2, "two"}}); // 定义一个带有初始值的map对象,存储int类型的键和string类型的值,按照默认的升序排列
  map<int, string, greater<int>> m3; // 定义一个空的map对象,存储int类型的键和string类型的值,按照降序排列

  m1.insert(pair<int, string>(1, "one")); // 向m1中插入{1, "one"}
  m1.insert(make_pair(2, "two")); // 向m1中插入{2,  "two"}); // 向m1中插入{2, "two"}
  auto p = m1.insert({3, "three"}); // 向m1中插入{3, "three"},并获取返回值
  cout << p.first->first << " " << p.first->second << endl; // 输出 3 three
  cout << p.second << endl; // 输出 true
  p = m1.insert({3, "san"}); // 尝试向m1中插入{3, "san"}
  cout << p.second << endl; // 输出 false
  m2.emplace(3, "three"); // 向m2中原位构造{3, "three"}
  m2.emplace_hint(m2.begin(), 4, "four"); // 向m2中原位构造{4, "four"},并提供一个提示位置
  m3[5] = "five"; // 向m3中插入或修改{5, "five"}
  m3[6] = "six"; // 向m3中插入或修改{6, "six"}
  cout << m3[7] << endl; // 输出 空字符串,并向m3中插入{7, ""}
  cout << m3.at(8) << endl; // 抛出异常,并不会向m3中插入{8, ""}

  m1.erase(1); // 从m1中删除键为1的键值对
  cout << m1.erase(4) << endl; // 输出 0,表示删除失败
  auto it = m1.begin(); // 获取m1的起始迭代器
  it++; // it指向第二个元素
  m1.erase(it); // 从m1中删除it所指向的元素
  it = m1.begin(); // 重新获取m1的起始迭代器
  auto it2 = m1.end(); // 获取m1的结束迭代器
  it2--; // it2指向最后一个元素
  m1.erase(it, it2); // 从m1中删除[it, it2)范围内的元素
  m2.clear(); // 清空m2中所有元素

  auto it = m2.find(3); // 查找m2中是否有键为3的元素
  if (it != m2.end()) cout << it->first << " " << it->second << endl; // 如果找到了,则输出 3 three
  cout << m2.count(4) << endl; // 输出 1,表示m2中有键为4的元素
  cout << m2.count(5) << endl; // 输出 0,表示m2中没有键为5的元素
  auto lb = m3.lower_bound(5); // 获取5在m3中的下界位置
  auto ub = m3.upper_bound(5); // 获取5在m3中的上界位置
  cout << lb->first << " " << lb->second << endl; // 输出 5 five
  cout << ub->first << " " << ub->second << endl; // 输出 6 six
  auto p = m3.equal_range(6); // 获取6在m3中的范围
  cout << p.first->first << " " << p.first->second << endl; // 输出 6 six
  cout << p.second->first << " " << p.second->second << endl; // 输出 7 空字符串

}

2.multimap

  • multimap允许键重复,而map不允许键重复,所以multimap是没有[ ]的。
  • multimap的插入操作总是成功,而map的插入操作可能失败,如果插入的键已经存在于map中。

multimap和map的其他用法和特点基本相同,都是基于红黑树实现的有序关联容器。

3.什么时候应该使用multimap而不是map

一般来说,应该使用multimap而不是map的情况是:

  • 需要存储多个具有相同键的键值对时,例如一个人的多个电话号码,一个词的多个同义词等。
  • 当不需要修改键值对的值时,因为multimap不支持使用下标运算符或者at()方法来修改值。
  • 不需要按照键的顺序遍历键值对时,因为multimap的迭代器可能会跳跃地访问相同键的元素。

最后两个OJ题作为练习
两个数组的交集
前K个高频单词

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/489368.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

如何把握未来增长话语权,全链路数字化运营有解

近年来&#xff0c;良品铺子、元气森林、蔚来等迅速成为市场中现象级的品牌&#xff0c;它们往往在很短时间内就发展成市场的生力军和消费者青睐的对象。 仔细研究背后&#xff0c;这些新生品牌的崛起&#xff0c;核心商业逻辑跟以往品牌大为不同&#xff0c;明显更“懂”新生…

基于微信小程序的酒店预定管理系统设计与实现

第1章 绪论 1 1.1开发背景与意义 1 1.2开发方法 1 1.3论文结构 1 2系统开发技术与环境 3 2.1 系统开发语言 3 2.2 系统开发工具 3 2.3 系统页面技术 3 2.4 系统数据库的选择 4 2.5 系统的运行环境 4 2.5.1 硬件环境 4 2.5.2 软件环境 4 3系统分析 5 3.1可行性分析 5 3.1.1 经济…

Java——和为S的连续正数序列

题目链接 牛客网在线oj题——和为S的连续正数序列 题目描述 小明很喜欢数学,有一天他在做数学作业时,要求计算出9~16的和,他马上就写出了正确答案是100。但是他并不满足于此,他在想究竟有多少种连续的正数序列的和为100(至少包括两个数)。没多久,他就得到另一组连续正数和为…

用Jmeter进行接口自动化测试的工作流程你知道吗?

目录 测试流程 接口测试相关文档管理规范 接口测试要点 测试流程 在测试负责人接受到测试任务后&#xff0c;应该按照以下流程规范完成测试工作。 2.1 测试需求分析 产品开发负责人在完成某产品功能的接口文档编写后&#xff0c;在核对无误后下发给对应的接口测试负责人…

word@论文后期优化和完善工作@页眉页脚页码@配置并导出pdf

文章目录 论文结构例 目录操作页眉页脚页眉样式检查所有页面的页眉添加横线 页码从第二页(封面后的一页)开始用罗马数字标页码 word导出pdf等其他格式额外配置带独立书签和目录打印pdf 最后的优化 论文结构 一篇规范的论文应该大致包括以下部分&#xff1a; 标题页&#xff1…

JavaScript全解析——canvas 入门(下)

canvas 线段两端的样式 ●canvas 中, 是可以设置线段两端的样子的 ●我们先来画三个平行线 // 0. 获取到页面上的 canvas 标签元素节点 const canvasEle document.querySelector(#canvas)// 1. 获取当前这个画布的工具箱 const ctx canvasEle.getContext(2d)// 2. 开始绘制第…

webpack 5 实战(2)

二十一、babel-loader 使用 使用babel-loader对js文件进行处理&#xff0c;在lg.Webpack.js配置文件中配置js文件规则。 使用单独的插件进行转换 使用预设进行转换 使用babel.config.js配置文件进行babel配置 const path require(path) const CopyWebpackPlugin require(…

day12 IP协议与ethernet协议

目录 IP包头 IP网的意义 IP数据报的格式 IP数据报分片 以太网包头&#xff08;链路层协议&#xff09; IP包头 IP网的意义 当互联网上的主机进行通信时&#xff0c;就好像在一个网络上通信一样&#xff0c;看不见互联的各具体的网络异构细节&#xff1b; 如果在这种覆盖…

RabbitMQ 死信队列实现

// consumer处理成功后&#xff0c;通知broker删除队列中的消息&#xff0c;如果设置multipletrue&#xff0c;表示支持批量确认机制以减少网络流量 channel.basicAck(deliveryTag, multiple);// 拒绝deliveryTag对应的消息&#xff0c;第二个参数是否requeue&#xff0c;true则…

Inception模型实现孤立手语词的识别

实现孤立手语词的识别流程如下&#xff0c;在实际研究中&#xff0c;本章将着重研究第三阶段内容&#xff0c;也就是模型的设计与实现过程&#xff0c;目的是提高手语图像的识别准确率。 Inception模型实现 Inception模型是谷歌研究人员在2014年提出的一个深度卷…

网工Python:如何使用Netmiko的SCP函数进行文件传输?

在网络设备管理中&#xff0c;传输配置文件、镜像文件等是经常需要进行的操作。Netmiko是一个Python库&#xff0c;可用于与各种网络设备进行交互&#xff0c;提供了一些用于传输文件的函数&#xff0c;其中包括SCP&#xff08;Secure Copy Protocol&#xff09;函数。本文将介…

【软考备战·希赛网每日一练】2023年5月4日

文章目录 一、今日成绩二、错题总结第一题第二题第三题第四题三、知识查缺 题目及解析来源&#xff1a;2023年05月04日软件设计师每日一练 一、今日成绩 二、错题总结 第一题 解析&#xff1a; 修改Linux文件权限命令&#xff1a;chmod。 第二题 解析&#xff1a; 第三题 解析…

欧拉奔赴品牌2.0时代,女性汽车真实用户需求被定义?

每年的上海国际汽车工业展览会&#xff0c;不仅是各大汽车品牌的技术“秀场”&#xff0c;也是品牌的营销“修罗场”。今年上海车展出圈的营销事件特别多&#xff0c;热度甚至一再蔓延到汽车行业外&#xff0c;其中欧拉也贡献了不少流量。 据了解&#xff0c;在2023上海车展欧…

mount disk space from SAN

mount disk from FC-SAN 配置硬盘域、存储池、LUN、主机及LUN与与主机之间的映射。 fc-san多路径范例1 fc-san多路径2 mount disk from iSCSI [rootqionghai11g ~]# iscsiadm -m discovery -t sendtargets -p 192.16.10.188:3260 Starting iscsid: [ OK ] 192.16.10.188:32…

Yolov1 源码讲解 voc.py

先看结构 1.mean_rgb是voc2007专用的均值 voc2007分别是这样的 坐标格式&#xff08;X0&#xff0c;Y0&#xff0c;X1&#xff0c;Y1&#xff09;其中X0,Y0是左上角的坐标,X1,Y1是右下角的坐标。 coco,voc ,yolo数据集中的bbox的坐标格式_coco bbox格式_十二耳环的博客-CSDN…

Jmeter之BeanShell取出需要参数,传递给下个请求

一、事件背景&#xff1a; 上周同事用Jmeter录制脚本&#xff0c;录制成功回放后&#xff0c;并没有达到自己想要的结果。 他的真实需求是&#xff0c;想从数据库取出某个字段值&#xff0c;然后对数据库做操作。 也就是想实现做参数传递的效果&#xff0c;我心痒痒的&#…

ConcurrentHashMap底层源码解析

ConcurrentHashMap线程安全&#xff0c;底层数组链表红黑树。 思想&#xff0c;分而治之。 JDK7有Segment对象&#xff0c;分段锁 JDK8没有了这个对象 总结&#xff0c; 首先计算hash&#xff0c; 如果集合没有元素&#xff0c;开始initTable方法初始化&#xff0c;这里扩容讲…

有人说ChatGPT信息不新?

Hello ,我是小索奇&#xff0c;今天给大家分享一个插件&#xff0c;这个插件可以通过抓取网页获得最新内容&#xff0c;它可以有效的避免ChatGPT信息过时&#xff0c;获取不到最新的信息等等 演示-这里问它一些问题&#xff1a; 现在几点了呀 可以看到时间也是很准确的&#x…

Linux权限(+Linux基本指令(下))

目录 一.基本指令补充 1.date指令 2.find指令 3.tar指令 4.Linux下的常用热键 二.Linux权限 1.Shell 2.Linux权限的概念 一.基本指令补充 1.date指令 &#x1f606;date指令可以用于显示日期和时间戳&#x1f606;Linux的时间戳与Unix时间戳一致,指的是从1970年1月1日…

使用无标注的数据训练Bert

文章目录 1、准备用于训练的数据集2、处理数据集3、克隆代码4、运行代码5、将ckpt模型转为bin模型使其可在pytorch中运用 Bert官方仓库&#xff1a;https://github.com/google-research/bert 1、准备用于训练的数据集 此处准备的是BBC news的数据集&#xff0c;下载链接&…