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

文章目录

    • 位图
      • 位图的概念
      • 位图的实现
        • 构造
        • set
        • rset
        • test
        • 位图完整代码实现
    • 布隆过滤器
      • 布隆过滤器的概念
      • 布隆过滤器的误判
      • 布隆过滤器的优缺点
      • 布隆过滤器的实现
        • 结构
        • set
        • test
        • 删除
        • 布隆过滤器完整代码实现

位图

位图的概念

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

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

位图的实现

构造

底层数据可以用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个整数进行或等运算**

【数据结构】位图及布隆过滤器_第1张图片

//把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位,并且取反,然后进行与等运算

【数据结构】位图及布隆过滤器_第2张图片

//把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年提出的 一种紧凑型的、比较巧妙的概率型数据结构,特点高效地插入和查询,可以用来告诉你 “某样东西一定不存在或者可能存在”,它是用多个哈希函数将一个数据映射到位图结构中。此种方式不仅可以提升查询效率,也可以节省大量的内存空间

【数据结构】位图及布隆过滤器_第3张图片


布隆过滤器的误判

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

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

你可能感兴趣的:(数据结构,数据结构)