大数据处理面试题分析
**大数据处理面试题分析**最近学习了关于搜索方面的数据结构--搜索树,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 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<"aaaaaaaaaa")<"aaaaa")<"bbbbbbbbbb")<"bbbbb")<"dddddddddd")<"dddd")<"pause");return 0;
}
7)如何扩展BloomFilter使得它支持删除元素的操作?!
算法分析:
因为布隆过滤器的一个Key对应多个位,所以如果要删除的话,就会有些麻烦,不能单纯的将对应位 全部置为0,因为可能还有其它key对应这些位,所以,需要对每一个位进行引用计数,以实现删除的 操作,那么也就引入了下面这个问题;
8)如何扩展BloomFilter使得它支持计数操作?!
题目分析:
作为上一道题的扩展,我们就要考虑引用计数该怎么实现的问题了,因为需要每一个对应位都需要一个计数,所以每一位至少需要一个int,那么我们就不得不放弃位图了,也就是放弃了最小的空间消耗,我们需要直接以一个就像数组一样的实现,只不过数组的内容存放的是引用计数;
大概实现代码如下:
template
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 _map;
};
“`
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
