【数据结构】位图及布隆过滤器

位图

位图的概念

位图其实就是哈希表的变形,当需要映射的数据过大时,如果要把数据都映射并存储,需要很大的空间,但是如果我们并不存储数据**,只关心数据的存在状态,那么我们就可以用比特位来标记数据的存在状态,大大节省内存

位图通常情况下用在数据量庞大,且数据不重复的情景下判断某个数据是否存在

位图的实现

构造

底层数据可以用vector存储,方便管理空间

根据模板参数传入要开的位数N,创建一个N位的位图,把位图中所有位初始化为0

一个整形有32个比特位,所有我们需要用到N/32+1个整形(+1是为了防止N不是32整数被的情况下少开位数)

template<size_t N>
class BitSet {
    public:
    BitSet() {
        _bits.resize(N / 32 + 1, 0);
    }
    private:
    std::vector<int> _bits;
};

set

set就是把需要设置的数的状态为置1

由于我们用的是整形存储,所以首先要算出这个状态位在第i个整数第j个比特位

然后再**把1左移j位,然后和第i个整数进行或等运算**

在这里插入图片描述

//把x映射的位标记为1 
void Set(size_t x) {
    assert(x < N);
    //算出x映射的位在第几个整数
    size_t i = x / 32;
    //算出x映射的位在这个整数的第几个位
    size_t j = x % 32;

    //_bit[i] 的第j位标记成1,并且不影响它的其他位

    _bits[i] |= (1 << j);

}

rset

reset就是把需要设置的状态置为0

和set一样算出这个状态位在第i个整数的第j个比特位

然后把1左移j位,并且取反,然后进行与等运算

在这里插入图片描述

//把x映射的位标记为0
void Reset(size_t x) {
    assert(x < N);

    size_t i = x / 32;
    size_t j = x % 32;

    //_bit[i] 的第j位标记成0,并且不影响它的其他位
    _bits[i] &= (~(1 << j));
}

test

test是查看这个数的状态位是否为1,也就是这个数在位图中是否被设置

算出这个状态位在第i个整数的第j个比特位

然后把1左移j位,进行与运算

bool Test(size_t x) {
    assert(x < N);

    size_t i = x / 32;
    size_t j = x % 32;

    //如果第j位是1,结果是非0,就是真
    //如果第j位是0,结果是0,就是假
    return _bits[i] & (1 << j);
}

位图完整代码实现
template<size_t N>
class BitSet {
    public:
    BitSet() {
        _bits.resize(N / 32 + 1, 0);
    }

    //把x映射的位标记为1 
    void Set(size_t x) {
        assert(x < N);
        //算出x映射的位在第几个整数
        size_t i = x / 32;
        //算出x映射的位在这个整数的第几个位
        size_t j = x % 32;

        //_bit[i] 的第j位标记成1,并且不影响它的其他位

        _bits[i] |= (1 << j);

    }

    //把x映射的位标记为0
    void Reset(size_t x) {
        assert(x < N);

        size_t i = x / 32;
        size_t j = x % 32;

        //_bit[i] 的第j位标记成0,并且不影响它的其他位
        _bits[i] &= (~(1 << j));
    }

    bool Test(size_t x) {
        assert(x < N);

        size_t i = x / 32;
        size_t j = x % 32;

        //如果第j位是1,结果是非0,就是真
        //如果第j位是0,结果是0,就是假
        return _bits[i] & (1 << j);
    }
    private:
    std::vector<int> _bits;
};

布隆过滤器

位图可以把数据的存在状态进行映射速度很快,也非常节省空间,但是位图只能映射整形数据,如果要映射的数据是字符串或者自定义类型,那么使用位图就无法进行映射为了解决位图不能映射字符串的问题,就有了布隆过滤器

布隆过滤器的概念

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

在这里插入图片描述


布隆过滤器的误判

要想把字符串映射到布隆过滤器,就得字符串转换成整数,使用字符串哈希算法,可以完成这个操作,但是字符串哈希算法转换成整形,会产生哈希冲突,也就是不同的字符串算出的映射地址是相同的,这时数据的存在状态就会存在误判,这个问题是无法避免的。

布隆提出了一个解决办法,可以降低误判的概率,那就是把一个字符串用多个哈希函数进行运算,计算出多个哈希地址,在进行检测时,多个位置判断都是存在,那么就判断数据可能存在降低了哈希冲突带来的误判概率,但是这种方式并不能确定这个数据一定存在,但是只要判断出三个位置一个状态为0,那么这个数据就一定不存在

也就是说

  • 布隆过滤器判断出数据存在的情况不一定准确可能会存在误判
  • 判断数据不存在的情况一定准确不会误判

布隆过滤器的优缺点

优点

  • 和位图一样,布隆过滤器的插入和查找效率非常高,时间复杂度为:O(K), (K为哈希函数的个数,一般比较小),与数据量大小无关
  • 哈希函数相互之间没有关系,方便硬件并行运算
  • 布隆过滤器不需要存储元素本身,在某些对保密要求比较严格的场合有很大优势
  • 能够承受一定的误判,布隆过滤器比其他数据结构有这很大的空间优势
  • 数据量很大时,布隆过滤器可以表示全集,其他数据结构不能
  • 使用同一组散列函数的布隆过滤器可以进行交、并、差运算

缺陷

  • 误判率,即存在假阳性(False Position),即不能准确判断元素是否在集合中
  • 不能获取元素本身
  • 一般情况下不能从布隆过滤器中删除元素
  • 如果采用计数方式删除,可能会存在计数回绕问题

布隆过滤器的实现

布隆过滤器需要用到多种字符串哈希算法,这里用三种哈希算法

struct HashBKDR{
	//BKDRHash算法
	size_t operator()(const std::string& s) {
		size_t value = 0;
		for (auto ch : s) {
			value += ch;
			value *= 131;
		}
		return value;
	}
};

struct HashAP {
	//AP Hash算法
	size_t operator()(const std::string& s) {
		register size_t hash = 0;
		size_t ch;
		for (long i = 0; i < s.size(); i++){
			ch = s[i];
			if ((i & 1)  == 0){
				hash ^= ((hash << 7) ^ ch ^ (hash >> 3));
			}
			else{
				hash ^= (~((hash << 11) ^ ch ^ (hash >> 5)));
			}
		}
		return hash;
	}
};

struct HashDJB {
	//DJB Hash算法
	size_t operator()(const std::string& s) {
		register size_t hash = 5381;
		for(auto ch : s)
		{
			hash += (hash << 5) + ch;
		}
		return hash;
	}
};

更多字符串哈希算法参考

字符串哈希算法

结构

布隆过滤器是位图的拓展底层用位图实现

template<size_t N, class K = std::string,
	class Hash1 = HashBKDR,
	class Hash2 = HashAP,
	class Hash3 = HashDJB>

class BloomFither {
//......
private:
	BitSet<N> _bitset;
};

set

set就是把三个哈希算法算出的哈希地址全都映射进布隆过滤器

void Set(const K& key) {
    size_t i1 = Hash1()(key) % N;
    size_t i2 = Hash2()(key) % N;
    size_t i3 = Hash3()(key) % N;

    _bitset.Set(i1);
    _bitset.Set(i2);
    _bitset.Set(i3);
}

test

test就是判断数据是否在布隆过滤器,数据的存在性会有误判,但是不存在性不会误判

bool Test(const K& key) {
    size_t i1 = Hash1()(key) % N;
    if (_bitset.Test(i1) == false) {
        return false;
    }
    size_t i2 = Hash2()(key) % N;
    if (_bitset.Test(i2) == false) {
        return false;
    }
    size_t i3 = Hash3()(key) % N;
    if (_bitset.Test(i3) == false) {
        return false;
    }

    //这里三个位都存在,有可能是其他key占了,存在是不准确的,存在误判
    return true;
}

删除

由于布隆过滤器三个位置可能会使用到别的元素的哈希地址,删除会影响别的元素的映射,所以布隆过滤器一般不提供删除操作

但是有一种支持删除的方法:将布隆过滤器中的每个比特位扩展成一个小的计数器,插入元素时给k个计数器(k个哈希函数计算出的哈希地址)加一,删除元素时,给k个计数器减一,通过多占用几倍存储空间的代价来增加删除操作


布隆过滤器完整代码实现
#pragma once
#include"BitSet.h"
using namespace std;

struct HashBKDR{
	//BKDRHash算法
	size_t operator()(const std::string& s) {
		size_t value = 0;
		for (auto ch : s) {
			value += ch;
			value *= 131;
		}
		return value;
	}
};

struct HashAP {
	//AP Hash算法
	size_t operator()(const std::string& s) {
		register size_t hash = 0;
		size_t ch;
		for (long i = 0; i < s.size(); i++){
			ch = s[i];
			if ((i & 1)  == 0){
				hash ^= ((hash << 7) ^ ch ^ (hash >> 3));
			}
			else{
				hash ^= (~((hash << 11) ^ ch ^ (hash >> 5)));
			}
		}
		return hash;
	}
};

struct HashDJB {
	//DJB Hash算法
	size_t operator()(const std::string& s) {
		register size_t hash = 5381;
		for(auto ch : s)
		{
			hash += (hash << 5) + ch;
		}
		return hash;
	}
};


template<size_t N, class K = std::string,
	class Hash1 = HashBKDR,
	class Hash2 = HashAP,
	class Hash3 = HashDJB>

class BloomFither {
public:
	void Set(const K& key) {
		size_t i1 = Hash1()(key) % N;
		size_t i2 = Hash2()(key) % N;
		size_t i3 = Hash3()(key) % N;

		_bitset.Set(i1);
		_bitset.Set(i2);
		_bitset.Set(i3);
	}
	bool Test(const K& key) {

		size_t i1 = Hash1()(key) % N;
		if (_bitset.Test(i1) == false) {
			return false;
		}
		size_t i2 = Hash2()(key) % N;
		if (_bitset.Test(i2) == false) {
			return false;
		}
		size_t i3 = Hash3()(key) % N;
		if (_bitset.Test(i3) == false) {
			return false;
		}

		//这里三个位都存在,有可能是其他key占了,存在是不准确的,存在误判
		return true;
	}
private:
	BitSet<N> _bitset;
};
<think>好的,用户想了解C++中位图布隆过滤器的相关知识及总结,我需要结合提供的引用资料来组织回答。首先,我应该先明确位图布隆过滤器的基本概念,然后讨论它们的实现和应用,最后总结它们的优缺点和应用场景。 首先看位图部分。根据引用[3],位图用于处理海量数据的存在性判断,比如40亿个整数去重。位图通过每个bit位表示一个数据是否存在,节省空间。需要提到位图数据结构设计,比如用vector<bool>或直接操作比特位。实现部分可能需要给出C++的代码示例,比如设置某位为1,清除为0,检查存在性。应用方面包括快速查找、去重、排序等,引用[2]和[3]都有提到这些应用。 然后是布隆过滤器。根据引用[4],布隆过滤器是概率型数据结构,用于判断元素可能存在或一定不存在。它使用多个哈希函数映射到不同的位,引用[3]和[4]都提到了这一点。实现部分需要说明插入和查找的过程,以及为什么不能删除,或者如何支持删除(可能需要引用计数)。应用方面,引用[5]提到了缓存穿透的解决方案,防止数据库被无效查询压垮。此外,分布式系统和爬虫中的URL去重也是常见应用。 需要注意布隆过滤器的优缺点,比如空间效率高但存在误判率,无法删除元素等。引用[4]和[5]都有相关描述。可能需要比较位图布隆过滤器的适用场景,比如位图适合精确判断且数据范围小的情况,而布隆过滤器适合大数据量且允许一定误判的情况。 代码实现部分,位图可以用bitset或手动操作位,布隆过滤器则需要多个哈希函数和位图的组合。需要确保示例代码正确,比如展示如何设置位,检查存在性,以及布隆过滤器的插入和查询步骤。 最后总结部分,需要将位图布隆过滤器的核心点对比,强调它们在海量数据处理中的重要性,并结合引用中的内容,如引用[1]提到的结合位图布隆过滤器的方案,可能可以提到这种混合结构在优化空间和查询速度上的优势。 现在需要检查是否有遗漏的引用内容,比如引用[1]提到结合位图布隆过滤器的优点,这可能是在实际应用中的优化策略,需要在总结部分提到。同时,用户要求生成相关问题,需要根据内容设计相关问题,比如应用场景、实现细节、优缺点比较等。 需要注意回答的结构,先讲位图,再讲布隆过滤器,每个部分包括概念、实现、应用,最后总结对比。确保引用标识正确添加,例如在提到布隆过滤器概念时加上[^4],在缓存穿透时引用[^5]等。</think>### C++位图布隆过滤器总结 #### 一、位图(Bitmap) **1. 概念与实现** 位图通过每个比特位(bit)表示一个数据的存在性(0/1),适用于海量数据的快速存在性判断[^3]。例如,40亿个整数(约16GB)用位图仅需约500MB内存。 **C++实现示例**: ```cpp class Bitmap { private: std::vector<uint32_t> bits; // 使用uint32_t数组存储位 public: Bitmap(size_t range) : bits((range >> 5) + 1, 0) {} // 计算需要多少32位块 void set(size_t x) { size_t index = x >> 5; // 确定数组下标 size_t offset = x % 32; // 确定比特位偏移 bits[index] |= (1 << offset); } bool test(size_t x) { size_t index = x >> 5; size_t offset = x % 32; return (bits[index] & (1 << offset)) != 0; } }; ``` **2. 应用场景** - **数据去重**:如统计40亿整数中不重复的数字。 - **快速查询**:判断IP是否在黑名单中。 - **排序**:对有限范围的整数进行非比较排序[^2]。 --- #### 二、布隆过滤器(Bloom Filter) **1. 概念与实现** 布隆过滤器通过$k$个哈希函数将元素映射到多个位,若所有对应位为1则“可能存在”,否则“一定不存在”[^4]。牺牲一定准确性换取极高的空间效率。 **C++实现核心逻辑**: ```cpp class BloomFilter { private: Bitmap bitmap; size_t k; // 哈希函数数量 public: BloomFilter(size_t size, size_t k) : bitmap(size), k(k) {} void add(const std::string& key) { for (size_t i = 0; i < k; ++i) { size_t hash = std::hash<std::string>{}(key + std::to_string(i)); bitmap.set(hash % bitmap.size()); } } bool contains(const std::string& key) { for (size_t i = 0; i < k; ++i) { size_t hash = std::hash<std::string>{}(key + std::to_string(i)); if (!bitmap.test(hash % bitmap.size())) return false; } return true; // 可能存在(有一定误判率) } }; ``` **2. 应用场景** - **缓存穿透防护**:拦截不存在的数据请求,保护数据库。 - **分布式系统**:减少节点间冗余数据传输。 - **爬虫URL去重**:避免重复抓取已处理的页面[^3]。 **3. 优缺点** - **优点**:空间效率高,查询时间$O(k)$,适合海量数据[^4]。 - **缺点**:误判率随元素增加而上升,且不支持删除(除非引入计数布隆过滤器)。 --- #### 三、对比与总结 | **特性** | **位图** | **布隆过滤器** | |------------------|------------------------------|------------------------------| | **数据范围** | 适用于整数且范围较小 | 支持任意数据类型 | | **误判率** | 无 | 有(可调整哈希函数数量优化) | | **删除支持** | 直接修改位即可 | 需额外结构(如计数位图) | | **典型场景** | 精确存在性判断 | 允许误判的存在性预筛 | **混合优化方案**:结合位图布隆过滤器,例如用位图处理高频数据,布隆过滤器处理低频数据,提升整体性能[^1]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

xiaomage1213888

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值