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. INSERT CLAUSE

    a.single table insert INSERT INTO jobs(job_id,job_title,min_salary,Max_Salary) VALUES('IT_PM','PROJE ...

  2. msysgit 上传文件夹,规范化的日常

    在我们第一次成功的上传到github之后,要上传文件夹的我们要在msysgit里输入些什么呢? 选择要上传的文件夹前一项右键点击git bash here 进入msysgit后 首先初始化,输入 gi ...

  3. 导出当前域内所有用户hash的技术整理

    0x00目标: 导出当前域内所有用户的hash 0x01测试环境: 域控:server2008 r2 杀毒软件:已安装* 域控权限:可使用net use远程登陆,不使用3389 0x02测试方法: ( ...

  4. js图形库

    SVG.js viz.js graphviz的js实现版 raphael d3 (http://d3js.org/) JavaScript InfoVis Toolkit Flotr2 and Env ...

  5. Server Host Cannot be null解决方法

    在用打开Services Directory application 或者访问 某个已发布的地图服务时,出现"Server Host Cannot be null"的错误. 问题的 ...

  6. contOS 网络配置

    设定VirtualBox虚拟网卡的IP地址(现在设定本地机器网卡IP 192.168.56.1  子网掩码255.255.255.0) 设置虚拟机中的网络设置 在虚拟机中选用host-only网络(注 ...

  7. CSS样式命名规则

    1.样式命名外 套: wrap主导航: mainnav子导航: subnav页 脚: footer整个页面: content页 眉: header页 脚: footer商 标: label标 题: t ...

  8. 【Vue】vue.js常用指令

    http://www.cnblogs.com/rik28/p/6024425.html Vue.js的指令是以v-开头的,它们作用于HTML元素,指令提供了一些特殊的特性,将指令绑定在元素上时,指令会 ...

  9. inux下使用自带mail发送邮件告警

    安装mailx工具,mailx是一个小型的邮件发送程序. 具体步骤如下: 1.安装 [root@localhost ~]# yum -y install mailx 2.编辑配置文件 [root@lo ...

  10. 部署Jar包到远程Maven仓库

    在使用maven开发工程时,模块A可能会依赖模块B的jar包,如果两个模块都是在一个工程里,只需要在模块A的pom文件中加入模块B的依赖信息,模块A就可以加载模块B的jar包.但如果模块A与模块B在不 ...