位图
腾讯面试题:给40亿个不重复的无符号整数,没排过序,给一个无符号整数,如何快速判断一个数是否在40亿个数中。
- 遍历??时间复杂度为O(N)
- set使用红黑树??
40亿个整数等于160亿byte,1G等于1024MB等于1024*1024kb等于1024*1024*1024byte,换算过来1G等于10亿byte,那么160亿byte大约等于16G。这种情况也仅仅只是使用顺序表来存储,如果使用容器(红黑树),那么内存消耗会更大。
可以利用哈希的直接定址法,如果需要确定的值有42亿个值,可以通过2^32bit位置来确定对应值是否存在,1个字节有8个bit位也就是可以确定2^3,此时42亿个数据需要2^29字节,即500MB的内存空间即可。

通过在顺序表中保存int的值,一个int有4个字节也就是代表32个值,如果需要存储例如80,只需要用80 / 32 = 2,也就是80在下标为2的int值中的第80%32的bit位置。

【科普】计算机底层存储可能是按照大端机,也可能是小端机存储方式。
cpp
#pragma once
#include <vector>
#include <iostream>
using namespace std;
template<size_t N>
class BitSet
{
public:
BitSet()
{
_a.resize(N / 32 + 1);
}
// 将x映射的值标记成1
void set(size_t x)
{
size_t i = x / 32;
size_t j = x % 32;
_a[i] |= (1 << j);
}
// 将x映射的值标记成0
void reset(size_t x)
{
size_t i = x / 32;
size_t j = x % 32;
_a[i] &= ~(1 << j);
}
// 检查一个值在不在
bool Test(size_t x)
{
size_t i = x / 32;
size_t j = x % 32;
return _a[i] & (1 << j);
}
private:
vector<int> _a;
};
库里面实现的位图bitset

位图相关问题:
(1)给定100亿个整数,设计算法找到只出现一次的整数
【解释】
方法1:改造位图,将两个bit位表示,00表示没有出现,01表示出现一次,10表示出现2次及2次以上。
方法2:使用库中位图结构,设计两个位图,表示一个整数。
(2)给两个文件,分别有100亿个整数,我们只有1G内存,如何找到两个文件交集?
【解释】设计两个位图结构,对应的位置进行&。对应位置都为1,那么这个值就是交集。
(3)位图应用变形:1个文件有100亿个int,1G内存,设计算法找到出现次数不超过2次的所有整数。
【解释】设计两个位图结构,00表示没有出现,01表示出现一次,10表示出现2次,11表示2次以上。
布隆过滤器
引入布隆过滤器
在使用抖音、新闻等客户端看内容时,会不停地推荐新的内容,而在每次推荐时都会进行去重,去掉已经看过的内容。此时的问题是,这些客户端推荐系统是如何实现推送去重的?
【解释】使用服务器记录用户看过的所有历史记录,当推荐系统推荐新闻时会从每个用户的历史记录中进行筛选,过滤掉那些已经存在的记录。
++问题:那么如何快速查找?++
【目前已知的方式】
- 用哈希表存储用户记录,缺点:浪费空间
- 用位图存储用户记录,缺点:位图一般只能处理整形,如果内容编号是字符串,就无法处理了。
- 将位图与哈希结合使用,即布隆过滤器。

布隆过滤器的概念
布隆过滤器是由布隆(Burton Howard Bloom)在1970年提出的一种紧凑的、比较巧妙地概率型数据结构,特点是高效地插入和查询,可以用来说明"某样东西一定不存在或者可能存在",他就是用多个哈希函数,将一个数据映射到位图结构中,此种方式不仅可以提升查询效率,也可以节省大量的内存空间。


将一个字符串使用多种方式映射到不同的位置上,只有当一个字符串进行查询时,每一个方式都可以对应上即说明这个字符串是对应的字符串,但是也会出现误判的情况,但是此时的误判率大大降低。
- 此时字符串存在:误判率较低
- 此时字符串不存在:准确
cpp
#pragma once
#include <iostream>
#include <bitset>
using namespace std;
struct BKDPHash
{
size_t operator()(const string& str)
{
size_t hash = 0;
for (auto ch : str)
{
hash = hash * 131 + ch;
}
return hash;
}
};
struct APHash
{
size_t operator()(const string& str)
{
size_t hash = 0;
for (int i = 0; i < str.size(); ++i)
{
size_t ch = str[i];
if ((i & i) == 0)
{
hash ^= ((hash << 7) ^ ch ^ (hash >> 3));
}
else
{
hash ^= (~((hash << 11) ^ ch ^ (hash >> 5)));
}
}
return hash;
}
};
struct DJBHash
{
size_t operator()(const string& str)
{
size_t hash = 5381;
for (auto ch : str)
{
hash += (hash << 5) + ch;
}
return hash;
}
};
template<size_t N,
class K = string,
class Hash1 = BKDPHash,
class Hash2 = APHash,
class Hash3 = DJBHash>
class BloomFilter
{
public:
void Set(const K& key)
{
size_t hash1 = Hash1()(key) % N;
size_t hash2 = Hash2()(key) % N;
size_t hash3 = Hash3()(key) % N;
_bs.set(hash1);
_bs.set(hash2);
_bs.set(hash3);
}
// 布隆过滤器一般不支持reset
bool Test(const K& key)
{
size_t hash1 = Hash1()(key) % N;
size_t hash2 = Hash2()(key) % N;
size_t hash3 = Hash3()(key) % N;
if (_bs.test(hash1) == false) return false;
if (_bs.test(hash2) == false) return false;
if (_bs.test(hash3) == false) return false;
return true; // 存在误判
}
private:
bitset<N> _bs;
};

应用场景:
1.不需要精确的场景,例如快速判断昵称是否注册过(容忍误判)。如果需要精确的场景,使用布隆过滤器当作筛板,如果不在直接返回,如果存在进一步到数据库查找并精准判断。

布隆过滤器的场景问题:
1.给两个文件,分别有100亿个query,我们只有1G内存,如何找到两个文件交集?分别给出精确算法和近似算法?
【答】近似算法使用布隆过滤器,精确算法使用哈希切分。
2.如何扩展BlookFilter使得其支持删除元素的操作?
【答】多个位标识一个值,使用引用计数。
