位图与布隆过滤器:高效数据结构解析

  1. 位图 (Bitmap / Bitset)

1.1 介绍

位图是一种极其节省空间的数据结构,用于高效地表示和操作大量布尔值(通常是存在或不存在)。其核心思想是:用一个比特位(bit)来标记一个元素的状态(通常是存在与否)

  • 优点:
    • 空间效率极高: 存储 N 个元素的状态只需要大约 N/8 个字节的内存。
    • 查询和修改速度快: 位运算(与、或、非、异或等)是 CPU 直接支持的基础操作,速度极快。
  • 缺点:
    • 只能表示整数状态: 通常只能表示一个元素是否存在(或最多几种有限状态,如果扩展到多位)。
    • 范围有限: 需要预先确定要表示的元素范围(最大值),如果元素值范围非常大但实际元素稀疏,空间浪费可能仍然存在。
  • 应用场景:
    • 快速查找一个数字是否在一个大集合中出现过(例如,40亿个整数中查找一个数)。
    • 排序(遍历位图即可得到排序结果)。
    • 数据去重。
    • 操作系统的磁盘块管理、内存页管理。

1.2 C++ 实现

C++ 标准库提供了 std::bitset,但这里我们实现一个简化版以理解原理。

cpp 复制代码
class Bitmap {
public:
    explicit Bitmap(size_t max_value) {
        // 计算需要的 char 数量,每个 char 有 8 位
        size_t num_chars = (max_value + 7) / 8; // +7 确保向上取整
        _bits.resize(num_chars, 0);
    }

    // 设置第 n 位为 1
    void set(size_t n) {
        size_t index = n / 8; // 找到在哪个 char 里
        size_t offset = n % 8; // 找到在 char 中的哪一位
        _bits[index] |= (1 << offset); // 使用位或操作设置该位
    }

    // 设置第 n 位为 0
    void reset(size_t n) {
        size_t index = n / 8;
        size_t offset = n % 8;
        _bits[index] &= ~(1 << offset); // 使用位与和取反操作清除该位
    }

    // 检查第 n 位是否为 1
    bool test(size_t n) const {
        size_t index = n / 8;
        size_t offset = n % 8;
        return (_bits[index] & (1 << offset)) != 0; // 使用位与操作检查该位
    }

private:
    std::vector<unsigned char> _bits; // 使用 unsigned char 数组存储比特位
};

2. 布隆过滤器 (Bloom Filter)

2.1 介绍

布隆过滤器是位图的一种扩展,用于高效地判断一个元素是否"可能存在于"一个集合中,或者"一定不存在于"集合中。它牺牲了确定性(可能存在误判),换来了更高的空间效率和插入/查询速度。

  • 核心思想:

    1. 使用一个大的位数组(位图)。
    2. 使用 k 个不同的哈希函数。
    3. 插入元素:将元素分别用 k 个哈希函数计算出 k 个哈希值(对应位数组中的 k 个位置),然后将这 k 个位置都设置为 1。
    4. 查询元素:用同样的 k 个哈希函数计算元素的 k 个位置。如果这 k 个位置 为 1,则认为元素"可能存在";如果其中任何一个位置为 0,则元素"一定不存在"。
  • 优点:

    • 空间效率极高: 远低于存储原始数据或使用哈希表。
    • 插入和查询速度快: 只涉及常数次(k 次)哈希计算和位操作。
  • 缺点:

    • 存在误判率: 可能将不存在的元素误判为存在(False Positive)。但绝不会将存在的元素误判为不存在(False Negative)。
    • 无法删除元素: 因为多个元素可能共享同一个比特位。强行删除会影响其他元素的判断。有支持删除的变种(Counting Bloom Filter),但需要更多空间。
  • 应用场景:

    • 缓存穿透保护(如 Redis 前的屏障)。
    • 垃圾邮件过滤。
    • 爬虫 URL 去重。
    • 数据库查询优化(判断某个键一定不存在)。
    • 分布式系统中判断元素是否存在于远程集合。

2.2 误判率分析

误判率 p 与位数组大小 m、插入元素数量 n、哈希函数数量 k 有关。近似公式为: $$ p \approx (1 - e^{-kn/m})^k $$

2.3 C++ 实现

cpp 复制代码
#include <vector>
#include <functional> // std::hash

class BloomFilter {
public:
    BloomFilter(size_t expected_size, double false_positive_prob)
        : _bit_size(static_cast<size_t>(-expected_size * std::log(false_positive_prob) / (std::log(2) * std::log(2)))),
          _hash_num(static_cast<size_t>(std::log(2) * _bit_size / expected_size)) {
        // 根据预期元素数量和误判率计算位图大小和哈希函数个数
        _bits.resize((_bit_size + 7) / 8, 0); // 向上取整计算字节数
    }

    void add(const std::string& key) {
        for (size_t i = 0; i < _hash_num; ++i) {
            size_t hash_val = hash_i(key, i) % _bit_size;
            size_t index = hash_val / 8;
            size_t offset = hash_val % 8;
            _bits[index] |= (1 << offset);
        }
    }

    bool contains(const std::string& key) const {
        for (size_t i = 0; i < _hash_num; ++i) {
            size_t hash_val = hash_i(key, i) % _bit_size;
            size_t index = hash_val / 8;
            size_t offset = hash_val % 8;
            if ((_bits[index] & (1 << offset)) == 0) {
                return false; // 只要有一位是0,肯定不存在
            }
        }
        return true; // 所有位都是1,可能存在(存在误判)
    }

private:
    std::vector<unsigned char> _bits;
    size_t _bit_size; // 位数组的总比特数
    size_t _hash_num; // 哈希函数的数量

    // 模拟第 i 个哈希函数 (实际应用中应选择不同的优质哈希函数)
    size_t hash_i(const std::string& key, size_t i) const {
        // 示例:使用 std::hash 加种子偏移模拟不同哈希函数
        // 实际应使用如 MurmurHash, FNV 等,或独立参数
        return std::hash<std::string>{}(key + std::to_string(i));
    }
};

3. 总结

  • 位图: 精确表示整数集合的存在性,空间效率极高,操作极快。
  • 布隆过滤器: 概率性地判断任意类型元素的存在性(可能有误判),空间效率比哈希表高得多,插入查询速度也很快。牺牲确定性换取空间和速度。 两者都是处理海量数据、空间敏感问题的利器,根据具体需求(是否需要精确性、元素类型、是否支持删除)选择合适的数据结构。
相关推荐
eso19831 小时前
白话讲述监督学习、非监督学习、强化学习
算法·ai·聚类
xiaoye-duck1 小时前
吃透C++类和对象(下):初始化列表深度解析
c++
chen_jared1 小时前
反对称矩阵的性质和几何意义
人工智能·算法·机器学习
Object~2 小时前
4.const和iota
开发语言·前端·javascript
海天一色y2 小时前
python---力扣数学部分
算法·leetcode·职场和发展
一起努力啊~2 小时前
算法刷题--哈希表
算法·面试·散列表
曼巴UE52 小时前
UE5 C++ GameInstanceSubsystem 在学习
c++·ue5·ue
willingli2 小时前
c语言经典100题 61-70题
c语言·开发语言·算法
我是小疯子662 小时前
深入解析C++右值引用与移动语义
java·开发语言·算法