哈希应用、海量数据处理:布隆过滤器

1.布隆过滤器概念

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

2.布隆过滤器的操作

2.1插入

插入原理:

        采用n个字符串哈希函数,分别计算字符串的哈希地址并映射到位图对应比特位上置1,即完成插入,相当于利用位图上的n个比特位同时为1,来表示一个数据。

        可以看出,以上方法是存在哈希冲突的可能性的,但是可能性较小,且采用的比特位数越多,概率越小。

bool Insert(const K& data) {
		size_t index = K2INT1()(data) % _bst.size();
		_bst.set(index);
		index = K2INT2()(data) % _bst.size();
		_bst.set(index);
		index = K2INT3()(data) % _bst.size();
		_bst.set(index);
		index = K2INT4()(data) % _bst.size();
		_bst.set(index);
		index = K2INT5()(data) % _bst.size();
		_bst.set(index);
		++_size;
		return true;
	}

2.2查找

        分别计算每个哈希值,判断对应的比特位是否为1,只要有一个位置是0,则说明该元素一定不在哈希表中。

注意:

        即使每个位置都为1,只能说明可能存在哈希表中,不能一定说明存在。

bool Find(const K& data) {
		size_t index = K2INT1()(data) % _bst.size();
		if (!_bst.test(index)) return false;
		index = K2INT2()(data) % _bst.size();
		if (!_bst.test(index)) return false;
		index = K2INT3()(data) % _bst.size();
		if (!_bst.test(index)) return false;
		index = K2INT4()(data) % _bst.size();
		if (!_bst.test(index)) return false;
		index = K2INT5()(data) % _bst.size();
		if (!_bst.test(index)) return false;
		//只能说明可能存在
		return true;
	}

2.3删除

        布隆过滤器不能直接支持删除工作,因为一个元素在比特位上可能与其他元素有重叠,在删除一个元素时,可能会影响其他元素。

如何支持删除操作:

        若想支持删除操作,则底层不能再使用位图,因为比特位只能表示两种状态,可能会与其他元素形成重叠。所以可以通过底层采用整形数组的形式,在插入时,给对应位置+1,删除时给对应位置-1,这样在删除一个元素时,就不会影响其他元素。

        缺陷:同样无法确定元素一定在布隆过滤器中;增加了几倍的空间;存在计算回绕问题。

3.布隆过滤器的优缺点

3.1优点

(1)增加和查询元素的时间复杂度为O(K),(K为哈希函数的个数,一般较小),与数据量大小无关。

(2)哈希函数互相之间没有关系,方便硬件并行计算。

(3)布隆过滤器不需要存储元素本身,在某些对数据保密性要求比较严格的场景有较大优势。

(4)在能够承受一定的误判时,布隆过滤器比其他数据结构有很大的空间优势。

(5)数据量很大时,布隆过滤器可以表示全集,而其他数据结构不能。

(6)使用同一组散列函数的布隆过滤器可以进行交、并、差集运算。

3.2缺点

(1)有误判率,即不能准确判断元素是否在集合中。

(2)不能获取元素本身。

(3)一般情况下,不能从布隆过滤器中删除元素。

(4)如果采用计算方式删除,可能存在计算回绕问题。

4.布隆过滤器实现

4.1字符串哈希算法Common.h

#pragma once
//将类型转换为整形
#include 
template
class T2INT {//int-->int
public:
	size_t operator()(const T& data) {
		return data;
	}
};

class Str2INT {//string-->int
public:
	size_t operator()(const std::string& str) {
		return SDBMHash(str.c_str());
	}

	unsigned int SDBMHash(const char* str) {
		unsigned int hash = 0;
		unsigned int seed = 131;
		while (*str) {
			hash = hash * seed + (*str++);
		}
		return (hash & 0x7FFFFFFF);
	}
};

//增容质数表
const int PRIMECOUNT = 31;
const size_t primeList[PRIMECOUNT] =
{
 5,11, 23,
 53ul, 97ul, 193ul, 389ul, 769ul,
 1543ul, 3079ul, 6151ul, 12289ul, 24593ul,
 49157ul, 98317ul, 196613ul, 393241ul, 786433ul,
 1572869ul, 3145739ul, 6291469ul, 12582917ul, 25165843ul,
 50331653ul, 100663319ul, 201326611ul, 402653189ul, 805306457ul,
 1610612741ul, 3221225473ul, 4294967291ul
};

size_t GetNextPrime(size_t prime) {
	for (size_t i = 0; i < PRIMECOUNT; ++i) {
		if (primeList[i] > prime) {
			return primeList[i];
		}
	}
	return primeList[PRIMECOUNT - 1];
}


/*五种字符串转整形的哈希算法*/

class Str2INT1 {
public:
    size_t operator()(const std::string& str) {
        return SDBMHash(str.c_str());
    }

    /// @brief SDBM Hash Function  
    /// @detail 本算法是由于在开源项目SDBM(一种简单的数据库引擎)中被应用而得名,它与BKDRHash思想一致,只是种子不同而已。  
    size_t SDBMHash(const char* str)
    {
        register size_t hash = 0;
        while (size_t ch = (size_t)*str++)
        {
            hash = 65599 * hash + ch;
            //hash = (size_t)ch + (hash << 6) + (hash << 16) - hash;  
        }
        return hash;
    }
};

class Str2INT2 {
public:
    size_t operator()(const std::string& str) {
        return RSHash(str.c_str());
    }

    /// @brief RS Hash Function  
    /// @detail 因Robert Sedgwicks在其《Algorithms in C》一书中展示而得名。  
    size_t RSHash(const char* str)
    {
        register size_t hash = 0;
        size_t magic = 63689;
        while (size_t ch = (size_t)*str++)
        {
            hash = hash * magic + ch;
            magic *= 378551;
        }
        return hash;
    }
};

class Str2INT3 {
public:
    size_t operator()(const std::string& str) {
        return APHash(str.c_str());
    }

    /// @brief AP Hash Function  
    /// @detail 由Arash Partow发明的一种hash算法。  
    size_t APHash(const char* str)
    {
        register size_t hash = 0;
        size_t ch;
        for (long i = 0; ch = (size_t)*str++; i++)
        {
            if ((i & 1) == 0)
            {
                hash ^= ((hash << 7) ^ ch ^ (hash >> 3));
            }
            else
            {
                hash ^= (~((hash << 11) ^ ch ^ (hash >> 5)));
            }
        }
        return hash;
    }
};

class Str2INT4 {
public:
    size_t operator()(const std::string& str) {
        return JSHash(str.c_str());
    }
    /// @brief JS Hash Function  
    /// 由Justin Sobel发明的一种hash算法。  
    size_t JSHash(const char* str)
    {
        if (!*str)        // 这是由本人添加,以保证空字符串返回哈希值0  
            return 0;
        register size_t hash = 1315423911;
        while (size_t ch = (size_t)*str++)
        {
            hash ^= ((hash << 5) + ch + (hash >> 2));
        }
        return hash;
    }
};

class Str2INT5 {
public:
    size_t operator()(const std::string& str) {
        return DEKHash(str.c_str());
    }
    /// @brief DEK Function  
    /// @detail 本算法是由于Donald E. Knuth在《Art Of Computer Programming Volume 3》中展示而得名。  
    size_t DEKHash(const char* str)
    {
        if (!*str)        // 这是由本人添加,以保证空字符串返回哈希值0  
            return 0;
        register size_t hash = 1315423911;
        while (size_t ch = (size_t)*str++)
        {
            hash = ((hash << 5) ^ (hash >> 27)) ^ ch;
        }
        return hash;
    }
};

4.2位图实现BitSet.hpp

#pragma once
#include 
#include 
namespace MyBitSet {
	template
	class bitset {
	public:
		/*N是比特位数,右移3位,即除以8,转化为字节为单位
		+1防止传入的位数小于8,右移后为0*/
		bitset()
			: _bst((N >> 3) + 1)
			, _count(0)
		{}

		void set(size_t which) {//比特位置1
			assert(which < N);
			size_t whichByte = which / 8;//计算处于那个字节
			size_t whichBite = which % 8;//计算处于该字节的那一个比特位
			if (_bst[whichByte] & (1 << whichBite)) {//说明该元素已经存在
				return;
			}
			_bst[whichByte] |= 1 << whichBite;
			++_count;//比特位为1的个数增加
		}
		void reset(size_t which) {//比特位置0
			assert(which < N);
			size_t whichByte = which / 8;//计算处于那个字节
			size_t whichBite = which % 8;//计算处于该字节的那一个比特位

			if (test(which)) {//该比特位是1,才进行置0
				_bst[whichByte] ^= 1 << whichBite;
				--_count;//比特位为1的个数减少
			}
		}

		bool test(size_t which) const {//检测比特位是否为1
			assert(which < N);
			size_t whichByte = which / 8;//计算处于那个字节
			size_t whichBite = which % 8;//计算处于该字节的那一个比特位

			return 0 != (_bst[whichByte] & (1 << whichBite));
		}

		size_t size() const {//返回比特位位数
			return N;
		}

		size_t count() const {//返回比特位为1的个数
			/*size_t num = 0;
			for (size_t i = 0; i < N; ++i) {
				if (test(i)) ++num;
			}
			return num;
			若比特位数较多,遍历则效率太低
			*/
			return _count;
		}

	private:
		std::vector _bst;
		size_t _count;//记录比特位为1的个数
	};
}

4.3布隆过滤器实现BloomFilter.hpp

#pragma once
#include "BitSet.hpp"
#include "Common.h"

//采用五个比特位,来映射一个数据
template
class BloomFilter {
public:
	BloomFilter()
		: _bst()
		, _size(0)
	{}

	bool Insert(const K& data) {
		size_t index = K2INT1()(data) % _bst.size();
		_bst.set(index);
		index = K2INT2()(data) % _bst.size();
		_bst.set(index);
		index = K2INT3()(data) % _bst.size();
		_bst.set(index);
		index = K2INT4()(data) % _bst.size();
		_bst.set(index);
		index = K2INT5()(data) % _bst.size();
		_bst.set(index);
		++_size;
		return true;
	}

	bool Find(const K& data) {
		size_t index = K2INT1()(data) % _bst.size();
		if (!_bst.test(index)) return false;
		index = K2INT2()(data) % _bst.size();
		if (!_bst.test(index)) return false;
		index = K2INT3()(data) % _bst.size();
		if (!_bst.test(index)) return false;
		index = K2INT4()(data) % _bst.size();
		if (!_bst.test(index)) return false;
		index = K2INT5()(data) % _bst.size();
		if (!_bst.test(index)) return false;
		//只能说明可能存在
		return true;
	}

	size_t Size() const {
		return _size;
	}

private:
	MyBitSet::bitset _bst;
	size_t _size;
};

你可能感兴趣的:(数据结构,C++程序设计,哈希算法,c++)