一道面试题,内存受限的情况,如何在海量的数据中找到重复最多的
来源:互联网 发布:张鲁农村淘宝网 编辑:程序博客网 时间:2024/06/11 21:55
昨天,去面试,被问到一题,给你内存500K,一百万条查询数据,每条数据20B,如何寻找到最多的那一条,内存不受限制的话,可以使用hash,之后排序就搞定了,但是内存受限的话,还真没有想过这个问题,因此憋了十分钟左右,还是没有想到,最后放弃了。之后的面试。。。。。。哎,果断的跪了。
回到实验室后的第一件事情,叹气之余,最大的想法就是我要把这个给编写出来,不然还是提高不了自己。
首先说说思路,内存受到限制,一百万条记录不可能放到内存中的,因此必须要把这个大文件(后文称记录文件)想办法分割成多块,每块保持在内存上限之下,要充分的运用硬盘资源,不过这样速度肯定慢下来了。分割多块的方法也不少,从记录文件中先读到差不多的数量就新建一个文件写进去,之后再从记录文件读,之后再新建一个文件写进去,直到记录文件中的内容读完为止,这个方法固然可行,不过后面每条记录出现的次数就不好统计了。所以,我们可以想办法尽可能的把相同的记录写到一个文件中去,这样就便于后面统计。什么方法有这个功能呢,没错,你猜对了,就是hash。
下面使用的就是Daniel J.Bernstein发明的算法,也是目前很有效的哈希函数。
inline long DJBHash(std::string& str){ long hash = 5381; for(int i=0; i<str.size(); ++i) { hash = ((hash << 5) + hash) + str[i]; } return hash;}
有了hash以后,那如何来分割文件呢,假设我们想把记录文件分割成num个,因此我们可以从记录文件中读取记录,之后对每一条记录使用哈希算法,会计算出一个整数,我们让这个整数对num求余,这样就可以保证同样哈希值存放在同一个文件中。具体的代码如下:
/* * @path: 记录文件的完整路径和名称 * @filenum: 生成的文件个数 */void SegmentFile(const char *path, int filenum){ long hash; std::string str; std::fstream fs, filearr[filenum]; fs.open(path, std::fstream::in); while(fs>>str) {//对读取的记录使用哈希算法 hash = DJBHash(str); hash = hash % filenum; if(!filearr[hash].is_open()) { //为了简便,新产生的文件的文件名直接使用数字表示 filearr[hash].open(std::to_string(hash).c_str(), std::fstream::out | std::fstream::in | std::fstream::trunc); if(!filearr[hash].is_open()) { std::cout<<"Open file "<<hash<<" fail."<<std::endl; continue; } }//保存记录到每个相应的文件中 filearr[hash]<<str<<'\n'; } for(int i=0; i<filenum; ++i) { if(filearr[i].is_open()) { filearr[i].close(); } } fs.close();}假设我们要分割成40个文件,那么经过上述函数后,一般会在程序的目录下生成40个文件,文件名为0到39。通过这种方式可以保证每个文件中的字串在其他文件中是不存在的。之后,我们需要做的就是统计每个文件中相同字符串的个数,由于每个文件的大小都在内存上限之内(如果不在上限中,可以使用SegmentFile再分),因此我们可以将它一条一条读到内存中,之后使用STL中的unordered_map来记录每个字符串的频数。代码如下:
/* * @filenum: 生成的文件个数 */void CaculateFrequence(int filenum){std::string str;std::fstream fs[filenum];std::unordered_map<std::string, int> map;for(int i=0; i<filenum; ++i) { map.clear();fs[i].open(std::to_string(i).c_str(), std::fstream::in); if(fs[i].is_open()) { while(fs[i]>>str) { std::unordered_map<std::string, int>::iterator it = map.find(str); if(it == map.end()) { map[str] = 1; } else { it->second += 1; } } fs[i].close(); fs[i].open(std::to_string(i).c_str(), std::fstream::out | std::fstream::in | std::fstream::trunc);if(fs[i].is_open()) { for(std::unordered_map<std::string, int>::iterator it = map.begin(); it != map.end(); ++it) { fs[i]<<it->first<<" "<<it->second<<'\n'; }} } } for(int i=0; i<filenum; ++i) { if(fs[i].is_open()) { fs[i].close(); } }}
完成上面的步骤之后,我们就可以把保存的结果按照字符串出现的频数,由高到低排列出来,我们可以使用大顶堆来排序,马上就想到使用STL中的priority_queue了。在使用priority_queue之前,我们先定义几个东西,后文会使用到。
struct node {int len;//该字符串出现的次数char data[21];//保存每个记录字符串std::fstream *pf;//该字符串保存在哪个文件流中node* next;//下一个节点node():len(0), pf(NULL), next(NULL) {}};
//仿函数,用于priority_queueclass mycomparison {public:mycomparison(bool param = false) { reverse=param; }bool operator()(const node* left, const node* right) {if(reverse) return left->len > right->len;else return left->len < right->len;}private:bool reverse;};
// 这个queue太长了,直接使用typedef把它简化typedef std::priority_queue<node *, std::vector<node*>, mycomparison> pq_t;
现在我们开始使用大顶堆来排序,代码如下:
/* * @filenum: 生成的文件个数 */void EveryFileDesc(int filenum){ pq_t bigheap(mycomparison(false)); std::fstream fs[filenum]; for(int i=0; i<filenum; ++i) { node *head = new node;node *ptr = head;fs[i].open(std::to_string(i).c_str(), std::fstream::in); if(fs[i].is_open()) { //读取每条记录到大顶堆中 while(fs[i]>>ptr->data>>ptr->len) { bigheap.push(ptr);ptr->next = new node;ptr = ptr->next; } fs[i].close(); fs[i].open(std::to_string(i).c_str(), std::fstream::out | std::fstream::trunc); if(fs[i].is_open()) { //从大顶堆中取出数据,保存到文件中 while(!bigheap.empty()){ node* tmp = bigheap.top(); fs[i]<<tmp->data<<" "<<tmp->len<<'\n'; bigheap.pop(); delete tmp; } } } } for(int i=0; i<filenum; ++i) { if(fs[i].is_open()) { fs[i].close(); } }}
使用上面的方法对每个文件排序后,终于到最后一步了,这一步我们就可以进行总的归并了,对多个文件进行归并,还是使用堆吧,这玩意真好用,靠谱!
/* * @k: 前k个重复次数最多的 * @filenum: 生成的文件个数 */void Mearge(int k, int filenum){pq_t bigheap(mycomparison(false));std::fstream fs[filenum], fd;node *head = new node;node *ptr = head;for(int i=0; i<filenum-1; ++i) {node* tmp = new node;ptr->next = tmp;ptr = ptr->next;}ptr = head;//先将每个文件的第一条记录读入堆中,堆中始终维持filenum个节点,保证可以不出现内存溢出for(int i=0; i<filenum; ++i) {fs[i].open(std::to_string(i).c_str(), std::fstream::in);if(fs[i].is_open()) { fs[i]>>ptr->data>>ptr->len;ptr->pf = &fs[i]; bigheap.push(ptr);ptr = ptr->next; }}fd.open("result", std::fstream::out | std::fstream::trunc);if(fd.is_open()) {//从堆中读出数据,可以保证读出的值是依次减小的,结果打印出来并保存在result文件中while(!bigheap.empty()) {if(k) {ptr = bigheap.top(); fd<<ptr->data<<" "<<ptr->len<<'\n'; std::cout<<ptr->data<<" "<<ptr->len<<std::endl;; bigheap.pop();if(!(*(ptr->pf)).eof()) {(*(ptr->pf))>>ptr->data>>ptr->len;bigheap.push(ptr);--k;}}else {break;}}//释放空间for(int i=0; i<filenum; ++i) {ptr = head;head = head->next;delete ptr;}}for(int i=0; i<filenum; ++i) { if(fs[i].is_open()) { fs[i].close(); } }fd.close();}
忘了,还有最后一步的。。。。包装一下嘛,起个名字--TopK :
void topK(int k, int filenum){ std::vector<std::string> vec; std::unordered_map<std::string, int> map; std::fstream fs[filenum]; std::string str; SegmentFile("data.txt", filenum); CaculateFrequence(filenum);EveryFileDesc(filenum);Mearge(k, filenum);}
最最后,就是生成一个简单的字符串集合,本文使用简单的数字代替了,rand函数伪随机生成1000000个0到10000之间的数,保存到名为data.txt的文件中
补上main函数,里面就没有详细的去判断参数的正确与否啦,自己写个analysizeCmd就好啦:
int main(int argv, char **argc){ if(argv != 4) {std::cout<<"Usage: demon path k filenum."<<std::endl;return 0; } topK(argc[1], std::stoi(std::string(argc[2])), std::stoi(std::string(argc[3]))); return 0;}
我的操作都是在linux操作系统下搞的,
编译的话使用: g++ -std=c++11 -g -o demon demon.cpp
我不知道在linux如何去监控一个程序的最大内存使用量,也希望大家能指导我一下,所以只能使用top命令代替了,截图如下:
main函数里面加了一个while(1)无限循环得到的结果,分割的文件数是20个。
参考文章:
常见的hash函数 http://blog.csdn.net/mycomputerxiaomei/article/details/7641221
这个大家都知道的 http://www.cplusplus.com/reference
- 一道面试题,内存受限的情况,如何在海量的数据中找到重复最多的
- 如何在有限的内存的情况下,找到大量数据中重复查询次数最多的语句
- 运用bitmap解决一道海量数据处理面试题:在2.5亿个整数中找出不重复的整数,注,内存不足以容纳这2.5亿个整数。
- 从海量数据中找出重复次数最多的一个
- 计数+分治求海量数据中重复最多的一个
- 海量数据处理中常见的一道面试题
- 怎么在海量数据中找出重复次数最多的一个
- 海量日志数据__怎么在海量数据中找出重复次数最多的一个;提取出某日访问网站次数最多的那个IP;提取出某日访问网站次数最多的前n个IP
- 海量数据选取重复次数最多的n个
- 一道小小的内存申请面试题
- 如何在数组中找出重复次数最多的数
- 如何找到一个数组里面重复次数最多的数
- 海量数据的算法面试题
- 【题目36】百度的一道面试题-找到攻击IP
- 【数据分析面试题】一道 面试题,我的答案
- 海量数据一,从2.5亿个正整数中找到不重复的整数
- 算法系列-大数据面试题-在超大文件中找出访问百度次数最多的IP
- 一道阿里巴巴面试题--海量数据查找
- 字符串处理函数补充strtok,strspn,strcspn,strchr,以及atof、atoi
- cocos2d3.0 Scale9Sprite
- 监控集群的磁盘空间使用量,超阀值发送报警邮件
- C++中的抽象基类(Primer学习)
- map删除元素注意
- 一道面试题,内存受限的情况,如何在海量的数据中找到重复最多的
- sort list
- stop HyperV VM by WMI in powershell
- 为什么Field不能被重写?
- COALESCE / GREATEST / LEAST.
- hdu1394 Minimum Inversion Number
- java学习值swing编程 (二)
- linux命令之删除
- CSS Font