map是C++ STL中的关联容器,存储的是键值对(key-value),可以通过key快速索引到value。map容器中的数据是自动排序的,其排序方式是严格的弱排序(stick weak ordering),即在判断Key1和Key2的大小时,使用“<”而不是“<=”。map 使用二叉搜索树实现,STL map的底层实现是红黑树。

map有几个值得注意的地方:map的赋值运算是深拷贝,即调用map_a = map_b后,map_a中的元素拥有独立的内存空间。map的[]运算比较有意思,当元素不存在的时候会插入新的元素;在map中查找key是否存在时可以用find或count方法,find查找成功返回的是迭代器,查找失败则返回mymap.end(),说明该key不存在;map中的key不允许重复,其count方法只能返回0或1。

map可以做什么?来看一个非常经典的算法面试题:有一个包含100万个整数的大文件,文件的每一行都是一个int型数字,那么如何找到其中出现次数最多的10个数?

从数据存储方面分析,通常一个int是4个字节,100万个int约为4MB字节。在处理这个问题时,需要记录每个整数出现的次数,次数也用一个int来存储,因此总共需要约8MB字节的内存。从数据处理方面分析,可以用一个key和value都为int的map容器来存储数据,通过逐行扫描文件,将文件中的整数作为key保存到map中,对应的value为其在文件中出现次数,这样就完成了数据输入。

对于求N个数中的最大K个数问题,是典型的top K问题,其解决方案是利用一个大小为K的数组构建小顶堆,然后依次对N个数中的元素和堆顶元素做比较,如果满足条件则替换堆顶元素,并重新构建小顶堆,否则处理下一个数据。待所有数据都处理完毕后,小顶堆中的数据集合即为最大的K个数,最后通过堆排序输出即可。

Talk is cheap,show me the code。整个程序的代码非常简短。

#include <string>
#include <vector>
#include <map>
#include <algorithm> // make_heap算法
#include <cstdio>
#include <string.h>
#include <climits> // 从文件中读取ID值,保存到map中
int ReadDataFile(const std::string &file_name,
std::map<int, int> &map_result) {
FILE *fp = fopen(file_name.c_str(), "r");
if (fp == NULL) {
perror("Open file fail");
return -1;
}
map_result.clear();
char buf[100];
int id;
while (true) {
memset(buf, 0, sizeof(buf));
if (fgets(buf, sizeof(buf), fp) == NULL) {
break;
}
id = atoi(buf);
++map_result[id]; // 记录ID出现次数
}
fclose(fp);
return 0;
} // 最终结果
typedef struct Result {
int id; // ID值
int times; // 该ID值出现的次数 Result() {
id = 0;
times = 0;
}
// 根据ID出现次数比较大小
bool operator<(const Result &other) const {
return times < other.times;
}
}Result; // 从map中获取top k的值,并保存到存储Result对象的vector容器中
void GetTopK(std::map<int, int> &map_result,
std::vector<Result> *final_result,
int k) {
// k值可能比map_result中的元素个数要多
size_t min_k = std::min((int)map_result.size(), k);
std::vector<Result> heap;
Result tmp;
for (int i = 0; i < min_k; ++i) {
tmp.id = 0;
tmp.times = INT_MAX; // 初始化为最大
heap.push_back(tmp);
}
// make_heap默认创建的是大顶堆,第三个参数是less
//std::make_heap(heap.begin(), heap.end(), std::less<Result>());
std::make_heap(heap.begin(), heap.end()); for (auto &it : map_result) {
tmp.times = it.second;
// 只需要处理ID出现次数小于堆顶位置元素的情况
if (tmp.times > heap[0].times) {
continue;
}
tmp.id = it.first;
heap[0] = tmp;
std::make_heap(heap.begin(), heap.end());
}
std::sort_heap(heap.begin(), heap.end());
final_result->clear();
final_result->swap(heap);
} int main(int argc, char **argv) {
// 读取文件数据并保存到map
const std::string file_name = "test.data";
std::map<int, int> map_result;
int ret = ReadDataFile(file_name, map_result);
if (ret < 0) {
printf("ERROR: ReadDataFile fail\n");
return -1;
}
printf("map_result size:%lu\n", map_result.size()); // 获取出现次数最少的100个ID值
std::vector<Result> final_result;
GetTopK(map_result, &final_result, 100);
for (auto &it : final_result) {
printf("id: %6d, times: %d\n", it.id, it.times);
}
return 0;
}

map中的元素是按key排序的,这个题目中其实不需要对key进行排序,C++11提供了基于哈希实现的map容器unordered_map,其访问元素的效率比map要高一些。

另外,这个面试题目有非常多的玩法和变种,例如将数字总数100万改成20个亿,同时限制内存使用不超过2GB,大家可以思考一下解决思路。

map功能强大,它的作用当然不只在于解决面试题目,在一个大型后端系统中,几乎到处都能看到map的身影。例如,面试题中的出现次数最多的数字可以想象为频繁非法访问的黑名单ID,可以用一个map来保存黑名单ID和其访问次数,那么如何使用这个map呢?假如有一个保存了很多ID的vector,里面可能有黑名单ID,在处理数据的时候需要将黑名单ID剔除,代码可以这么写:

#include <map>
#include <vector>
#include <string>
#include <iostream> using namespace std; int main() {
vector<int> vi{0, 1, 2, 3, 4, 5};
map<int, string> black_list;
black_list.emplace(3, "aaaa");
black_list[4] = "bbb";
auto it = vi.begin();
while (it != vi.end()) {
if (black_list.find(*it) != black_list.end()) {
vi.erase(it);
} else {
++it;
}
}
cout << *(--it) << endl;
for (it = vi.begin(); it != vi.end(); ++it) {
cout << *it << endl;
}
return 0;
}

想要掌握更多的map原理和使用技巧,推荐大家阅读《C++ Primer》或者网上搜索“C++ map”相关的博客文章,然后主动练习,不断丰富自己的开发武器库。


金句分享

尽管赚钱很好,但拥有有意义的工作和人际关系要比赚钱有价值的多。

——《原则》,作者是瑞·达利欧(Ray Dalio),全球最大对冲基金桥水基金(Bridgewater)创始人。

面试和工作中的map的更多相关文章

  1. 【基础】工作中常用的linux命令,经常会被面试官问到

    前言 面试经常会问到一些Linux操作命令,下面就工作中常用的和面试问的频率较高的命令做详细描述. 常用命令 修改密码:passwd 用户名 切换用户名:su 用户名 查看当前路径:pwd 调整路径: ...

  2. 工作中,ES6 可能掌握这些就足够了

    刚开始用vue或者react,很多时候我们都会把ES6这个大兄弟加入我们的技术栈中.但是ES6那么多那么多特性,我们需要全部都掌握吗?秉着二八原则,掌握好常用的,有用的这个可以让我们快速起飞. 接下来 ...

  3. 工作中常用到的Java集合类有哪些?

    前言 只有光头才能变强. 文本已收录至我的GitHub精选文章,欢迎Star:https://github.com/ZhongFuCheng3y/3y Java集合是我认为在Java基础中最最重要的知 ...

  4. [工作中的设计模式]解释器模式模式Interpreter

    一.模式解析 解释器模式是类的行为模式.给定一个语言之后,解释器模式可以定义出其文法的一种表示,并同时提供一个解释器.客户端可以使用这个解释器来解释这个语言中的句子. 以上是解释器模式的类图,事实上我 ...

  5. [工作中的设计模式]享元模式模式FlyWeight

    一.模式解析 Flyweight在拳击比赛中指最轻量级,即“蝇量级”或“雨量级”,这里选择使用“享元模式”的意译,是因为这样更能反映模式的用意.享元模式是对象的结构模式.享元模式以共享的方式高效地支持 ...

  6. [工作中的设计模式]迭代子模式Iterator

    一.模式解析 迭代子模式又叫游标(Cursor)模式,是对象的行为模式.迭代子模式可以顺序地访问一个聚集中的元素而不必暴露聚集的内部表象 1.迭代子模式一般用于对集合框架的访问,常用的集合框架为lis ...

  7. 工作中使用seajs后的一些总结

    工作中用seajs一段时间了,小小地总结一下. 使用seajs五部曲: 1.布置你项目的目录结构 2.设置seajs的config项,我一般是单独一个js文件--> seajs-config.j ...

  8. iOS 模式详解—「runtime面试、工作」看我就 🐒 了 ^_^.

    引导 Copyright © PBwaterln Unauthorized shall not be *copy reprinted* . 对于从事 iOS 开发人员来说,所有的人都会答出「runti ...

  9. 软件测试人员在工作中如何运用Linux

    从事过软件测试的小伙们就会明白会使用Linux是多么重要的一件事,工作时需要用到,面试时会被问到,简历中需要写到. 对于软件测试人员来说,不需要你多么熟练使用Linux所有命令,也不需要你对Linux ...

随机推荐

  1. F5 IIS Log获取客户端源IP

    1.配置F5启用X-Forwarded-For方法: 1:Local Traffic-Profiles-Http-改"Insert XForwarded For"为Enable 2 ...

  2. 使用 grep 的 -o 和 -E 选项进行正则的精确匹配

    sed 命令可以很好的进行行匹配,但从某一行中精确匹配某些内容,则使用 grep 命令并辅以 -o 和 -E 选项可达到此目的.其中 -o 表示“only-matching”,即“仅匹配”之意.光用它 ...

  3. MySQL 数据库--索引原理与慢查询优化

    索引的原理 本质都是:通过不断地缩小想要获取数据的范围来筛选出最终想要的结果,同时把随机的事件变成顺序的事件,也就是说,有了这种索引机制,我们可以总是用同一种查找方式来锁定数据. 索引的数据结构 b+ ...

  4. 深入浅出SharePoint2012——安装Report Service

    安装顺序 Microsoft .NET Framework 3.5 SP1 report service installation,pls SQLServer2008R2SP1-KB2528583-x ...

  5. 关于 Can't connect to MySQL server on 'localhost' (10061) 的一个解决方案

    问题描述: 使用Navicat for mysql 无法远程连接到本地数据库,提示Can't connect to MySQL server on 'localhost' (10038) . 本地服务 ...

  6. UML设计--人月神教

    任务分配 用例图 类图 活动图 状态图 使用工具 所有图都是用VISO编辑出来的,因为VISO是比较经典工具,也是学校电脑自带的.....

  7. 用 Visual Studio 2012 调试你的ASP程序

    最近搞到一段很值得参考的ASP项目,无奈技术有限,打开看完代码后感觉自己就像从来没学过ASP一样.唉...大神的世界 不过在网上看到一个有趣的方法,可以用Visual Studio 2005来调试AS ...

  8. D3——scale

    d3.scale 比例尺 “Scales are functions that map from an input domain to an output range” Domains 定义域 和 R ...

  9. HTML的CoreText流畅度超过WebView。CoreText第三方框架DTCoreText的介绍

    为什么要用CoreText(富文本)来取代WebView去显示内容.主要的原因就WebView有很大的问题,性能,FPS,卡顿,与原生不搭.这些都是大问题. WebView的缺点 1.直接使用WebV ...

  10. TTransport 概述

    TTransport TTransport主要作用是定义了IO读写操作以及本地缓存的操作,下面来看TIOStreamTransport是如何实现的. public abstract class TTr ...