大数据处理面试题分析

来源:互联网 发布:淘宝我的评价在哪 编辑:程序博客网 时间: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个文件互相比较完!

51个⽂文件有100亿个int1G内存,设计算法找到出现次数不超过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)给上千个文件,每个⽂件大小为1K100M。给n个词,设计算法对每个词找到所有包含它的文件,你只有100K内存!10)有⼀一个词典,包含N个英⽂文单词,现在任意给⼀一个字符串,设计算法找出包含这个字符串的所有英⽂文单词!

最后两道题留着和大家一块学习!

3 0
原创粉丝点击