[C++ 系列] 86. 海量数据处理

文章目录

      • 0. 前言
      • 1. 位图应用
      • 2. 布隆过滤器应用
      • 3. 哈希切割
      • 4. 倒排索引
      • 5. 后续补充

0. 前言

该文章会长期收录一些关于 海量数据处理 的常见问题,在面试中很容易被问到,希望做以记录帮助到读者。

1. 位图应用

1. 给定 100亿 个整数,设计算法找到只出现一次的整数?

  • 方法一
  • 遇到海量数据问题,首先得分析数据大小。如果我们需要存放一个 int 范围的所有数据,即 4G 个数据,我们仅需要 4G / 8 = 512M 大小的空间就够了。
  • 根据位图的性质,我们采用一个位图是无法得到只出现一个的整数的。在此很容易想到 双位图 的做法:
    • 用位图 1 来确定数字是否出现,若出现对应位置置 1
    • 用位图 2 来确定数字是否再次出现,当位图 1 的对应位置已经为 1 时,且该数字再次出现,即在位图 2 进行置 1
  • 这样便能够查找得到只出现一个的整数,所用两个 512M 的位图,那么就是额外 1G 的空间

  • 方法二
  • 这个问题也可以用一个大位图来进行解决,以前的 512M 的位图表现了 int 型数据的所有情况,但只能表现出现过,并不能表现多次出现。究其原因还是只用了 1 个比特位来表示,那么就只能产生 0 、1 这两种情况,在此我们采用 2 个比特位表示一个数字的出现情况,这样就能得到 4 中情况,出现0、1、2、3次,也就是两张普通位图的合并结果,这样做也是 1G 的空间。参照我们普通位图的实现 [C++ 系列] 84. 位图的概念及应用 稍加改造就能解决该问题。

参见代码如下:

#include
#include
#include

typedef struct TwoBitSet
{
	// 数组
    size_t *bts;
    // 数据的范围
    size_t range;
}TwoBitSet;

void TBSInit(TwoBitSet *tbs,size_t range)
{
    assert(tbs);
    // 需要两位表示一个数据,所以除以16,
    // (range >> 4) + 1计算需要多少个整型
    tbs->bts = (size_t *)malloc(((range >> 4) + 1) * sizeof(size_t));
    assert(tbs->bts);
    memset(tbs->bts, 0,((range >> 4) + 1) * sizeof(size_t));
    tbs->range = range;
}
int TBSGetValue(TwoBitSet *tbs, size_t x)//
{
    assert(tbs);
    // 计算x在数组里下标
    size_t index = x >> 4;
    // 因为两位表示一位,需要乘2
    size_t num = x % 16 * 2;
    // 不能改变tbs->bts[index],所以设置value
    int value = tbs->bts[index];
    // 将value向右移
    value >>= num;
    // 任何数和3(11)&为任何数
    return value & 3;
}
void TBSSetVaule(TwoBitSet *tbs, size_t x, size_t value)//设置值
{
    assert(tbs);
    // 计算x在数组里下标
    size_t index = x >> 4;
    // 因为两位表示一位,需要乘2
    size_t num = x % 16 * 2;
    if (value == 0)//设置为00
    {
        // 先将3左移到要设置的位,然后取反,保证在这两位为0,
        // 然后&,这两位为0,其他为不变
        tbs->bts[index] &= ~(3 << num);
    }
    // 要设置为01
    else if (value == 1)
    {
   		// 设置1 
        tbs->bts[index] |= (1 << num);
        tbs->bts[index] &= ~(1 << (num + 1));
    }
    // 要设置为10
    else if (value == 2)
    {
    	// 设置1
        tbs->bts[index] |= (1 << (num + 1));
        // 设置0
        tbs->bts[index] &= ~(1 << num);
    }
    // 要设置为11
    else if (value == 3)
    {
        tbs->bts[index] |= (3 << num);
    }       
}

// 销毁
void BTSDestory(TwoBitSet *tbs)
{
    assert(tbs);
    free(tbs->bts);
    tbs->bts = NULL;
    tbs->range = 0;
}

2. 给两个文件,分别有 100 亿个整数,我们只有 1G 内存,如何找到两个文件交集?

  • 如问题 1:一个文件 512M,文件 A 中数据出现一次将位图 1 中该位设置为 1,再出现不用设置
  • 文件 B 中数据出现一次将位图 2 中该位设置为 1,再出现不用设置(一个整型 32 位,存 32 个数据),然后位图1 & 位图 2,& 结果为 1 的位则是两个文件的交集。

  • 当所给的内存再小的话, 例如仅给 512M 的内存,那我们就需要对该文件进行文件切分。具体操作就是:设置一种 / 多种哈希规则,将大文件进行切分成小的文件,那么相同的数字在同种哈希映射下会出现在相同的小文件中。然后分别对各个小文件进行找交集即可。主要考察文件分割。

3. 位图应用变形:1 个文件有 100 亿个 int1G 内存,设计算法找到出现次数不超过 2 次的所有整数

  • 该问题解法同问题 1 的解法。两张位图两位表现 4 种情况其中 00 出现 0 次,01 出现 1 次,10 出现 2 次, 11 出现 2 次以上。扫描 01、10 这两种情况就能够解决该问题了。

2. 布隆过滤器应用

1. 给两个文件,分别有 100亿 个 query,我们只有 1G 内存,如何找到两个文件交集?分别给出精确算法和近似算法

  • 精确算法参考位图应用问题 2 即可
  • 近似算法就采用布隆过滤器即可,将第一个文件的所有内容放进布隆过滤器,再对第二个文件的所有内容在布隆过滤器中查找其是否存在。则为一种近似的求交集算法。

2. 如何扩展布隆过滤器使得它支持删除元素的操作

  • 需要对布隆过滤器进行简单改造,将每一位改造为一个计数器,一旦用到该位置不再执行置 1 的操作,而是将该计数器 ++,当删除数据时也不再是置 0,而是将计数器 --,当位置为 0 时,说明该数据不存在。但是这个计数器到底设置多大呢?这得考虑到数据的大小,若设置为 int 将有 32 来表示数据存在与否,但是这样完全丧失了布隆过滤器的优点。若设置为 char 这样会很容易产生计数回绕,因为它只能保存 256 种情况。

3. 哈希切割

1. 给一个超过 100G 大小的 log filelog 中存着 IP 地址, 设计算法找到出现次数最多的 IP 地址? 与上题条件相同,如何找到top K的IP?如何直接用Linux系统命令实现?

  • IP 地址是一个点分十进制,unsigned_int,最简单粗暴的方法就是直接进行排序,设置 4G 内存对所有的 IP 地址进行标志,也就是拿 4G * 4 = 16G 个空间进行处理。在服务器上 16G 并不是什么难事情。但是效率就很低下了。
  • 采用哈希切割的方式,设置哈希函数,对文件进行拆分,那么相同的数据通过同样的哈希函数肯定存放在相同的小文件中。能知道一个 IP 地址是一个 4 字节的整数,我们可以通过后 2 字节有 2^16 种可能性,将其分为 65536 个桶,然后 65536 个桶中会存在 65536 种可能性,这个可能性来自前 2 个字节。然后可以在 65536 个桶中查找到其桶内的出现次数最多的 IP,然后再将这些进行再排序,就可以得到第一名。同理得到 TOP K

  • Linux 首先使用 sort 指令排序 log_file,这样就可以得到一个升序排序,此时相同的 IP 将会相邻。再采用 uniq 指令去重,这里需要 uniq -c 将会将合并了多少行直接显示在前面,再使用 sort -nr 其中 sort -n 会对数字进行排序,即对合并后的行号大小进行排序。sort -nr 即对合并后行号进行降序排序。再采用 head - K 即可得到前 K 重复 IP。综合就是:sort log_file | uniq -c | sort -nr | head -k

4. 倒排索引

1. 给上千个文件,每个文件大小为 1K—100M。给 n 个词,设计算法对每个词找到所有包含它的文件,你只有 100K 内存

  • 简单理解下倒排索引:在文件中找对应单词,那么就是正序索引。那么拿着这 n 个词找对应文件就是倒排索引。扫描所有文件,若该单词在某文件出现的话就在单词后面记录下文件号即可。这样扫描完毕所有的文件就能够解决这个问题。至于这 100M 的文件仅有 100K 的内存,只需要对文件进行切割就好了。

5. 后续补充

Trie树 即哈希多叉树,字典树,单词查找树,前缀树,其用于在大量字符串中快速检索。

跳跃链表、跳跃表、跳表等相关海量数据问题。

你可能感兴趣的:([C++系列],C++系列,海量数据)