目录
1、前言
在好多面试题中,会有海量数据量的场景,那么遇到这些问题该怎么去入手,今天来看看位图和布隆过滤器以及哈希切割的思想,本篇文章仅仅为个人学习过程中的记录,如果有错误,请各位友友们指出,
2、位图
面试题:给40亿个不重复的无符号整数,没排过序。给一个无符号整数,如何快速判断一个数是否在这40亿个数中。
1. 遍历,时间复杂度O(N)
2. 排序(O(NlogN)),利用二分查找: logN
3. 位图解决
数据是否在给定的整形数据中,结果是在或者不在,刚好是两种状态,那么可以使用一个二进制比特位来代表数据是否存在的信息,如果二进制比特位为1,代表存在,为0代表不存在。
所谓位图,就是用每一位来存放某种状态,适用于海量数据,数据无重复的场景。通常是用来判断某个数据存不存在的。
位图的模拟实现
//bitset.hpp
#pragma once
#include <iostream>
#include <vector>
using namespace std;
namespace ltx {
class bitset {
public:
bitset(size_t size) :_v(size), _size(0) {}
// 将which比特位置1
void set(size_t which) {
size_t i = which / 8;
size_t j = which % 8;
if (!((_v[i] >> j) & 1)) {
_v[i] |= (1 << j);
_size++;
}
}
// 将which比特位置0
void reset(size_t which) {
size_t i = which / 8;
size_t j = which % 8;
if (!((_v[i] >> j) & 1)) {
_v[i] &= ~(1 << j);
_size--;
}
}
// 检测位图中which是否为1
bool test(size_t which) {
size_t i = which / 8;
size_t j = which % 8;
return (_v[i] >> j) & 1;
}
size_t size()const { return _size; }
private:
vector<char> _v;
size_t _size;
};
}
位图的应用场景
1. 快速查找某个数据是否在一个集合中
2. 排序 + 去重
3. 求两个集合的交集、并集等
4. 操作系统中磁盘块标记
3、布隆过滤器
布隆过滤器是由布隆(Burton Howard Bloom)在1970年提出的 一种紧凑型的、比较巧妙的概率型数据结构,特点是高效地插入和查询,可以用来告诉你 “某样东西一定不存在或者可能存在”,它是用多个哈希函数,将一个数据映射到位图结构中。此种方式不仅可以提升查询效率,也可以节省大量的内存空间。
他使用的是哈希的思想,但是可能会发生冲突,不存在是精确的,但是存在是不精确的。
布隆过滤器的模拟实现
//BloomFilter.hpp
#pragma once
#include "bitset.hpp"
namespace ltx {
struct KeyToInt1 {
size_t operator()(const string& s) {
// BKDR
size_t value = 0;
for (auto ch : s)
{
value *= 31;
value += ch;
}
return value;
}
};
struct KeyToInt2 {
size_t operator()(const string& s) {
size_t hash = 5381;
for (auto ch : s)
{
hash += (hash << 5) + ch;
}
return hash;
}
};
struct KeyToInt3 {
size_t operator()(const string& s) {
size_t hash = 0;
for (long i = 0; i < s.size(); i++)
{
if ((i & 1) == 0)
{
hash ^= ((hash << 7) ^ s[i] ^ (hash >> 3));
}
else
{
hash ^= (~((hash << 11) ^ s[i] ^ (hash >> 5)));
}
}
return hash;
}
};
// 假设布隆过滤器中元素类型为K,每个元素对应3个哈希函数
template<class K, class KToInt1 = KeyToInt1, class KToInt2 = KeyToInt2, class KToInt3 = KeyToInt3>
class BloomFilter
{
public:
BloomFilter(size_t size) // 布隆过滤器中元素个数
: _bmp(5 * size), _size(size)
{}
// 插入数据
bool Insert(const K& key) {
KToInt1 k1;
KToInt2 k2;
KToInt3 k3;
size_t i1 = k1(key) % (5 * _size);
size_t i2 = k2(key) % (5 * _size);
size_t i3 = k3(key) % (5 * _size);
_bmp.set(i1);
_bmp.set(i2);
_bmp.set(i3);
return true;
}
// 检测在不在
bool IsInBloomFilter(const K& key) {
KToInt1 k1;
KToInt2 k2;
KToInt3 k3;
size_t i1 = k1(key) % (5 * _size);
size_t i2 = k2(key) % (5 * _size);
size_t i3 = k3(key) % (5 * _size);
if (!_bmp.test(i1))
return false;
else if (!_bmp.test(i2))
return false;
else if (!_bmp.test(i3))
return false;
return true;
}
// 位图不支持删除
private:
ltx::bitset _bmp;
size_t _size; // 实际元素的个数
};
}
海量数据巨量的题目,和解决(这里的解决完全是我自己个人的想法,如果有错误,请各位大佬指出来)
求两个文件的交集:精确算法和近似算法:
给两个文件,分别有100亿个query,我们只有1G内存,如何找到两个文件交集?分别给出精确算法和近似算法。如何扩展BloomFilter使得它支持删除元素的操作
答:不管使用精确算法还是近似算法,首先都要对这100亿个query进行哈希切割,
近似算法:
直接使用BloomFilter进行两个文件切割后的哈希切块query进行统计求交集
精确算法:
对文件进行哈希切割后,可以使用哈希表对两个文件的分割块求交集
对BoolFilter进行扩展,让其支持删除元素的操作:
可以使用可以将key的模型改成key_value的模型,记录每个比特位被标记的次数,删除的时候这个比特位的次数-1,如果设计好的算法,减少哈希碰撞的几率,使用两位比特位就可以解决删除的操作
位图应用变形:1个文件有100亿个int,1G内存,设计算法找到出现次数不超过2次的所有整数
答:对位图进行变形或者封装,这样每个数据,就可以得到两个可操作比特位,也就可以表示四种状态,分别可以表示为:
00表示未出现过
01表示出现过一次
10表示出现过两次
11表示出现过两次以上
然后再对这些数据进行统计处理,最后就可以找出出现次数不超过两次的整数了
给两个文件,分别有100亿个整数,我们只有1G内存,如何找到两个文件交集?
答:可以使用两个位图,分别映射统计两个文件中出现的整数,最后对比两张位图就可以得出两个文件的交集
给定100亿个整数,设计算法找到只出现一次的整数?
对位图进行变形或者封装,这样每个数据,就可以得到两个可操作比特位,也就可以表示四种状态,分别可以表示为:
00表示未出现过
01表示出现过一次
10表示出现过两次以上
然后再对这些数据进行统计处理,最后就可以找出出现过一次的整数了
给一个超过100G大小的log file, log中存着IP地址, 设计算法找到出现次数最多的IP地址? 与上题条件相同,如何找到top K的IP?
先对这个日志文件进行哈希切割,然后使用哈希表对一个切割后的IP地址进行统计出现的次数,
使用建小堆的方式,统计top k