Map类:pair键值对|map的基本操作|operator[]

I.Map类

0x00:介绍

cpp 复制代码
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;

STL是map的关联式容器,按照特定顺序存储关联,由KeyValue组合而成的元素

𝑣𝑎𝑙={𝑘:𝑣}
它们组合成键值对Key通常用于排序和唯一的表示元素value中存贮与key关联的内容

key和value的类型额可以不同,在map内部,key和value通过成员类型value_type绑定

value_type 实际上就是 pair 类型:

cpp 复制代码
typedef pair value_type;

底层基于红黑树,是一种自平衡的二叉搜索树,能确保在插入和删除元素时维持良好的性能。

map 通常被实现为二叉搜索树 ,更准确地说是平衡二叉搜索树(红黑树)

底层基于红黑树,是一种自平衡的二叉搜索树,能确保在插入和删除元素时维持良好的性能。

0x01 pair类型

map 的insert 接口就是是一个value_type,我们先来搞清楚什么是 value_type

value_type 实际上就是 pair 类型,在 map 中称之为 键值对

cpp 复制代码
typedef pair<const Key, T> value_type;

pair 是在库里面就定义好了的,SGI-STL 中关于键值对 的定义如下

cpp 复制代码
template<class T1, class T2>
struct pair {
	typedef T1 fisrt_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) 
	{}
};

0x02 map插入(insert)

我们假设 dict 变量的数据类型为 map<string, string>,我们可以如何插入数据呢?

我们可以直接调用 pair 的构造函数来插入:

cpp 复制代码
dict.insert(pair<string, string>("sort", "排序"));

似乎比较麻烦?我们可以用匿名对象的方式来写:

cpp 复制代码
pair<string, string> kv("insert", "插入");
dict.insert(kv);

还有更爽的方式,调用神奇的 make_pair !我们戏称之为 "做对子" :

cpp 复制代码
dict.insert( make_pair("left", "左边") );

make_pair 是一个函数模板,使用时不用声明参数,可自动推导出对应的类型。

cpp 复制代码
template<class T1, class T2>
pair<T1, T2> make_pair(T1 x, T2 y) {
    return (pair<T1, T2>(x, y) );
}

还有更强大的方式插入,直接用 { } :

cpp 复制代码
dict.insert( {"right", "右边"} );

0x03 遍历

cpp 复制代码
map<string, string>::iterator it = dict.begin();

map 的迭代器的底层相似于链表的迭代器。这里的类型真的是很长,写起来很费劲......

还有 auto 关键字

cpp 复制代码
auto it = dict.begin();

下面我们来写遍历部分,我们发现似乎不行:

当然不行!本质上是因为 C++ 不支持一次返回两个值,这里的 * 就是 it->operator*() 。

但是可以打包,我们可以把这些返回值打包成一个结构数据 ------ pair

这里有两种方法,第一种方法就是在 *it 中分别提取 first 和 second:

cpp 复制代码
auto it = dict.begin();
while (it != dict.end()) {
    cout << (*it).first << ":" << (*it).second << " ";
	it++;
}
cout << endl;

结果如下:

我们可以看到,map 也是会自动排序的。当然,我们还可以用 -> 操作符

cpp 复制代码
auto it = dict.begin();
while (it != dict.end()) {
	cout << it->first << ":" << it->second << " ";
	it++;
}
cout << endl;

0x04 统计次数方式

现在我们要统计一些数据,比如我们需要统计这些水果的个数:

"苹果", "西瓜", "苹果", "香蕉", "草莓", "柠檬"

"西瓜", "苹果", "柠檬", "柠檬", "西瓜", "橙子"

"草莓", "柠檬", "香蕉", "苹果", "橙子", "柠檬"

我们就可以使用 map 来存储,pair 我们就可以用 string + int 类型来组合

cpp 复制代码
map<string, int> count_map;   // 统计水果的个数

我们先用 "最老实" 的方式来进行统计,用迭代器进行检查。

检查很简单,如果这个元素在map 中就次数++,如果不在就将元素 insertmap 中:

cpp 复制代码
map<string,int>CountMap;
for(auto&str:arr){
   map<string,int>::iterator it = CountMap.find(str);
   if(it!=CountMap.end()){
         it->second++;
   }
   else{
         CountMap.insert(make_pair(str,1));//CountMap.insert({str,1});
   }

打印一下:

cpp 复制代码
void test_map2() {
	string arr[] = {
		"苹果", "西瓜", "苹果", "香蕉", "草莓", "柠檬",
		"西瓜", "苹果", "柠檬", "柠檬", "西瓜", "橙子",
		"草莓", "柠檬", "香蕉", "苹果", "橙子", "柠檬"
	};
 
	// 统计
	map<string, int> count_map;
	for (auto& str : arr) {
		map<string, int>::iterator it = count_map.find(str);
		if (it != count_map.end()) {
			it->second++;
		}
		else {
			count_map.insert(make_pair(str, 1));
		}
	}
	
	// 打印
	for (auto& kv : count_map) {
		cout << kv.first << ":" << kv.second << endl;
	}
	cout << endl;
}

结果如下:

当然,我们也可以这么做:

cpp 复制代码
for (auto& str : arr) {
	pair< map<string, int>::iterator, bool >ret
    = count_map.insert(make_pair(str, 1)); 
}

auto一下

cpp 复制代码
for (auto& str : arr) {
	auto ret = count_map.insert(make_pair(str, 1)); 
}

为什么可以这么做呢???

我们来看下 map 的 insert 接口的原型:

cpp 复制代码
std::pair<iterator, bool>insert(const value_type& value);

我们可以看到 insert 的返回值是一个 pair<iterator, bool> 类型。

插入成功, second 就是 true;如果插入失败, second 就是 false。

cpp 复制代码
map<string, int> count_map;
for (auto& str : arr) {
	auto ret = count_map.insert(make_pair(str, 1)); 
	if (ret.second == false) {
		ret.first->second++;
	}
}

结果如下:

🔑 解读:ret.first->second++ 不好理解?实际上这里是两个pair 嵌套在一块的!一个 pair 是 insert 的返回值,一个 pair 是节点。insert 的返回值里是 pair<iterator, int> ,其中第一个是迭代器,所以我们取first 。而迭代器里是节点 pair<string, int> ,我们要让次数 +1,所以我们节点里我们取 second

实际运用中这两种方法我们几乎都不用,因为有更加🐂🍺的方式!非常恐怖的方式!

cpp 复制代码
for (auto& str : arr) {
	count_map[str]++;
}

下面我们来看一下operator[]

0x05 map::operator[]

底层:

cpp 复制代码
mapped_type& operator[] (const key_type& k) {
	return (*((this->insert(make_pair(k, mapped_type()))).first)).second;
}

好长的一段啊!!!这也太复杂了吧!!!我们让他简洁一些:

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

🔑 解读:目的是在关联容器中通过键访问元素,如果元素不存在,则插入一个具有默认值的新元素,并返回对这个元素的引用。首先 const key_type& k 是传入的键,表示要查找或插入的元素的键值make_pair(k, mapped_type()) 创建了一个 pair 对象 ,该对象的第一个元素是传入的键 k,第二个元素是使用 mapped_type 的默认构造函数创建的默认值。this->insert(...) 调用关联容器的 insert 函数,该函数用于将一个键-值对插入容器中。返回值是一个pair ,其first 成员指向插入的元素second 成员指示是否插入成功 。(this->insert(...).first) 获取返回的 std::pair 对象的 first 成员,即指向插入的元素的迭代器。*((this->insert(...).first)) 解引用上一步中获取的迭代器,得到插入的元素。(*((this->insert(...).first)).second 再次解引用,获取插入的元素的 second 成员,即与传入的键关联的值。return (*((this->insert(...).first)).second; 返回通过**operator[]**访问或插入的元素的引用。

这样就使得operator[]的实现:如果是第一次出现,就先插入。插入成功后会返回插入的节点中的次数 0 的引用,对它 ++ 后变为 1。如果是第二次插入,插入失败,会返回它所在节点的迭代器的次数,再 ++。

II.Map算法题

cpp 复制代码
class Solution {
public:vector<int> topKFrequent(vector<int>& nums, int k) {

我们看到题目中提到要记录整数K的出现频率,除了暴力解法的遍历之外,还可以想到使用map<int,int> CountMap 来解决,第一个int表示记录的元素值,第二个int表示出现的次数

那么这道题的解法就很明了了

方法一:TopK问题,我们可以使用堆来求解,这道题我们直接建立一个容量为K的最小堆

cpp 复制代码
class Solution {
public:
//仿函数,出现频率更高的插入到最小堆
    struct Comp{
        bool operator()(const pair<int,int> &a,const pair<int,int> &b){
            return a.second>b.second;
        }
    };
    vector<int> topKFrequent(vector<int>& nums, int k) {
//定义Map来存储数据和出现次数
        unordered_map<int,int> mp;
        for(auto m:nums){
            mp[m]++;
        }
//建立最小堆
        priority_queue<pair<int,int>,vector<pair<int,int>>, Comp> pq;
        int c=0;//记录堆内元素个数
        for(auto m:mp){
            if(c<k){
                pq.push(m);//堆大小不足K,直接入堆
                c++;
            }
            else{
              //  cout<<m.first<<" "<<m.second<<endl;
               //堆已满K个元素,档期那数字的频率>堆顶最小频率时
                if(m.second>pq.top().second){
                    //cout<<m.second<<endl;
                    pq.pop();//弹出堆顶
                    pq.push(m);//加入当前更大频率的数字
                }
            }
        }
//从最小堆中取出前K个元素,存入vector中返回
        vector<int> ans;
        for(int i=0;i<k;i++){
            ans.push_back(pq.top().first);
            pq.pop();
        }
        return ans;
    }
};

priority_queue<
    pair<int,int>,    // 第1个:堆里存什么类型 <数字,频率>
    vector<pair<int,int>>, // 第2个:底层容器(固定写法)
    Comp              // 第3个:比较规则(我们写的最小堆规则)
> pq;

方法二:快速选择

cpp 复制代码
class Solution {
public:
    struct Compare{
        bool operator()(const pair<int,int>& kv1,const pair<int,int>& kv2)
        {
            return kv1.second>kv2.second;
        }
    };
    vector<int> topKFrequent(vector<int>& nums, int k) {
       //创造一个map来计数,first:int ,因为这是个整数数组
       //second:int ,计数
       map<int,int>CountMap;
       for(auto&e:nums){
            CountMap[e]++;
       }
       //放到vector里面排序
       vector<pair<int,int>> v(CountMap.begin(),CountMap.end());
       sort(v.begin(),v.end(),Compare());
       //前k个
       vector<int> ret;
       int i = 0;
       while(k--)
       {
           ret.push_back(v[i++].first);
       }
       return ret;
    }
};
相关推荐
澈2071 小时前
C++核心:封装与static静态成员实战指南
开发语言·c++·算法
田梓燊1 小时前
力扣:146.LRU 缓存
算法·leetcode·缓存
_深海凉_1 小时前
LeetCode热题100-杨辉三角
算法·leetcode·职场和发展
ShineWinsu1 小时前
对于Linux:进程间通信IPC(匿名管道)的解析
linux·c++·面试·进程·通信·管道·ipc
wuyoula1 小时前
全新多平台电商代付商城源码
开发语言·c++·ui·小程序·php源码
玖疯子1 小时前
IT疑难杂症诊疗室:系统性故障排查指南
开发语言·php
码云数智-大飞1 小时前
OpCache 原理深挖:从字节码缓存到预加载(Preloading)的实战配置
java·开发语言
小O的算法实验室1 小时前
2025年SEVC,面向进化计算的学习注入式优化,深度解析+性能实测
算法·论文复现·智能算法·智能算法改进
handler011 小时前
进程状态流转的本质:Linux 内核队列与底层数据结构解密
linux·运维·c语言·数据结构·c++·笔记·学习