大数据处理面试题分析

**大数据处理面试题分析**

最近学习了关于搜索方面的数据结构--搜索树,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;
};

“`

  • 5
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值