大数据处理面试题分析
来源:互联网 发布:淘宝我的评价在哪 编辑:程序博客网 时间:2024/04/30 21:24
**大数据处理面试题分析**最近学习了关于搜索方面的数据结构--搜索树,AVL树,红黑树,哈希表,哈希表的扩展-位图,布隆过滤器;大数据在当前社会下是非常火的,同样随之而来的是在IT行业进行面试的时候,也就变成了经常问的话题;所以我就尝试利用所学知识对以下大数据方面的面试题进行分析:我发现,如果是内存放不下,并且位图等数据结构也用不上的情况下,哈希切分则扮演重要的角色!当然,我建议如果你对数据结构有了一定的了解之后再看这篇文章比较好,否则可能看了也白看!譬如说,你不知道什么是堆,哈希表,位图,那就可以不看了,哈哈!我们学习这些数据结构的目的就在于知道用它的实际应用,应该知其所以然!(个人所学和理解程度有限,希望读者可以指出不足,共同学习!)
1)给⼀一个超过100G⼤小的log file, log中存着IP地址, 设计算法找到出现次数最多的IP地址?!
题目分析:
首先,我们看到的是一个大小为100G的一个日志文件,第一点就应该想到的是一般计算机的内存放不下吧; 肯定需要切分,切成100份,把这100个文件当作是大小为100的哈希表,每份只有1G的大小,就可以读入内存进行处理,内存中该怎么处理?往下看;
然后,再看题目要求:找出出现次数最多的IP;
那么,内存中怎样处理的依据就出来了,我们当然需要把相同的IP放入同一个文件中,因为要统计出现次数;
而怎样做到把相同的IP放入到这100个小文件中的同一个文件呢?
哈希函数!
我们在学习哈希表的时候,知道关键字key映射相应的存储位置,其中对于字符串有相应的哈希函数将字符串转为对应的key,那么我们把IP当作字符串做同样的处理,得到key,然后根据index = key%100决定存在哪一个文件中,而相同的IP得到的key肯定相同,也就进入了同一个文件;
类似于下面这个函数得到key: size_t HashFunc1(const K& key) { const char* str = key.c_str(); unsigned int seed = 131; unsigned int hash = 0; while(*str) { hash = hash*seed + (*str++); } return(hash& 0x7FFFFFFF); };
上面的这些步骤才是重点(就是哈希切分的过程),下面的就是常规过程了;
现在我们要重新依次把这100个文件读入内存,每读入内存一个文件,我们要看的事情是 统计这个文件中出现次数最多的IP(快一点方法,利用key就是IP,value代表出现次数的key_value的结构的搜索树就可以),用MAX记录下来,
再读第二个文件,统计出新文件中出现次数最多的IP,和MAX比较,如果大于MAX就更新MAX,继续上述步骤!
这就是基本的算法了;本题的重点是哈西切分!
2)与上题条件相同,如何找到top K的IP?如何直接⽤用Linux系统命令实现?!
题目分析:
本题也是进行哈希切分,这个过程我就省略了;
第一题只要找最多的一个,而这道题要找最多的K个;其实我们学过数据结构的话,应该一看到top K就应该
想到 堆排序,那么问题其实就简单了,当然,我们还是得统计第一个文件中每个IP出现的次数(方法同第一题),
如果是一棵搜索树的话,取其中的K个(key_value结构)结点建一个最小堆;
注意:这里是建最小堆!!! 我们一看到top K就下意识的想回答建最大堆,那就中计了;
为什么是建最小堆?
因为当前这堆中的K个IP不一定就是top K,所以我们还得往堆里插入;而又必须保证堆的大小还是K,而且堆中的K个结点的IP还是当前出现次数最多的K个,所以我们得和堆中的结点互换,那么,重点是该用哪个结点和新插入的结点交换?
最符合条件的肯定是堆顶元素,前提是最小堆,那么堆顶元素就是当前K个IP出现次数最小的,而我新来的这个IP出现的次数比堆顶的大,那么我就可以交换了,交换之后对堆进行一次向下调整,保证堆顶元素仍是最小!直到第一个结点构建的搜索树都比较完,然后,再读入第二个文件,构建搜索树,统计出现次数,和堆顶元素的比较,重复以上过程,直到结束!
最后堆中的K个IP就是top K;
3)给定100亿个整数,设计算法找到只出现一次的整数!
题目分析:
这道题就简单了,但是看到同样一个问题是100亿个整数,大约是35G的大小,但是整数能表示的最大范围也就是2的32次方
那么大约就是16G的大小,那么剩下的就都是重复的数,这道题没有规定死内存大小,但是16G还是太大了,如何继续缩小
需要的内存呢?
<位图>
我们可以利用位图的特性,位图的一般是一位表示一个key,这里我们需要两个比特位表示一个整数,因为我们需要表示的状态有:00不存在, 01出现一次,10出现多次(>=2次),11不用,那么我们大约需要1G的内存就可以了;
最后遍历找出出现一次的整数即可!
4)给两个⽂文件,分别有100亿个整数,我们只有1G内存,如何找到两个文件交集!
题目分析:
又是100亿个整数,但是是要找两个文件的交集,这个用上面位图的方法就不太好做了,但是我们用数据结构处理不了的话,不是还有哈希切分嘛!
我们将两个文件分别切分为1000个文件,每个文件的大小就几十兆的样子,先对文件A分的1000个文件里的整数进行哈希分配,即取出来整数模除1000,使相同的整出进入相同的文件,文件B切分的1000个文件进行同样的处理,然后分别拿A哈希切分好的第一个文件和B哈希切分好的第一个文件对比,找出交集存到一个新文件中,依次类推,直到2000个文件互相比较完!
5)1个⽂文件有100亿个int,1G内存,设计算法找到出现次数不超过2次的所有整数!
题目分析:
类似于第3题,利用位图求解!只不过11代表的是出现次数超过了两次的整数!
6)给两个⽂文件,分别有100亿个query,我们只有1G内存,如何找到两个⽂文件交集?分别给出精确算法和近似算法!
题目分析:
先看题目就和第四题很像,但是不同的是分别要近似算法和精确算法;
精确算法,就是第四题的解法;
近似算法:布隆过滤器
即利用哈希算法和位图实现;
先对A和B文件进行哈希切分,然后取A文件的第一个切分文件进行布隆过滤器的插入,然后取B文件的第一个切分文件进行布隆过滤器的查找,即判断是否存在!
需要注意的是为了减少误判,我们一般用多个哈希函数生成多个对应位,即一个字符串对应多个比特位;
为什么布隆过滤器是近似算法,是因为它的不存在是确定的,存在是不确定的,即一个字符串对应5个位, 如果有一个位为0,则这个字符串肯定不存在,如果一个字符串对应的5个位都为1,但是这个字符串却不
一定存在,因为可能这5个位都是被其它字符串的对应位置为1的!
大概实现代码如下,代码只是核心算法部分:
template<class K = string>class BloomFilter{ size_t HashFunc1(const K& key) { const char* str = key.c_str(); unsigned int seed = 131; unsigned int hash = 0; while(*str) { hash = hash*seed + (*str++); } return(hash& 0x7FFFFFFF); }; size_t HashFunc2(const K& key) { const char* str = key.c_str(); register size_t hash = 0; while (size_t ch = (size_t)*str++) { hash = 65599 * hash + ch; //hash = (size_t)ch + (hash << 6) + (hash << 16) - hash; } return hash; } size_t HashFunc3(const K& key) { const char* str = key.c_str(); register size_t hash = 0; size_t magic = 63689; while (size_t ch = (size_t)*str++) { hash = hash * magic + ch; magic *= 378551; } return hash; } size_t HashFunc4(const K& key) { const char* str = key.c_str(); register size_t hash = 0; size_t ch; for (long i = 0; ch = (size_t)*str++; i++) { if ((i & 1) == 0) { hash ^= ((hash << 7) ^ ch ^ (hash >> 3)); } else { hash ^= (~((hash << 11) ^ ch ^ (hash >> 5))); } } return hash; } size_t HashFunc5(const K& key) { const char* str = key.c_str(); if(!*str) return 0; register size_t hash = 1315423911; while (size_t ch = (size_t)*str++) { hash ^= ((hash << 5) + ch + (hash >> 2)); } return hash; } public: BloomFilter(const size_t size) :_size(size) ,_bitmap(size) {} void Set(const K& key) { size_t hash1 = HashFunc1(key); _bitmap.Set(hash1%_size); size_t hash2 = HashFunc1(key); _bitmap.Set(hash2%_size); size_t hash3 = HashFunc1(key); _bitmap.Set(hash3%_size); size_t hash4 = HashFunc1(key); _bitmap.Set(hash4%_size); size_t hash5 = HashFunc1(key); _bitmap.Set(hash5%_size); } void ReSet() {} bool Test(const K& key) { size_t hash1 = HashFunc1(key); if(!_bitmap.Test(hash1%_size)) return false; size_t hash2 = HashFunc1(key); if(!_bitmap.Test(hash2%_size)) return false; size_t hash3 = HashFunc1(key); if(!_bitmap.Test(hash3%_size)) return false; size_t hash4 = HashFunc1(key); if(!_bitmap.Test(hash4%_size)) return false; size_t hash5 = HashFunc1(key); if(!_bitmap.Test(hash5%_size)) return false; return true; }private: BitMap _bitmap; size_t _size;};int main(){ BloomFilter<> bloom(32); bloom.Set("aaaaaaaaaa"); bloom.Set("bbbbbbbbbb"); bloom.Set("cccccccccc"); bloom.Set("dddddddddd"); cout<<bloom.Test("aaaaaaaaaa")<<endl; cout<<bloom.Test("aaaaa")<<endl; cout<<bloom.Test("bbbbbbbbbb")<<endl; cout<<bloom.Test("bbbbb")<<endl; cout<<bloom.Test("dddddddddd")<<endl; cout<<bloom.Test("dddd")<<endl; system("pause"); return 0;}
7)如何扩展BloomFilter使得它支持删除元素的操作?!
算法分析:
因为布隆过滤器的一个Key对应多个位,所以如果要删除的话,就会有些麻烦,不能单纯的将对应位 全部置为0,因为可能还有其它key对应这些位,所以,需要对每一个位进行引用计数,以实现删除的 操作,那么也就引入了下面这个问题;
8)如何扩展BloomFilter使得它支持计数操作?!
题目分析:
作为上一道题的扩展,我们就要考虑引用计数该怎么实现的问题了,因为需要每一个对应位都需要一个计数,所以每一位至少需要一个int,那么我们就不得不放弃位图了,也就是放弃了最小的空间消耗,我们需要直接以一个就像数组一样的实现,只不过数组的内容存放的是引用计数;
大概实现代码如下:
template<class K = string>class BloomFilter{ size_t HashFunc1(const K& key) { const char* str = key.c_str(); unsigned int seed = 131; unsigned int hash = 0; while(*str) { hash = hash*seed + (*str++); } return(hash& 0x7FFFFFFF); }; size_t HashFunc2(const K& key) { const char* str = key.c_str(); register size_t hash = 0; while (size_t ch = (size_t)*str++) { hash = 65599 * hash + ch; //hash = (size_t)ch + (hash << 6) + (hash << 16) - hash; } return hash; } size_t HashFunc3(const K& key) { const char* str = key.c_str(); register size_t hash = 0; size_t magic = 63689; while (size_t ch = (size_t)*str++) { hash = hash * magic + ch; magic *= 378551; } return hash; } size_t HashFunc4(const K& key) { const char* str = key.c_str(); register size_t hash = 0; size_t ch; for (long i = 0; ch = (size_t)*str++; i++) { if ((i & 1) == 0) { hash ^= ((hash << 7) ^ ch ^ (hash >> 3)); } else { hash ^= (~((hash << 11) ^ ch ^ (hash >> 5))); } } return hash; } size_t HashFunc5(const K& key) { const char* str = key.c_str(); if(!*str) return 0; register size_t hash = 1315423911; while (size_t ch = (size_t)*str++) { hash ^= ((hash << 5) + ch + (hash >> 2)); } return hash; } public: BloomFilter(const size_t size) { _map.resize(size); for(size_t i = 0; i<_map.size();++i) _map[i] = 0; } void Set(const K& key) { size_t hash1 = HashFunc1(key); size_t hash2 = HashFunc2(key); size_t hash3 = HashFunc3(key); size_t hash4 = HashFunc4(key); size_t hash5 = HashFunc5(key); _map[hash1%_map.size()]++; _map[hash2%_map.size()]++; _map[hash3%_map.size()]++; _map[hash4%_map.size()]++; _map[hash5%_map.size()]++; } bool ReSet(const K& key) { size_t hash1 = HashFunc1(key); if(_map[hash1%_map.size()]==0) return false; size_t hash2 = HashFunc2(key); if(_map[hash2%_map.size()]==0) return false; size_t hash3 = HashFunc3(key); if(_map[hash3%_map.size()]==0) return false; size_t hash4 = HashFunc4(key); if(_map[hash4%_map.size()]==0) return false; size_t hash5 = HashFunc5(key); if(_map[hash5%_map.size()]==0) return false; _map[hash1%_map.size()]--; _map[hash2%_map.size()]--; _map[hash3%_map.size()]--; _map[hash4%_map.size()]--; _map[hash5%_map.size()]--; return true; } bool Test(const K& key) { size_t hash1 = HashFunc1(key); if(_map[hash1%_map.size()] == 0) return false; size_t hash2 = HashFunc2(key); if(_map[hash2%_map.size()] == 0) return false; size_t hash3 = HashFunc3(key); if(_map[hash3%_map.size()] == 0) return false; size_t hash4 = HashFunc4(key); if(_map[hash4%_map.size()] == 0) return false; size_t hash5 = HashFunc5(key); if(_map[hash5%_map.size()] == 0) return false; return true; }private: vector<size_t> _map;};
9)给上千个文件,每个⽂件大小为1K—100M。给n个词,设计算法对每个词找到所有包含它的文件,你只有100K内存!10)有⼀一个词典,包含N个英⽂文单词,现在任意给⼀一个字符串,设计算法找出包含这个字符串的所有英⽂文单词!
最后两道题留着和大家一块学习!
- 大数据处理面试题分析
- 一道大数据处理的面试题
- 十道海量大数据处理面试题
- 【数据结构】大数据处理面试题解法
- 大数据面试题分析
- 大数据面试题分析
- 海量数据处理面试题与十个方法大总结分类
- 海量数据处理面试题与十个方法大总结
- 面试题——大数据处理解题思路
- 海量数据处理 常用思路 大公司面试题
- 海量数据处理面试题
- 海量数据处理面试题
- 海量数据处理面试题
- 海量数据处理面试题
- 海量数据处理面试题
- 海量数据处理面试题
- 面试题:海量数据处理
- 海量数据处理面试题
- 数据库常见知识
- 浅析Lua调试器的实现
- C/C++拾遗1
- 安装jdk,配置Java环境变量
- 基于spring4.3+hibernate5.1+Struts2.5整合的个人记账管理系统
- 大数据处理面试题分析
- 自定义View常用细节性集合
- throw与throws
- C/C++拾遗2
- Qt5.7.0编译移植到Linux-Arm-A9
- 框架中Web容器套路,对于框架的一点理解
- SQLServer2008 视图创建实例
- Java开发环境搭建、Eclipse优化设置
- delegate_sdgj