海量数据处理问题
一、给一个超过100G大小的 long file,其中存放着IP地址,设计算法找到出现次数最多的IP地址。
问题分析:
100G普通机器内肯定是存放不下的,目前使用的IP地址相当于一个32位的字符串,所以这么大的文件
我们需要切分,但是仅仅切分是不够的,假设我们有1G的可用内存,我们把原文件切分成100份,然后
在每份中遍历,把每个IP出现的次数都统计出来,最后在找出最多的,这种方案虽然可行,但是效率
太低。
解决方案:
我们把100个G的文件分成1000份,每份是512M这样内存是很容易读取的,从磁盘上读取这些IP,
通过字符串哈希函数,转成相应的整数,然后再模上1000,余数是多少就把对应的IP放在对应的文件里边,
这样就保证IP相同的肯定在一个文件里边放着,这也叫hash分桶法,然后问题就转换成了找出这1000
个IP出现最多的一个。
二、与上题条件相同,如何找到TopK的IP,或者直接用Linux系统命令实现?
前半部分解决方案和上题相同,文件分好后;我们建立一个K个数据的小堆,然后依次遍历每个文件,
选出每个文件的前K个数据,然后再对这1000K个数据进行挑选,直到选出K个IP。
Linux命令:
sort long_file | uniq -c | sort -nr -k1 | head -k
sort long_file:对这IP进行排序
uniq -c 打印每一重复行出现的次数
sort -nr -k1 按照重复行出现的次数倒序排列,-k1以第一列排序
head -k 取出前k多的IP
三、给定100亿个整数,设计算法找到只出现一次的整数?
解决方案:
整数一共有42亿多个,这里100亿个肯定有重复的,
这个题我们考虑可以用位图来解决,但是位图只能表示一个数存在还是不存在,所以应对这个题我们
需要变动,我们用两个位,来表示一个数,00表示不存在,01表示出现一次,10表示出现一次以上,
我们知道1G=2^30字节大概有10亿多字节,一字节有8个位,那么1G空间就能处理50多亿数,足够
解决这个题了,如果面试官想让你500M解决这个问题,你可以将正整数和负整数分开处理就行。
四、给定两个文件,分别有100亿个整数,我们只有1G内存,如何求出两文件的交集。
解决方案:
我们将文件分成1000份,每个小文件都标号,对A,B两个大文件的100亿个整数进行切分,每个数
对1000求余,余数为几就放在对应的文件之中,最后比较的时候,我们只取编号相同的小文件去比较,
(将100亿个数分到1000个文件当中,每个文件大小512M,每次取文件A,B进行比较,正好1G)
五、一个文件有100亿个int,1G内存,设计算法找到不超过两次的所有整数。
这个题和第三个题类似,不过这次我们关心的是出现一次和两次的。
六、给两个文件,分别有100亿个query,我们只有1G内存,如何找到两个文件的交集?
给出精确算法和近似算法。
解决方案:精确算法和第三题类似。
近似算法我们可以用布隆过滤器解决。
将一个文件中的数都映射在布隆过滤器中,然后另一个文件在这里边找,如果这个位置是1说明存在,
如果不是1说明不存在,因为这种可能存在误判,所以是近似算法,但是效率高。
#pragma once
//布隆过滤器
#include "BitMap.h"
struct __HashFunc1
{
size_t BKDRHash(const char *str)
{
register size_t hash = 0;
while (size_t ch = (size_t)*str++)
{
hash = hash * 131 + ch;
}
return hash;
}
size_t operator()(const string& s)
{
return BKDRHash(s.c_str());
}
};
struct __HashFunc2
{
size_t SDBMHash(const char *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 operator()(const string& s)
{
return SDBMHash(s.c_str());
}
};
struct __HashFunc3
{
size_t RSHash(const char *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 operator()(const string& s)
{
return RSHash(s.c_str());
}
};
struct __HashFunc4
{
size_t APHash(const char *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 operator()(const string& s)
{
return APHash(s.c_str());
}
};
struct __HashFunc5
{
size_t JSHash(const char *str)
{
if (!*str) // 这是由本人添加,以保证空字符串返回哈希值0
return 0;
register size_t hash = 1315423911;
while (size_t ch = (size_t)*str++)
{
hash ^= ((hash << 5) + ch + (hash >> 2));
}
return hash;
}
size_t operator()(const string& s)
{
return JSHash(s.c_str());
}
};
template<class Key=string,
class HashFunc1=__HashFunc1,
class HashFunc2=__HashFunc2,
class HashFunc3=__HashFunc3,
class HashFunc4=__HashFunc4,
class HashFunc5=__HashFunc5>
class BloomFilter
{
public:
BloomFilter(size_t range)
:_bitmap(range * 5)
, range(range * 5)
{}
void Setb(const Key& key)
{
size_t Hash1 = HashFunc1()(key) % range;
size_t Hash2 = HashFunc2()(key) % range;
size_t Hash3 = HashFunc3()(key) % range;
size_t Hash4 = HashFunc4()(key) % range;
size_t Hash5 = HashFunc5()(key) % range;
//为了精确将一个数映射到五个位置
_bitmap.Set(Hash1);
_bitmap.Set(Hash2);
_bitmap.Set(Hash3);
_bitmap.Set(Hash4);
_bitmap.Set(Hash5);
}
bool Testb(const Key& key)
{
size_t Hash1 = HashFunc1()(key) % range;
size_t Hash2 = HashFunc2()(key) % range;
size_t Hash3 = HashFunc3()(key) % range;
size_t Hash4 = HashFunc4()(key) % range;
size_t Hash5 = HashFunc5()(key) % range;
if (_bitmap.Test(Hash1) == false)
return false;
if (_bitmap.Test(Hash2) == false)
return false;
if (_bitmap.Test(Hash3) == false)
return false;
if (_bitmap.Test(Hash4) == false)
return false;
if (_bitmap.Test(Hash5) == false)
return false;
return true;
}
private:
BitMap _bitmap;
size_t range;
};
void TestBloom()
{
BloomFilter<> bm(1024);
bm.Setb("1234");
bm.Setb("abcd");
bm.Setb("//blog.csdn.net/xiaobingrsq/artile/details/73332221");
cout << bm.Testb("11111111") << endl;
cout << bm.Testb("1234") << endl;
cout << bm.Testb("abcd") << endl;
cout << bm.Testb("4321") << endl;
}
七、如何扩展BloomFilter使得它⽀持删除元素的操作?
八、如何扩展BloomFilter使得它⽀持计数操作?
#pragma once
//支持删除的布隆过滤器
struct __HashFunc1
{
size_t BKDRHash(const char *str)
{
register size_t hash = 0;
while (size_t ch = (size_t)*str++)
{
hash = hash * 131 + ch;
}
return hash;
}
size_t operator()(const string& s)
{
return BKDRHash(s.c_str());
}
};
struct __HashFunc2
{
size_t SDBMHash(const char *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 operator()(const string& s)
{
return SDBMHash(s.c_str());
}
};
struct __HashFunc3
{
size_t RSHash(const char *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 operator()(const string& s)
{
return RSHash(s.c_str());
}
};
struct __HashFunc4
{
size_t APHash(const char *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 operator()(const string& s)
{
return APHash(s.c_str());
}
};
struct __HashFunc5
{
size_t JSHash(const char *str)
{
if (!*str) // 这是由本人添加,以保证空字符串返回哈希值0
return 0;
register size_t hash = 1315423911;
while (size_t ch = (size_t)*str++)
{
hash ^= ((hash << 5) + ch + (hash >> 2));
}
return hash;
}
size_t operator()(const string& s)
{
return JSHash(s.c_str());
}
};
template<class Key=string,
class HashFunc1=__HashFunc1,
class HashFunc2=__HashFunc2,
class HashFunc3=__HashFunc3,
class HashFunc4=__HashFunc4,
class HashFunc5=__HashFunc5>
class BloomFIliter
{
public:
BloomFIliter(size_t range)
{
_bit.resize(range * 5, 0);
}
void Set(const Key& key)
{
size_t range = _bit.size();
size_t Hash1 = HashFunc1()(key) % range;
size_t Hash2 = HashFunc2()(key) % range;
size_t Hash3 = HashFunc3()(key) % range;
size_t Hash4 = HashFunc4()(key) % range;
size_t Hash5 = HashFunc5()(key) % range;
_bit[Hash1]++;
_bit[Hash2]++;
_bit[Hash3]++;
_bit[Hash4]++;
_bit[Hash5]++;
}
bool ReSet(const Key& key)
{
size_t range = _bit.size();
size_t Hash1 = HashFunc1()(key) % range;
size_t Hash2 = HashFunc2()(key) % range;
size_t Hash3 = HashFunc3()(key) % range;
size_t Hash4 = HashFunc4()(key) % range;
size_t Hash5 = HashFunc5()(key) % range;
if (_bit[Hash1] == 0 ||
_bit[Hash2] == 0 ||
_bit[Hash3] == 0 ||
_bit[Hash4] == 0 ||
_bit[Hash5] == 0 )
{
return false;
}
_bit[Hash1]--;
_bit[Hash2]--;
_bit[Hash3]--;
_bit[Hash4]--;
_bit[Hash5]--;
return true;
}
bool Test(const Key& key)
{
size_t range = _bit.size();
size_t Hash1 = HashFunc1()(key) % range;
size_t Hash2 = HashFunc2()(key) % range;
size_t Hash3 = HashFunc3()(key) % range;
size_t Hash4 = HashFunc4()(key) % range;
size_t Hash5 = HashFunc5()(key) % range;
if (_bit[Hash1] != 0
&& _bit[Hash2] != 0
&& _bit[Hash3] != 0
&& _bit[Hash4] != 0
&& _bit[Hash5] != 0)
{
return true;
}
return false;
}
private:
vector<size_t> _bit;
};
void TestBllo()
{
BloomFIliter<>bt(1024);
bt.Set("1234");
bt.Set("4567");
bt.Set("abcd");
cout << bt.Test("000000") << endl;
cout << bt.Test("1234") << endl;
cout << bt.Test("4567") << endl;
cout << bt.Test("abcd") << endl;
bt.ReSet("abcd");
cout << bt.Test("abcd") << endl;
}
九、给上千个文件,每个文件1k-100M。给n个词,设计算法找到每个词所有包含它的文件,
你只有100k内存。
0:用一个文件info准备来保存n个词,和所有包含它的文件的信息。
1:首先把n个词,分成x份,对每一份生成一个布隆过滤器,(因为如果生成一个,内存可能不够),
把生成的所有布隆过滤器,放到外存的一个filter文件当中。
2:将内存分为两块缓冲区,一块用于每次读入一个布隆过滤器,一次用于读入一个文件,大文件
可以分为几个小文件,但是必须存储是属于哪个大文件的。
3:每次从文件里读取一个单词,拿出一个布隆过滤器,如果存在就更新info信息,不存在就取出
下一个布隆过滤器,如果所有的布隆过滤器都不存在这个词,就拿出下一个词,直到将文件读完。
十、有一个词典包含n个英文单词,现在任意给一个字符串,设计算法找到包含这个字符串的所有单词。
这道题我们可以用单词查找树来解决,Trie树,是一种树形结构,是一种哈希树的变种,典型的应用
是用于统计,排序和保存大量字符串,所以经常被搜索引擎用于文本词统计,它的优点是利用字符
串的公共前缀,来减少查询时间,最大限度减少无谓的字符串比较,查找效率比哈希表高。
用给出的n个英文单词建立一个Trie树,用任意字符串与字典树的每个单词相比较,在每一层找到和
任意字符串第一相等的字母,依次往下找,直到包含这个任意字符串。
十一、 搜索引擎会通过日志文件把用户每次检索使用的所有检索串都记录下来,
每个查询串的长度为1-255字节。
第一步:先进行记录的统计。
我么可以用哈希的方式来进行,我们使用key–value的结构,我们只需要将文件遍历一遍即可。
时间复杂度为O(n);
第二步:我们可以用堆排序找到前K个,时间复杂度为O(n*lgn)
十二、有一个1G大小的一个文件,里面每一行是一个词,词的大小不超过16字节,
内存限制大小是1M。返回频数最高的100个词。
解决方案:
因为内存限制太小,所以我们采用分治的思想,我们可以对这个文件进行hash切分,分为两千个文件,
每个文件都是500Kb的大小,然后我们对每个词进行字符串哈希函数,除以2000,将余数放到
对应的文件之中,然后我们统计出每个词出现的次数利用hash_map,利用小堆找出出现次数最多的
100个词,这下两千个文件存放着100个词,然后我们可以再用归并排序,找出频数最多的100个词。
十三、上千万或上亿数据(有重复),统计其中出现次数最多的前N个数据。
先用hash_map,再用堆排序找到前N个数据。
十四、一个文本文件,大约有一万行,每行一个词,要求统计出其中最频繁出现的前10个词,
请给出思想,给出时间复杂度分析。
用字典树统计出每个次出现的次数,时间复杂度为O(n*le)(le单词的平均长度),然后是找出
出现最频繁的前十个词,时间复杂度为O(lgn*10);所以总的时间复杂度是它俩之中较的一个。
十五、怎么在海量数据中找出重复次数最多的一个?
先做hash,再求模映射为小文件,求出每个小文件中重复次数最多的一个,并记录重复次数,
然后找到每个小文件中最多的一个可以用归并排序。
十六、在2.5亿个整数中找出不重复的整数,注,内存不足以容纳这2.5亿个整数。
这个和我们前边讲的一样,用两个位,00不存在,01,只有一个,10一个以上。
然后我们位图需要5亿个位也就是大概需要70M就够了。
十七、 给定a、b两个文件,各存放50亿个url,每个url各占64字节,
内存限制是4G,让你找出a、b文件共同的url?
4G内存就是320亿位。
url是3200亿字节,也就是320G,我们进行hash切分,切成1000个文件每个文件也就是320M,
对应的url进行hash字符串算法求余,把a和b都这样做,把每个a都做成布隆过滤器,相应的编号b
在其中找,然后就可以的到共同的url。
上一篇: 十个海量数据问题及解决方案
下一篇: 数据结构--最小优先队列