【C++】mapsetmultimapmultiset使用说明

news2024/12/27 1:40:28

文章目录

  • 关联式容器
  • 键值对
    • pair的定义(键值对)
  • 树形结构的关联式容器
    • Set -> 排序+去重
      • Set的文档介绍
      • Set的使用:
      • set的构造
      • set的迭代器
      • set的容量
      • set修改操作
      • API接口总结:
  • multiset -> 排序 + 可重复
    • lower_bound&&upper_bound
  • map
    • map的模板参数说明
      • map的构造
      • map的迭代器-访问方式
      • map的容量与元素访问
      • map中元素的修改
      • map的insert:
      • map的operator[]
  • multimap
      • multimap的使用
  • 在OJ中的使用
    • 1.选出前k个最受欢迎的水果
    • 2.前k个高频单词
  • 底层结构

关联式容器

C++STL包含了序列式容器关联式容器

1.序列式容器里面存储的是元素本身,其底层为线性序列的数据结构:

  • 如:vector、list、deque、forward_list(C++11)等

2.关联式容器也是用来存储数据的,与序列式容器不同的是,其里面存储的是<key, value>结构的键值对,在数据检索时比序列式容器效率更高

  • 如:set、map、unordered_set、unordered_map等

键值对

什么是键值对:

用来表示具有一一对应关系的一种结构,该结构中一般只包含两个成员变量key和value,key代表键值,value表示与key对应的信息

比如:现在要建立一个英汉互译的字典,那该字典中必然有英文单词与其对应的中文含义,而且,英文单词与其中文含义是一一对应的关系,即通过该应该单词,在词典中就可以找到与其对应的中文含义


pair的定义(键值对)

SGI-STL中关于键值对的定义: 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)
    {}
};

树形结构的关联式容器

根据应用场景的不同,STL总共实现了两种不同结构的管理式容器:树型结构与哈希结构

树型结构的关联式容器主要有四种:map、set、multimap、multiset

上述这四种容器的共同点是:使用平衡搜索树(即红黑树)作为其底层结果,容器中的元素是一个有序的序列

关联式容器容器结构底层结构
set、map、multiset、multimap树型结构平衡搜索树(红黑树)
unordered_set、unordered_map、unordered_multiset、unordered_multimap哈希结构哈希表

注意:树型结构容器中的元素是一个有序的序列,而哈希结构容器中的元素是一个无序的序列


Set -> 排序+去重

Set的文档介绍

image-20220511145339674


注意点:

  1. set是按照一定次序存储元素的容器,使用set的迭代器遍历set中的元素,可以得到有序序列
  2. set当中存储元素的value都是唯一的,不可以重复,因此可以使用set进行去重
  3. 与map/multimap不同,map/multimap中存储的是真正的键值对<key, value>,set中只放value,但在底层实际存放的是由<value, value>构成的键值对,在set容器中插入元素时,只需要插入value即可,不需要构造键值对,
  4. set中的元素不能被修改,因为set在底层是用二叉搜索树来实现的,若是对二叉搜索树当中某个结点的值进行了修改,那么这棵树将不再是二叉搜索树 (不可以修改元素,因为可能修改了就违反了二叉搜索树的原则)
  5. 在内部,set中的元素总是按照其内部比较对象所指示的特定严格弱排序准则进行排序,当不传入内部比较对象时,set中的元素默认按照小于来比较
  6. set容器通过key访问单个元素的速度通常比unordered_set容器慢,但set容器允许根据顺序对元素进行迭代
  7. set在底层是用平衡搜索树(红黑树)实现的,所以在set当中查找某个元素的时间复杂度为O(logN)


Set的使用:

set的模板参数列表

image-20220511145513530

  • T: set中存放元素的类型,实际在底层存储<value, value>的键值对
  • Compare:set中元素默认按照小于来比较
  • Alloc:set中元素空间的管理方式,使用STL提供的空间配置器管理

set的构造

函数声明功能介绍
set (const Compare& comp = Compare(), const Allocator& =
Allocator() );
构造空的set
set (InputIterator first, InputIterator last, const Compare&
comp = Compare(), const Allocator& = Allocator() );
用[first, last)区间
中的元素构造set
set ( const set<Key,Compare,Allocator>& x);set的拷贝构造

实例:

//方式1:构造某个类型的空容器
set<int> s1;//构造某个类型的空容器

//方式2:拷贝构造
set<int> s2(s1);///s1拷贝构造s2

//方式3:迭代器区间构造
int a[] = {1,2,3,4};
set<int> s3(a,a+3);//原生指针是迭代器

//如何更改比较方式:默认按照小于less<T>来比较,可以自定义仿函数,也可以用greater<T>
set<int,greater<int>> s4;//构造int类型的空容器,比较方式为大于


//可以通过{}的方式,在构造的时候初始化set
set<int> s{1,2,3,4,5}

set的迭代器

函数声明功能介绍
iterator begin()返回set中起始位置元素的迭代器
iterator end()返回set中最后一个元素后面的迭代器
const_iterator cbegin() const返回set中起始位置元素的const迭代器
const_iterator cend() const返回set中最后一个元素后面的const迭代器
reverse_iterator rbegin()返回set第一个元素的反向迭代器,即end
reverse_iterator rend()返回set最后一个元素下一个位置的反向迭代器,即rbegin
const_reverse_iterator crbegin() const返回set第一个元素的反向const迭代器,即cend
const_reverse_iterator crend() const返回set最后一个元素下一个位置的反向const迭代器,即crbegin

简写版:

成员函数函数功能
begin获取容器中第一个元素的正向迭代器
end获取容器中最后一个元素下一个位置的正向迭代器
rbegin获取容器中最后一个元素的反向迭代器
rend获取容器中第一个元素前一个位置的反向迭代器

image-20220512103410544

实例

int main()
{
    vector<int> v{ 5,4,3,2,1 };
    set<int> s1(v.begin(), v.end());//构造s1,默认排升序

    //方法1:正向迭代器遍历
    set<int> ::iterator it = s1.begin();
    //使用auto自动推导更方便
    //auto it = s1.begin();
    while (it != s1.end())
    {
        cout << *(it) << " ";//1 2 3 4 5
        it++;
    }
    cout << endl;

    //方法2:反向迭代器遍历
    set<int> :: reverse_iterator it2 = s1.rbegin(); //反向迭代器 reverse_iterator
    //使用auto自动推导更方便
    while (it2 != s1.rend())
    {
        cout << *(it2) << " ";  // 5 4 3 2 1
        it2++;
    }
    cout << endl;

    //方法3:范围for   -> 本质还是迭代器 (正向迭代器)
    for (auto& x : s1)
    {
        cout << x << " ";//1 2 3 4 5
    }
    return 0;
}

set的容量

函数声明功能介绍
bool empty ( ) const检测set是否为空,空返回true,否则返回false
size_type size() const返回set中有效元素的个数

实例

vector<int> v{ 7,7,5,4,3,2,1};
set<int> s(v.begin(), v.end());//迭代器构造s

cout << s.size() << endl;//返回容器中的有效元素个数 -> 6
//问:为什么是6个而不是7个呢?原因:set有去重作用

cout << s.empty() << endl;//判断容器是否为空 -> 0 false

set修改操作

函数声明功能介绍
pair<iterator,bool> insert (const value_type& x )在set中插入元素x,实际插入的是<x, x>构成的键值对,返回的是pair键值对! 如果插入成功,返回<该元素在set中的位置,true>,如果插入失败,说明x在set中已经存在,返回<x在set中的位置,false>
void erase ( iterator position )(迭代器删除)删除set中position位置上的元素
size_type erase ( const key_type& x )(按值删除)删除set中值为x的元素,返回删除的元素的个数
void erase ( iterator first, iterator last )(迭代器区间删除)删除set中[first, last)区间中的元素
void swap (set<Key,Compare,Allocator>& st );交换set中的元素
void clear ( )将set中的元素清空
iterator find ( const key_type& x ) const按值查找,返回set中值为x的元素的位置
size_type count ( const key_type& x ) const返回set中值为x的元素的个数

简化版:

成员函数功能
insert插入元素
erase删除元素,可以迭代器删除,也可以按值删除,也可以用迭代器区间删除 返回值为:删除的个数, 一般搭配find函数使用
find查找元素,如果找到成功,返回该位置的迭代器,如果找到的元素不在set中,返回end()迭代器位置,
swap交换两个容器中的数据,要保证两个容器中存放的是相同的类型
count获取容器中指定元素值的元素个数, 由于set底层是二叉排序树,value不可以重复,所以返回值要么为0,要么为1
clear清空容器中的内容

实例 insert erase find

int main()
{
	set<int> s1;
	set<int> s2;

	//insert: 插入元素
	s1.insert(1); 
	s1.insert(1);//不起作用,因为set的底层是二叉排序树,相同的value不进行插入
	s1.insert(2);
	s1.insert(3);
	for (auto& x : s1) cout << x << " ";	//1 2 3 
	cout << endl;

	//erase:删除元素,可以迭代器删除,也可以按值删除,也可以用迭代器区间删除  返回值为:删除的个数, 一般搭配find函数使用
	s1.erase(1);//  按值删除
	for (auto& x : s1) cout << x << " ";	//2 3
	cout << endl;

	auto pos = s1.find(2);//找到value为2的位置,返回该位置的迭代器
	s1.erase(pos);//迭代器删除
	for (auto& x : s1) cout << x << " ";	//3

	s1.insert(4);
	s1.insert(5);
	s1.erase(s1.begin(), s1.end());//迭代器区间删除
	for (auto& x : s1) cout << x << " ";//空容器
	cout << endl;
}

实例2:swap count clear

int main()
{
	set<int> s1{ 1,2,3,4 };
	set<char> s2{'a','b','c'};
	set<int> s3{ 5,6,7 };
	//s1.swap(s2);//两个容器存放的类型不匹配,不能进行交换
	s1.swap(s3);
	for (auto& x : s1) cout << x << " ";	//5 6 7 
	cout << endl;

	for (auto& x : s3) cout << x << " "; //1 2 3 4
	cout << endl;
	
	//count:获取容器中指定元素值的元素个数, 由于set底层是二叉排序树,value不可以重复,所以返回值要么为0,要么为1
	//可以通过count判断指定元素值是否在set中
	set<int> s{ 1,1,2,2,3 };
	cout << s.count(1) << endl;//1
	cout << s.count(5) << endl;//0

	//clear:清空容器的内容
	s.clear();
	for (auto& x : s) cout << x << " ";//空容器
	return 0;
}

API接口总结:

关于erase:

可以删除一个值,也可以根据迭代器位置删除, 

  • 根据迭代器位置删除: 一般搭配find函数使用, 就必须保证是一个合法位置的迭代器,否则会崩溃,所以需要检测find函数的返回值,如果find查找到了该元素,则返回该元素的迭代器,否则返回end()迭代器位置
int main()
{
    set<int> s{ 1,2,3,4,5 };
    set<int> ::iterator pos = s.find(20);//不合法的位置
    //需要判断pos位置是否合法!
    if (pos != s.end())
    {
        //说明pos位置是合法的迭代器位置!
        s.erase(pos);
    }
    return 0;
}
  • 根据值删除:如果该值存在就直接删除,如果不存在,就不做任何处理

erase的返回值:返回的是删除的元素的个数

可以通过返回值知道到底有没有删除数据,返回的类型是size_type, 返回删除元素的数量 没有删除就是0否则就是1(因为set不可以存相同的val)

官方文档关于size_type的解释:本质就是无符号整形size_t

成员类型定义Note
size_typean unsigned integral type that can represent any non-negative value of difference_typeusually the same as size_t

关于swap函数: 如果直接交换两个容器的内容,可能涉及深拷贝,代价太大,

只需把两棵树的根节点的指针进行交换即可


multiset -> 排序 + 可重复

multiset的介绍

image-20220511153332808


[翻译]:

  1. multiset是按照特定顺序存储元素的容器,其中元素是可以重复的
  2. 在multiset中,元素的value也会识别它(因为multiset中本身存储的就是<value, value>组成的键值对,因此value本身就是key,key就是value,类型为T). multiset元素的值不能在容器中进行修改(因为元素总是const的),但可以从容器中插入或删除
    • 不可以修改元素,因为可能修改了就违反了二叉搜索树的原则
  3. 在内部,multiset中的元素总是按照其内部比较规则(类型比较)所指示的特定严格弱排序准则进行排序
  4. multiset容器通过key访问单个元素的速度通常比unordered_multiset容器慢,但当使用迭代器遍历时会得到一个有序序列
  5. multiset底层结构为二叉搜索树(红黑树)

注意:

  1. multiset中再底层中存储的是<value, value>的键值对
  2. mtltiset的插入接口中只需要插入即可
  3. 与set的区别是,multiset中的元素可以重复,set是中value是唯一的
  4. 使用迭代器对multiset中的元素进行遍历,可以得到有序的序列
  5. multiset中的元素不能修改
  6. 在multiset中找某个元素,时间复杂度为O(logN)
  7. multiset的作用:可以对元素进行排序

lower_bound&&upper_bound

vector里面也有这两个函数,也是同样的含义

iterator lower_bound (const value_type& val) const;// 返回的是第一个<=给定元素val的值
iterator upper_bound (const value_type& val) const;//返回的是第一个>给定元素val的位置

如果找不到,就返回end()迭代器位置

使用例子:

int main()
{
	vector<int> v{5,6,3,2,1,8};
	multiset<int> mt(v.begin(),v.end());
	auto pos = mt.lower_bound(5);
	if( pos!= mt.end())
	{
		cout << *pos << endl;//5
	}
	
	pos = mt.upper_bound(5);
	if( pos!= mt.end())
	{
		cout << *pos << endl;//6
	}
	return 0;
}

multiset的使用:此处只简单演示set与multiset的不同,其他接口接口与set相同

multiset容器和set容器的唯一区别就是,multiset允许键值冗余,即multiset容器当中存储的元素是可以重复的

void testMuSet()
{
    multiset<int> ms{1,1,1,1,1,1,2,2,3};
    // 注意:multiset在底层实际存储的是<int, int>的键值对
    //插入元素可以重复
    for (auto& e : s) cout << e << " ";//1 1 1 1 1 1 2 2 3
    cout << endl;
    cout <<s.count(1)<<endl;//6, 在set中count接口函数返回值只能为0/1,因为不允许数据重复
}

注意:由于multiset容器允许键值冗余,因此set和multiset的成员函数find和count的意义也有所不同:

接口对比:

find函数功能
set容器返回值为val的元素的迭代器
multiset容器返回搜索树中序的第一个值为val的元素的迭代器

注意:find(val) 在multiset容器在返回的是中序遍历中第一个值为val的元素的迭代器

验证:

int main()
{
    multiset<int> ms{ 0,2,1,4,1,2,6,7 };
    //find的val有多个值的时候,返回中序遍历第一个val所在位置的迭代器
    multiset<int>::iterator it = ms.find(1);
    while (it != ms.end())
    {
        cout << (*it) << " ";//1 1 2 2 4 6 7
        it++;
    }
    cout << endl;
}

通过find函数找到迭代器所在位置,不可以删除!

auto pos = ms.find(1);
if(pos !=ms.end())
{
   	*pos = 10;//err
}

*pos是常量,multiset的内容不可以修改!

怎么做到的呢 : 迭代器调用operator*或者operator->,返回值是const T&

为什么不支持修改:因为修改之后不一定是二叉搜索树了


要求:删除所有的某个值

int main()
{
    multiset<int> ms{ 1,1,1,1,0,0,1,2 };
    //要求删除所有值为1的元素
    multiset<int> ::iterator pos = ms.find(1);
    //因为ms内部是排序的,所以可以一直删除
    //循环结束条件:直到删除到结尾 || 当前值不是1了
    while (pos != ms.end() && *pos == 1)
    {
        ms.erase(pos);
        pos++;
    }
    return 0;
}

现象: 直接崩溃了!

原因:迭代器失效了,因为底层是二叉搜索树,所以迭代器类似于节点的指针,erase这个迭代器的位置,实际是把节点释放掉了,pos里面迭代器的指针就是一个野指针,所以++pos就会出错

image-20220512153831398


解决办法1:用迭代器区间删除,

不断往后走,走到值不是val的位置 注意erase迭代器区间,区间是左闭右开的!

int main()
{
    multiset<int> ms{ 1,1,1,1,0,0,1,2 };
    for (auto& x : ms) cout << x << " ";    //0 0 1 1 1 1 1 2
    cout << endl;

    //要求删除所有值为1的元素
    multiset<int> ::iterator posStart = ms.find(1);
    multiset<int> ::iterator posEnd = posStart;
    //防止越界 && 走到值不为1的位置
    while (posEnd != ms.end() && *posEnd == 1)
    {
        posEnd++;
    }
    //迭代器区间删除 [posStart,posEnd)就是值为1的区间
    ms.erase(posStart, posEnd);
    for (auto& x : ms) cout << x << " "; // 0 0 2
    cout << endl;
    return 0;
}

image-20220512154419357


解决方法2:先保存下一个位置的的迭代器位置 再删除当前位置的迭代器

int main()
{
    multiset<int> ms{ 1,1,1,1,0,0,1,2 };
    for (auto& x : ms) cout << x << " ";    //0 0 1 1 1 1 1 2
    cout << endl;
    
    //要求删除所有值为1的元素
    multiset<int> ::iterator pos = ms.find(1);
    while (pos != ms.end() && *pos == 1)
    {
        //1.保存下一个位置的迭代器
        auto next = pos;
        next++;

        //2.删除当前位置的迭代器
        ms.erase(pos);

        //3.判断下一个位置
        pos = next;
    }
    for (auto& x : ms) cout << x << " ";    //0 0 2
    cout << endl;
    return 0;
}

解决方法3:直接按值删除,删除所有值为val的元素

int main()
{
    multiset<int> ms{ 1,1,1,1,0,0,1,2 };
    for (auto& x : ms) cout << x << " ";    //0 0 1 1 1 1 1 2
    cout << endl;

    //要求删除所有值为1的元素
    cout << ms.erase(1) << endl;//按值删除, 返回删除的元素个数:5

    for (auto& x : ms) cout << x << " ";    //0 0 2
    cout << endl;
    return 0;
}

count函数功能
set容器返回值为val的元素个数, 但是只能为0/1,因为不能数据重复 如果val的元素存在则返回1不存在则返回0 (find成员函数可代替此处count的作用)
multiset容器返回值为val的元素个数(find成员函数不可代替此处count的作用)

map

map的介绍

image-20220511150329622


翻译:

  1. map是关联式容器,它按照特定的次序(按照key来比较)存储键值key和值value组成的元素,使用map的迭代器遍历map中的元素,可以得到有序序列
  2. 在map中,键值key通常用于排序和唯一地标识元素,而值value中存储与此键值key关联的内容,键值key和值value的类型可能不同,并且在map的内部,key与value通过成员类型value_type绑定在一起,并取别名为pair typedef pair value_type;
  3. map容器中元素的键值key不能被修改,但是元素的值value可以被修改,因为map底层的二叉搜索树是根据每个元素的键值key进行构建的,而不是值value
  4. 在内部,map中的元素总是按照键值key进行比较排序的,当不传入内部比较对象时,map中元素的键值key默认按照小于来比较
  5. map容器通过键值key访问单个元素的速度通常比unordered_map容器慢,但map容器允许根据顺序对元素进行直接迭代
  6. map容器支持下标访问符,即在[]中放入key,就可以找到与key对应的value
  7. map在底层是用平衡搜索树(红黑树)实现的,所以在map当中查找某个元素的时间复杂度为O(logN)

map的模板参数说明

image-20220511150418867

key: 键值对中key的类型

T: 键值对中value的类型

Compare: 比较器的类型**,map中的元素是按照key来比较的**,缺省情况下按照小于来比较,一般情况下(内置类型元素)该参数不需要传递,如果无法比较时(自定义类型),需要用户自己显式传递比较规则(一般情况下按照函数指针或者仿函数来传递)

Alloc:通过空间配置器来申请底层空间,不需要用户传递,除非用户不想使用标准库提供的空间配置器
注意:在使用map时,需要包含头文件


map的构造

方式1:指定key和value的类型构造一个空容器

方式2:拷贝构造容器

方式3:使用迭代器进行拷贝构造

map默认是根据key进行比较的,比较方式默认为小于less< key>,我们可以指定比较方式为大于:greater

同样我们可以通过{}在构造的时候赋值

void testMap1()
{
	map<string, int> m;//构造一个空的map
    map<string,int> m2(m);//用m容器拷贝构造m2容器
    map<string,int> m3(m.begin(),m.end());//使用m容器的迭代器拷贝构造m3
    
    map<string,int,greater<string>> m4;//默认按照key进行比较,比较方式为小于,可以指定为大于
	//同样我们可以通过{}在构造的时候赋值
	map<string, string> m1{ {"芒果","Mango"},{"柠檬","Lemon"} };
}

map的迭代器-访问方式

函数声明功能介绍
begin()和end()begin:首元素的位置,end最后一个元素的下一个位置
cbegin()和cend()与begin和end意义相同,但cbegin和cend所指向的元素不能修改
rbegin()和rend()反向迭代器,rbegin在end位置,rend在begin位置,其++和–操作与<begin和end操作移动相反
crbegin()和crend()与rbegin和rend位置相同,操作相同,但crbegin和crend所指向的元素不能修改

简写版:

成员函数功能
begin获取容器中第一个元素的正向迭代器
end获取容器中最后一个元素下一个位置的正向迭代器
rbegin获取容器中最后一个元素的反向迭代器
rend获取容器中第一个元素前一个位置的反向迭代器

方式1:正向迭代器遍历

int main()
{
    map<string,int> m;
    m.insert(make_pair("芒果",6));
    m.insert(make_pair("柠檬",0));
    m.insert(make_pair("葡萄",3));
    //方式1:正向迭代器遍历
    map<string, int>::iterator it = m.begin();
    //使用auto推导更方便
    //auto it = m.begin();
    while (it != m.end())
    {
        //方式1:通过解引用访问
        
        //it是迭代器,*it,解引用拿到map中对应的内容是一个pair键值对
        //pair是一个对象,通过.访问pair的两个成员first和second, key-value
        cout << (*it).first << " " << (*it).second << endl;

        //方式2:直接->访问
        //it是迭代器,迭代器调用operator-> 
        //返回数据的指针,数据就是pair,所以就是pair指针
        //然后用pair指针访问成员 it-> -> first  ==>编译器优化为it->first
        cout << it->first << " " << it->second << endl;
        it++;
    }
    return 0;
}

注意:map默认是根据key进行排序的,这里的key是string类型,所以是根据string进行排序

错误方式:

auto it = m.begin();
while(it!=m.end())
{
    cout <<*it<<" ";//it是迭代器, *it调用operator*   it++;
}

会报错!原因:map的迭代器解引用返回值是pair,而pair没有重载<<运算符,我们需要指定访问第一个成员还是第二个成员


方式2:反向迭代器遍历

int main()
{
    map<string,int> m;
    m.insert(make_pair("芒果",6));
    m.insert(make_pair("柠檬",0));
    m.insert(make_pair("葡萄",3));
    //方式2:反向迭代器遍历
    map<string, int>::reverse_iterator it = m.rbegin();
    //使用auto推导更方便
    //auto it = m.begin();
    while (it != m.rend())
    {
        //方式1:通过解引用访问
        
        //it是迭代器,*it,解引用拿到map中对应的内容是一个pair键值对
        //pair是一个对象,通过.访问pair的两个成员first和second, key-value
        //注意: .的优先级比*高
        cout << (*it).first << " " << (*it).second << endl;

        //方式2:直接->访问
        //it是迭代器,迭代器调用operator-> 
        //返回数据的指针,数据就是pair,所以就是pair指针
        //然后用pair指针访问成员 it-> -> first  ==>编译器优化为it->first
        cout << it->first << " " << it->second << endl;
        it++;
    }
    return 0;
}

方式3:范围for遍历 注意:范围for的本质就是迭代器遍历,支持迭代器遍历就支持范围for遍历

int main()
{
    map<string,int> m;
    m.insert(make_pair("芒果",6));
    m.insert(make_pair("柠檬",0));
    m.insert(make_pair("葡萄",3));
    //方式3:范围for遍历
    //使用auto推导更加方便
    //加引用是为了防止深拷贝,代价太大
    //范围for本质是迭代器,把*(it)赋值给kx  auto kx = *(it)
    //kv相当于就是一个pair对象,通过.访问成员
    for (auto& kx : m)
    {
        cout << kx.first << " " << kx.second << " ";
    }
    cout << endl;
    return 0;
}

map的容量与元素访问

函数声明功能简介
bool empty ( ) const检测map中的元素是否为空,是返回true,否则返回false
size_type size() const返回map中有效元素的个数
mapped_type& operator[] (const key_type& k)返回key对应的value

问题:当key不在map中时,通过operator获取对应value时会发生什么问题?

image-20220511152042746

注意:在元素访问时,有一个与operator[]类似的操作at()(该函数不常用)函数,都是通过key找到与key对应的value然后返回其引用,不同的是:当key不存在时,operator[]用默认value与key构造键值对然后插入,返回该默value,at()函数直接抛异常,


map中元素的修改

函数声明功能简介
pair<iterator,bool> insert (const value_type& x )在map中插入键值对x,注意x是一个键值对,返回值也是键值对:iterator代表新插入元素的位置,bool代表释放插入成功
void erase ( iterator position )删除position位置上的元素
size_type erase ( constkey_type& x )删除键值为x的元素
void erase (iterator first,iterator last )删除[first, last)区间中的元素
void swap (map<Key,T,Compare,Allocator>&mp )交换两个map中的元素
void clear ( )将map中的元素清空
iterator find ( const key_type& x)在map中插入key x的元素,找到返回该元素的位置的迭代器,否则返回end
const_iterator find ( const<key_type& x ) const在map中插入key为x的元素,找到返回该元素的位置的const迭代器,否则返回cend
size_type count ( const key_type& x ) const返回key为x的键值在map中的个数,注意map中key是唯一的,因此该函数的返回值要么为0,要么为1,因此也可以用该函数来检测一个key是否在map中

map的insert:

image-20220512194245440

在map中插入元素的方式:

int main()
{
    map<string, string> dict;
    //方式1:构造一个pair对象插入
    pair<string, string> kv1("Mango", "芒果");
    dict.insert(kv1);

    // 方式2:构造匿名pair对象插入
    dict.insert(pair<string, string>("Lemon", "柠檬"));

    //方式3:使用make_pair直到推导类型插入
    //make_pair的本质是方式2封装了一层,因为make_pair本质就是利用函数模板,构造匿名对象返回
    dict.insert(make_pair("Apple", "苹果"));

    //方式4:直接使用{key,value}插入,C++11的用法
    dict.insert({ "Orange","橘子" });

    auto it = dict.begin();
    while (it != dict.end())
    {
        cout << (*it).first << " " << (*it).second << endl;
        it++;
    }
    return 0;
}

小实例:统计水果出现的次数

方法1:直接遍历

int main()
{
    string arr[] = { "苹果","苹果","芒果","葡萄","香蕉","芒果" };
    //key-value :水果-次数
    map<string, int> countMap;
    //遍历数组统计
    for (auto& str : arr)
    {
        auto ret = countMap.find(str);//查找水果是否在map中出现过
        //没有找到
        if (ret == countMap.end())
        {
            countMap.insert(make_pair(str, 1));//新增该水果,次数设定为1
        }
        else
        {
            //find返回的是迭代器,迭代器调用operator->返回的是数据指针
            //数据是pair,就是pair指针,再通过->访问first和second成员
            // ret->->first 被优化成: ret->first
            ret->second++;//出现过,次数++
        }
    }
    //遍历map
    for (auto& kv : countMap)
    {
        cout << kv.first<<" " <<kv.second<< endl;
    }
    cout << endl;
    return 0;
}

缺点:如果水果没有出现过,则需要:1.查找一次 2.在相应位置插入 相当于走了两遍


优化: 使用insert函数

如果插入的元素已经存在: 插入失败,返回pair<该元素迭代器位置,false>

不存在:插入成功,然后返回 pair<新插入元素的迭代器位置,true>

image-20220512200204590

int main()
{
    string arr[] = { "苹果","苹果","芒果","葡萄","香蕉","芒果" };
    //key-value :水果-次数
    map<string, int> countMap;
    //遍历数组统计
    for (auto& str : arr)
    {
        // 不管水果在不在,直接插入
        //如果水果不在:插入成功,次数正确设置为1次,返回pair对象,pair<新插入位置迭代器,true>
        //如果水果在,插入失败,返回pair对象,pair<已经存在位置的迭代器,false>
        //只需根据返回对象的second的值判断是否存在即可
        auto kv = countMap.insert(make_pair(str,1));
        if (kv.second == false)
        {
            //原水果存在,出现次数++
            // kv是一个pair对象  <迭代器,bool>
            //kv.first访问kv的第一个成员.就是已经存在位置的迭代器,然后通过->访问第一个成员的second
            kv.first->second++;
        }
    }
    //遍历map
    for (auto& kv : countMap)
    {
        cout << kv.first<<" " <<kv.second<< endl;
    }
    cout << endl;
    return 0;
}

map的operator[]

方式2:通过 []进行统计

int main()
{
    string arr[] = { "苹果","苹果","芒果","葡萄","香蕉","芒果" };
    //key-value :水果-次数
    map<string, int> countMap;
    //遍历数组统计
    for (auto& str : arr)
    {
        countMap[str]++;
    }
    //遍历map
    for (auto& kv : countMap)
    {
        cout << kv.first<<" " <<kv.second<< endl;
    }
    cout << endl;
    return 0;
}

为什么可以这样呢?

image-20220512202931394

mapped_type& operator[](const key_type& k)
{
    pair<iterator, bool> ret = insert(make_pair(k, mapped_type()));
    return ret.first->second;
}

[]是通过insert实现的,

1.k在map中,insert插入失败,因为k已经有了,insert返回的pair会带出k在map中存储节点的迭代器,通过这个迭代器,我们可以拿到k对于value的值,进行返回

2.k不在map中,insert插入成功,插入的值是pair<k,value()> ,insert的返回值会带出刚插入的k所在节点的迭代器,通过这个迭代器,我们可以拿到k对于的value值进行返回

总结:map的operator[]的特征:

1.k不存在,插入默认构造函数生成的缺省值的value的pair<k,V()>,然后返回该key对于的val的默认值的引用

2.k存在,返回k对于的value值

image-20220512204050174


[]的功能:

  • 插入,查找,修改

例子1:

int main()
{
    map<string, string> dict;
    dict.insert(make_pair("sort", "排序"));
    dict.insert(make_pair("left","左边"));
    dict.insert(make_pair("left", "剩余"));//不修改原来的val,因为key已经存在了,不插入

    dict["left"] = "剩余";//查找+修改 拿到key对于的val,返回的是引用,然后修改val的值 
    dict["test"];//查找+插入
    dict["string"] = "字符串";//插入+修改

    cout << dict["test"] << endl;//查找  大于为空,因为sting的默认值为""
}

#include <string>
#include <map>
void TestMap()
{
    map<string, string> m;
    // 向map中插入元素的方式:
    // 将键值对<"peach","桃子">插入map中,用pair直接来构造键值对
    m.insert(pair<string, string>("peach", "桃子"));
    // 将键值对<"peach","桃子">插入map中,用make_pair函数来构造键值对
    m.insert(make_pair("banan", "香蕉"));
    // 借用operator[]向map中插入元素
    /*
    operator[]的原理是:
    用<key, T()>构造一个键值对,然后调用insert()函数将该键值对插入到map中
    如果key已经存在,插入失败,insert函数返回该key所在位置的迭代器
    如果key不存在,插入成功,insert函数返回新插入元素所在位置的迭代器
    operator[]函数最后将insert返回值键值对中的value返回
    */
    // 将<"apple", "">插入map中,插入成功,返回value的引用,将“苹果”赋值给该引用结果,
    m["apple"] = "苹果";
    // key不存在时抛异常
    //m.at("waterme") = "水蜜桃";
    cout << m.size() << endl;
    // 用迭代器去遍历map中的元素,可以得到一个按照key排序的序列
    for (auto& e : m)
        cout << e.first << "--->" << e.second << endl;
    cout << endl;
    // map中的键值对key一定是唯一的,如果key存在将插入失败
    auto ret = m.insert(make_pair("peach", "桃色"));
    if (ret.second)
        cout << "<peach, 桃色>不在map中, 已经插入" << endl;
    else
        cout << "键值为peach的元素已经存在:" << ret.first->first << "--->" <<
        ret.first->second <<" 插入失败"<< endl;
    // 删除key为"apple"的元素
    m.erase("apple");
    if (1 == m.count("apple"))
        cout << "apple还在" << endl;
    else
        cout << "apple被吃了" << endl;
}

【总结】

  1. map中的的元素是键值对
  2. map中的key是唯一的,并且不能修改
  3. 默认按照小于的方式对key进行比较
  4. map中的元素如果用迭代器去遍历,可以得到一个有序的序列
  5. map的底层为平衡搜索树(红黑树),查找效率比较高,查找的时间复杂度为:O(logN)
  6. 支持[]操作符,operator[]中实际进行插入查找


multimap

multimap的介绍

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JozGjC0t-1682472748983)(https://mangoimage.oss-cn-guangzhou.aliyuncs.com/202205111535424.png)]

翻译:

  1. Multimaps是关联式容器,它按照特定的顺序,存储由key和value映射成的键值对<key, value>,其中多个键值对之间的key是可以重复的
  2. 在multimap中,通常按照key排序和惟一地标识元素,而映射的value存储与key关联的内容,key和value的类型可能不同,通过multimap内部的成员类型value_type组合在一起,value_type是组合key和value的键值对:
    typedef pair<const Key, T> value_type;
  3. 在内部,multimap中的元素总是通过其内部比较对象,按照指定的特定严格弱排序标准对key进行排序的
  4. multimap通过key访问单个元素的速度通常比unordered_multimap容器慢,但是使用迭代器直接遍历
    multimap中的元素可以得到关于key有序的序列,
  5. multimap在底层用二叉搜索树(红黑树)来实现

注意:multimap和map的唯一不同就是:map中的key是唯一的,而multimap中key是可以重复的


multimap的使用

multimap中的接口可以参考map,功能都是类似的,

注意:

  1. map中的key是唯一的,multimap中的key是可以重复的

  2. multimap中的元素默认将key按照小于来比较(相同的key值也能输出)

  3. multimap中没有重载operator[]操作

    原因:multimap中的key值是可重复的,允许数据冗余,如果支持operator[] 底层不知道该修改哪个key对应的value

  4. erase(key) 会把所有key相同的元素全部删除掉,返回删除的元素个数

  5. 使用时与map包含的头文件相同

int main()
{
    multimap<string, string> dict;
    dict.insert(make_pair("sort", "排序"));
    dict.insert(make_pair("left", "左边"));
    dict.insert(make_pair("left", "剩余")); //multimap支持重复元素,成功插入
    dict.insert(make_pair("left", "剩余")); 
}

image-20220512205122826


在OJ中的使用

1.选出前k个最受欢迎的水果

本公司现在要给公司员工发波福利,在员工工作时间会提供大量的水果供员工补充营养,由于水果种类比较多,但是却又不知道哪种水果比较受欢迎,然后公司就让每个员工报告了自己最爱吃的k种水果,并且告知已经将所有员工喜欢吃的水果存储于一个数组中,然后让我们统计出所有水果出现的次数,并且求出大家最喜欢吃的前k种水果

函数接口:

void GetFavoriteFruit(const vector<string>& fruits,size_t k)
{} 

注意点:

  • sort的底层是快速排序,快排有三数取中优化,提升效率,所以要支持随机访问,而map的数据不支持随机访问

所以:

sort(m.begin(),m.end());//err,map的数据不支持随机访问

  • map默认是按key进行排序的,那么问题来了,可不可以将map的键值对定义为: map<int,string>呢,此时key为:水果次数 val为:水果
    • 不可行!因为map不支持插入重复元素,原因:这样会导致:如果水果次数相同,则不进行插入,导致出错!

那如何对map按照val进行排序呢?

我们可以把map里面的元素放到vector中,vector存放的是与map键值对对应的pair对象,map不支持sort,但是vector支持sort

vector存放的数据是pair对象,pair对象本身是可以比较大小的 ,

image-20220513170439952

所以如果我们希望按照value进行排序,就需要自己实现仿函数传给sort函数

注意:sort函数的第三个参数:函数名(函数的入口地址 ) || 对象(匿名对象 )|| lambda表达式


方法1:

#include<map>
#include<algorithm>
#include<iostream>
#include<vector>
#include<string>
using namespace std;
//仿函数,根据pair的val进行比较
struct CountVal
{
    //vector里面存放的是pair对象
    bool operator()(const pair<string, int>& l, const pair<string, int>& r)
    {
        // 因为选的是最受欢迎的前k个水果,所以按val降序排序
        return l.second > r.second;
    }
};
void GetFavoriteFruit(const vector<string>& fruits, size_t k)
{
    map<string, int> countMap;
    //1.遍历容器统计每一种水果的次数
    for (auto& str : fruits)
    {
        countMap[str]++;
    }
    //2.把countMap里面的东西放到vector
    vector<pair<string, int>> sortV;
    for (auto& kv : countMap)
    {
        //kv就是countMap的成员,是一个pair对象
        sortV.push_back(kv);
    }
    //3.对vector,根据pair的val进行排序
    sort(sortV.begin(), sortV.end(), CountVal());

    //4.输出vector的前k个元素
    for (int i = 0; i < k; i++)
    {
        //vector里面存放的是pair对象,pair没有重载<<
        //我们需要指定访问first和second
        cout << sortV[i].first << " " << sortV[i].second << endl;
    }
}
int main()
{
    vector<string> fruits{ "芒果","苹果","芒果","雪梨","榴莲","芒果","榴莲" };
    GetFavoriteFruit(fruits, 2);
    return 0;
}

缺陷:把countMap的pair对象拷贝赋值给kv要进行深拷贝,pair的第二个成员是string对象,又要进行深拷贝,代价大


优化:vector里面不存countMap中的pair对象,改为存countMap的迭代器!

map的迭代器不支持排序,但是vector的迭代器支持排序

此时vector里面存放的是map的迭代器,所以我们需要实现仿函数,告诉编译器怎么比较

struct CountIterVal
{
    //此时vector里面存放的是迭代器
    bool operator()(const map<string, int>::iterator& l, const map<string, int>::iterator& r)
    {
        //写法1:迭代器解引用 :调用operator* 得到的是pair对象,然后通过.访问成员
        //按照value值排序,排降序
        //return (*l).second > (*r).second;

        //写法2:迭代器->  调用operator-> 得到的是对象的指针,即pair指针
        //然后pair指针->访问成员
        //迭代器->->成员 被系统优化为:迭代器->成员
        return l->second > r->second;
    }
};
void GetFavoriteFruit(const vector<string>& fruits, size_t k)
{
    map<string, int> countMap;
    //1.遍历容器统计每一种水果的次数
    for (auto& str : fruits)
    {
        countMap[str]++;
    }
    //2.vector里面存countMap的迭代器
    vector<map<string, int>::iterator> sortV;
    auto it = countMap.begin();
    while (it != countMap.end())
    {
        sortV.push_back(it);
        it++;
    }
    //3.对vector进行排序
    sort(sortV.begin(), sortV.end(), CountIterVal());

    //4.输出容器的前k个元素的内容
    for (int i = 0; i < k; i++)
    {
        //vector里面的元素是迭代器
        //解释见仿函数部分
        cout << sortV[i]->first << sortV[i]->second << endl;
        //方式2:
        cout << (*sortV[i]).first << (*sortV[i]).second << endl;
    }
}
int main()
{
    vector<string> fruits{ "芒果","苹果","芒果","雪梨","苹果","芒果","榴莲" };
    GetFavoriteFruit(fruits, 2);
    return 0;
}

方法3:map<int,string>行不通,本质是因为map中不支持重复元素,但是我们可以使用multimap,它支持重复元素

void GetFavoriteFruit(const vector<string>& fruits, size_t k)
{
    map<string, int> countMap;
    //1.遍历容器统计每一种水果的次数
    for (auto& str : fruits)
    {
        countMap[str]++;
    }
    multimap<int, string> sortMap;
    //2.将countMap内容放到multimap中
    //其中countMap的键值对为<string,int>:strig:水果 int: 水果出现次数
    //sortMap的键值对为<int,string>: int: 水果出现次数 strig:水果
    // 所以sortMap中默认按key排序,也就是按水果出现次数给我们排好序
    //所以插入的时候要注意顺序!!!
    for (auto& kv : countMap)
    {
        //kv是countMap的一个pair对象
        //注意插入顺序!
        sortMap.insert(make_pair(kv.second, kv.first));
    }
    //此时sortMap中就是按照次数排好序的
    //for (auto& kv : sortMap)
    //{
    //    cout << kv.first << " " << kv.second << endl;
    //}
    /*
    1 榴莲
    1 雪梨
    2 苹果
    3 芒果
    */
    //为什么是这样排序的呢?因为我们没有指定multimap的第三个参数,默认就是less<Key>,默认是排升序
    //我们可以使用反向迭代器输出前k个
    auto it = sortMap.rbegin();
    for (int i = 0; i < k; i++)
    {
        //it是迭代器
        cout << it->first <<" "<< it->second << endl;
        it++;
    }
}
int main()
{
    vector<string> fruits{ "芒果","苹果","芒果","雪梨","苹果","芒果","榴莲" };
    GetFavoriteFruit(fruits, 2);
    return 0;
}

image-20220513203407732

我们也可以传仿函数greater<key>指定multimap的排序方式为降序,需要引用头文件:#include<functional>

void GetFavoriteFruit(const vector<string>& fruits, size_t k)
{
    map<string, int> countMap;
    //1.遍历容器统计每一种水果的次数
    for (auto& str : fruits)
    {
        countMap[str]++;
    }
    // key是int类型,所以仿函数为greater<int>
    multimap<int, string,greater<int>> sortMap;
    //2.将countMap内容放到multimap中
    for (auto& kv : countMap)
    {
        //kv是countMap的一个pair对象
        //注意插入顺序!
        sortMap.insert(make_pair(kv.second, kv.first));
    }
    //sortMap中按水果次数  降序排好序了,
    auto it = sortMap.begin();
    for (int i = 0; i < k; i++)
    {
        //it是迭代器
        cout << it->first << " " << it->second << endl;
        it++;
    }
}
int main()
{
    vector<string> fruits{ "芒果","苹果","芒果","雪梨","苹果","芒果","榴莲" };
    GetFavoriteFruit(fruits, 2);
    return 0;
}

方法:top-k问题:使用优先级队列

#include<queue>
//仿函数,根据val进行比较
struct CountVal
{
    //vector里面存放的是pair对象
    bool operator()(const pair<string, int>& l, const pair<string, int>& r)
    {
        // 因为选的是最受欢迎的前k个水果,所以按val降序排序
        return l.second < r.second;
    }
};
void GetFavoriteFruit(const vector<string>& fruits, size_t k)
{
    map<string, int> countMap;
    //1.遍历容器统计每一种水果的次数
    for (auto& str : fruits)
    {
        countMap[str]++;
    }
    //2.把countMap的数据插入到优先级队列中,所以堆中存放的是pair对象
    //我们要自定义堆的比较方式
    // 堆中存放的是pair,我们要按val进行比较,所以要传仿函数
    //第三个参数传类型
    priority_queue<pair<string, int>, vector<pair<string, int>>, CountVal> pq;
    for (auto& kv : countMap)
    {
        pq.push(kv);
    }
    //3.输出堆顶数据
    while (k--)
    {
        //堆中的元素是pair对象
        cout << pq.top().first << " " << pq.top().second << endl;
        pq.pop();
    }
}
int main()
{
    vector<string> fruits{ "芒果","苹果","芒果","雪梨","苹果","芒果","榴莲" };
    GetFavoriteFruit(fruits, 2);
    return 0;
}

缺点:把countMap的pair对象拷贝给kv要进行深拷贝,pair的第二个成员是string对象,又要进行深拷贝,代价大!

优化:存map的迭代器,没必要把数据拷贝出来

#include<queue>
struct CountIterVal
{
    //此时优先级队列里面存放的是迭代器
    bool operator()(const map<string, int>::iterator& l, const map<string, int>::iterator& r)
    {
        //写法1:迭代器解引用 :调用operator* 得到的是pair对象,然后通过.访问成员
        //按照value值排序,排降序
        //return (*l).second > (*r).second;

        //写法2:迭代器->  调用operator-> 得到的是对象的指针,即pair指针
        //然后pair指针->访问成员
        //迭代器->->成员 被系统优化为:迭代器->成员
        return l->second < r->second;
    }
};

void GetFavoriteFruit(const vector<string>& fruits, size_t k)
{
    map<string, int> countMap;
    //1.遍历容器统计每一种水果的次数
    for (auto& str : fruits)
    {
        countMap[str]++;
    }
    //2.把countMap中元素迭代器插入到优先级队列中,所以堆中存放的是pair对象
    //我们要自定义堆的比较方式
    //堆中存放的是map的迭代器,我们要按val进行比较,所以要传仿函数
    //第三个参数传类型
    priority_queue<map<string,int>::iterator,vector<map<string,int>::iterator>,CountIterVal> pq;
    auto it = countMap.begin();
    while (it != countMap.end())
    {
        pq.push(it);
        it++;
    }
    //3.输出堆顶数据
    while (k--)
    {
        //堆中的元素是countMap的迭代器
        //具体看仿函数那里的解析
        cout << pq.top()->first << " " << pq.top()->second << endl;
        pq.pop();
    }
}
int main()
{
    vector<string> fruits{ "芒果","苹果","芒果","雪梨","苹果","芒果","榴莲" };
    GetFavoriteFruit(fruits, 2);
    return 0;
}

priority_queue<int,vector,greater> q 小堆 默认是less 大堆


2.前k个高频单词

https://leetcode.cn/problems/top-k-frequent-words/description/

image-20220511153825575

出现频率从高到低排序,如果出现次数相同,保证字典序排序

image-20220513211551645

方法1:使用map + multimap

class Solution {
public:
    vector<string> topKFrequent(vector<string>& words, int k)
    {
        map<string,int> countMap;
        //1.统计单词次数
        for(auto& str:words)
        {
            countMap[str]++;
        }
        multimap<int,string,greater<int>> sortMap;//按降序排序 
        //2.将countMap的元素放到multimap中
        //multimap按<水果出现次数,水果>,此时key为:水果出现次数
        //默认是按照key排序,所以是根据水果出现次数排序
        //multimap支持重复元素,所以水果出现次数也插入了
        //但是如果是map的话,就不能重复元素
        //所以map<水果出现次数,水果>,会出错
        for(auto& kv:countMap)
        {
            //kv是pair对象
            //注意插入顺序为:<水果出现次数,水果>
            sortMap.insert(make_pair(kv.second,kv.first));
        }
        //3.取出前k个元素放到容器中
        vector<string> v;
        auto it = sortMap.begin();
        while(k--)
        {
            //要的是第二个成员string
            v.push_back(it->second);
            it++;
        }
        return v;
    }
};

方法2:使用map + stable_sort

map的迭代器不支持排序,但是vector的迭代器支持排序,所以可以把map的迭代器放到容器中,然后指定方式排序

vector中也可以存map的元素,也可以存map元素的迭代器,但是后者效率更高

注意:不能使用sort,因为sort函数不稳定!!!要用stable_sort才是稳定排序

class Solution {
public:
    vector<string> topKFrequent(vector<string>& words, int k)
    {
        map<string,int> countMap;
        //1.统计单词次数
        for(auto& str:words)
        {
            countMap[str]++;
        }
        vector<map<string,int>::iterator> sortV;//存放map元素的迭代器
        //2.把countMap中元素的迭代器放到容器中
        auto it = countMap.begin();
        while(it!=countMap.end())
        {
            sortV.push_back(it);
            it++;
        }
        //3.排序,需要指定排序方式,重写仿函数        
        struct CmpIter
        {
            //vector中存放的是map的迭代器
            bool operator()(const map<string,int>::iterator& l,const map<string,int>::iterator& r)
            {
                return l->second > r->second;//按照value排序,排降序
            }
        };
        //注意:不能使用sort,因为sort函数不稳定!!!
        stable_sort(sortV.begin(),sortV.end(),CmpIter());//第三个参数传匿名函数
        //3.把排序好的内容放到vector中,要的是string
        vector<string> v;
        for(int i = 0 ;i<k;i++)
        {
            //sortV[i]是map的迭代器,调用-> 得到pair指针,再用->访问成员
            //迭代器->->成员 优化为迭代器->成员
            //此处的是pair<string,次数>
            v.push_back(sortV[i]->first);//要的是string对象,即第一个成员
        }
        return v;
    }
};

方法3:优先级队列

堆中也可以存map的元素,也可以存map元素的迭代器,但是后者效率更高

注意:此处的仿函数不能简单的写成如下形式:

struct CmpIter
{
    //优先级队列中存放的是map的迭代器
    bool operator()(const map<string,int>::iterator& l,const map<string,int>::iterator& r)
    {
		return l->second < r->second;//按照value排序,排降序
    }
};

原因:当l->second == r->second的时候,要按字典序排序!!!

所以应该写成:

struct CmpIter
{
    //优先级队列中存放的是map的迭代器
    bool operator()(const map<string,int>::iterator& l,const map<string,int>::iterator& r)
    {
        if(l->second == r->second)
        {
            //字典序排序 
            return (l->first + r->first) > (r->first + l->first);
        }
        else
        {
            return l->second < r->second;//按照value排序,排降序
        }
    }
};

class Solution {
public:
    vector<string> topKFrequent(vector<string>& words, int k)
    {
        map<string,int> countMap;
        //1.统计单词次数
        for(auto& str:words)
        {
            countMap[str]++;
        }
        //优先级队列存放的是map的迭代器
        //第三个参数传的是类型
        //需要指定排序方式,重写仿函数  
        //按照value排序      
        struct CmpIter
        {
            //优先级队列中存放的是map的迭代器
            bool operator()(const map<string,int>::iterator& l,const map<string,int>::iterator& r)
            {
                if(l->second == r->second)
                {
                    //字典序排序 
                    return (l->first + r->first) > (r->first + l->first);
                }
                else
                {
                    return l->second < r->second;//按照value排序,排降序
                }
            }
        };
        priority_queue<map<string,int>::iterator,vector<map<string,int>::iterator>,CmpIter> pq;
        //2.把countMap中元素的迭代器放到优先级队列中
        auto it = countMap.begin();
        while(it!=countMap.end())
        {
            pq.push(it);
            it++;
        }
        //3.把优先级队列的内容放到vector中,要的是string
        vector<string> v;
        for(int i = 0 ;i<k;i++)
        {
            //pq.top()是map的迭代器,调用-> 得到pair指针,再用->访问成员
            //迭代器->->成员 优化为迭代器->成员
            //此处的是pair<string,次数>
            v.push_back(pq.top()->first);//要的是string对象,即第一个成员
            pq.pop();
        }
        return v;
    }
};

底层结构

前面对map/multimap/set/multiset进行了简单的介绍,在其文档介绍中发现:

  • 这几个容器有个共同点是:其底层都是按照二叉搜索树来实现的,

但是二叉搜索树有其自身的缺陷,假如往树中插入的元素有序或者接近有序,二叉搜索树就会退化成单支树,时间复杂度会退化成O(N),因此map、set等关联式容器的底层结构是对二叉树行了平衡处理,即采用平衡树来实现


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

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

相关文章

毕业季,各互联网大厂急缺这类 Python 人才!

又快到了应届毕业生找工作的季节了&#xff0c;一大波大厂高薪岗位陆续开放&#xff0c;想拿 Offer、升职加薪的你准备得怎么样了&#xff1f; 今年的招聘力度可以说是近几年最大&#xff0c;比如字节跳动旗下的大力教育&#xff0c;高调宣布&#xff1a;“未来 4 个月&#x…

最新整理Java面试八股文,大厂必备神器

在看这篇文章之前&#xff0c;我想我们需要先搞明白八股文是什么&#xff1f;&#xff1f;&#xff1f; 明清科举考试的一种文体&#xff0c;也称制义、制艺、时文、八比文。八股文章就四书五经取题&#xff0c;内容必须用古人的语气&#xff0c;绝对不允许自由发挥&#xff0…

记录--axios和loading不得不说的故事

这里给大家分享我在网上总结出来的一些知识&#xff0c;希望对大家有所帮助 loading的展示和取消可以说是每个前端对接口的时候都要关心的一个问题。这篇文章将要帮你解决的就是如何结合axios更加简洁的处理loading展示与取消的逻辑。 首先在我们平时处理业务的时候loading一般…

OpenAI 重磅发布 ChatGPT iOS 客户端!

公众号关注 “GitHubDaily” 设为 “星标”&#xff0c;每天带你逛 GitHub&#xff01; 今天凌晨&#xff0c;OpenAI 正式发布了 iOS 客户端&#xff01; 这代表你可以直接在 iPhone 和 iPad 上直接使用 ChatGPT 进行聊天了。 该客户端基于 Whisper 开源模型&#xff0c;集成了…

用排列组合来编码通信(七)——《我的5/4张牌的预言》

早点关注我&#xff0c;精彩不错过&#xff01; 从《5张牌的预言》开始&#xff0c;前面介绍了3个拓展思路&#xff0c;分别从引入额外信息解放选牌&#xff08;Eigens value&#xff09;&#xff0c;引入正反信息来编码&#xff08;ups and downs&#xff09;&#xff0c;继续…

从零开始学架构——可扩展架构模式

可扩展架构模式的基本思想和模式 软件系统与硬件和建筑系统最大的差异在于软件是可扩展的&#xff0c;一个硬件生产出来后就不会再进行改变、一个建筑完工后也不会再改变其整体结构 例如&#xff0c;一颗 CPU 生产出来后装到一台 PC 机上&#xff0c;不会再返回工厂进行加工以…

从零玩转前后端加解密之SM2-sm2

title: 从零玩转前后端加解密之SM2 date: 2022-08-21 19:42:00.907 updated: 2023-03-30 13:28:48.866 url: https://www.yby6.com/archives/sm2 categories: - 加密算法 - 从零玩转系列 tags: - 加密算法 - sm2 前言 SM2是国家密码管理局于2010年12月17日发布的椭圆曲线公钥…

工业互联网UWB定位系统源码,支持自定义开发

工厂人员定位系统&#xff0c;采用UWB定位技术&#xff0c;通过在厂区内部署一定数量的定位基站&#xff0c;以及为人员、车辆、物资佩戴标签卡的形式&#xff0c;实时获取人员精确位置&#xff0c;精度高达10cm。 文末获取联系 工厂人员定位系统可实现物资/车辆实时定位&#…

不同厂家对讲机耳塞耳挂/领夹型988对讲机如何写频改频点/频率能互相通信

988型号都是很多厂家代工出来的,代工出来默认的频点都不一样,有可能买回来的2个不同厂家生产的对讲机,这样它们要能通讯,必须要同频点才能互通,它一般出厂设定16个频道,长按+和-键来切换频道。 需要用到typeC 的写频线,其实是用CH430芯片的usb写频线,可以找厂家要写频线…

编程语言中,循环变量通常都用 i?你知道为什么吗?

01 前天&#xff0c;我在朋友圈发了一个问题&#xff1a; 为什么编程中&#xff0c;循环变量通常都是用 i ? 没想到&#xff0c;回复的人这么多&#xff01;要连翻好几页。 这个问题&#xff0c;有 2/3 的人回答正确&#xff0c;有少部分人知道&#xff0c;但是不太确定。 习惯…

Hadoop基础学习---2、Hadoop概述

1、Hadoop概述 1.1 Hadoop是什么&#xff1f; 1、Hadoop是一个又Apache基金会所开发的分布式系统基础架构。 2、主要解决海量数据的存储和海量数据的分析计算。 3、广义上来说&#xff0c;Hadoop通常是指一个更广泛的概念——Hadoop生态圈。 1.2 Hadoop 优势&#xff08;4高…

六级备考28天|CET-6|翻译井冈山|2021年12月|8:20~9:40+ ~10:17

目录 四级翻译5篇必练真题 六级翻译5篇必练真题 井冈山 四级翻译5篇必练真题 ①2023年3月一卷①自驾游 ②2022年 12月一卷③立秋 ③2022年6月一卷①拔苗助长 ④2021年 12月一卷②大运河 ⑤2021年6月一卷③普洱茶 六级翻译5篇必练真题 ①2023年3月一卷②郑和下西洋 ②2022年…

微服务多模块:Springboot+Security+Redis+Gateway+OpenFeign+Nacos+JWT (附源码)仅需一招,520彻底拿捏你

可能有些人会觉得这篇似曾相识&#xff0c;没错&#xff0c;这篇是由原文章进行二次开发的。 前阵子有些事情&#xff0c;但最近看到评论区说原文章最后实现的是单模块的验证&#xff0c;由于过去太久也懒得验证&#xff0c;所以重新写了一个完整的可以跑得动的一个。 OK&#…

nvidia-smi 失效解决

服务器重启后&#xff0c;跑模型发现&#xff1a; RuntimeError: Attempting to deserialize object on a CUDA device but torch.cuda.is_available() is False. If you are running on a CPU-only machine, please use torch.load with map_locationtorch.device(cpu) to ma…

Linux常用命令——hping3命令

在线Linux命令查询工具 hping3 测试网络及主机的安全 补充说明 hping是用于生成和解析TCPIP协议数据包的开源工具。创作者是Salvatore Sanfilippo。目前最新版是hping3&#xff0c;支持使用tcl脚本自动化地调用其API。hping是安全审计、防火墙测试等工作的标配工具。hping优…

【开源项目】Nepxion Aquarius实现分布式锁、缓存、ID生成、限速的源码解读

项目地址 项目地址 https://toscode.gitee.com/nepxion/Aquarius 项目介绍 Nepxion Aquarius是一款基于Redis Zookeeper的分布式应用组件集合&#xff0c;包含分布式锁&#xff0c;缓存&#xff0c;ID生成器&#xff0c;限速限流器。它采用Nepxion Matrix AOP框架进行切面架构…

天工开物 #6 Git 分支管理与版本发布

Git 版本管理系统由 Linux 的作者 Linus Torvalds 于 2005 年创造&#xff0c;至今不到二十年。 起初&#xff0c;Git 用于 Linux Kernel 的协同开发&#xff0c;用于替代不再提供免费许可的 BitKeeper 软件。随后&#xff0c;这一提供轻量级分支的分布式版本管理系统得到了开源…

产品经理被气的脸都绿了!

见字如面&#xff0c;我是军哥&#xff01; 本来今天不想发文了&#xff0c;想躺平一下&#xff0c;毕竟今天周五了嘛。 可是今天早上一位买了我《技术人核心能力》的程序员学员发来私信&#xff0c;说他给产品经理上了一课&#xff0c;声称产品经理当时脸都绿了&#xff0c;并…

浅浅的理解MVI

MVI 的概念 官网解释&#xff1a; https://developer.android.google.cn/topic/architecture?hlzh-cn MVI在架构分层上和MVP没有本质区别&#xff0c;但区别主要体现在架构风格和编程思想上。 MVI 是 Model-View-Intent 的缩写&#xff0c;它也是一种响应式 流式处理思想的…

【Linux高级 I/O(2)】如何使用阻塞 I/O 与非阻塞 I/O?——select()函数

上次我们虽然使用非阻塞式 I/O 解决了阻塞式 I/O 情况下并发读取文件所出现的问题&#xff0c;但依然不够完美&#xff0c;使得程序的 CPU 占用率特别高。解决这个问题&#xff0c;就要用到本文将要介绍的 I/O 多路复用方法。 何为 I/O 多路复用 I/O 多路复用&#xff08;IO m…