【数据结构】布隆过滤器的简单实现

布隆过滤器是一种空间效率高的概率型数据结构,用于判断一个元素是否可能在一个集合中。其由位图和多个独立的哈希函数组成,通过哈希函数将元素映射到位图中,可能存在误判但无法删除。适用于数据字典、判重和集合交集场景。本文介绍了布隆过滤器的基本原理并提供了C语言实现。

布隆过滤器的简单实现


布隆过滤器(BloomFilter)是1970年由布隆提出的,它实际上是一个很长的二进制向量和一系列随机映射函数


布隆过滤器可以用于检索一个元素是否在一个集合中,它的优点是空间效率和查询时间都远远超过一般的算法,缺点

是有一定的误识别率和删除困难。


布隆过滤器(Bloom Filter)的适用范围如下:


实现数据字典,数据的判重,或者集合求交集


基本原理如下:


它的内容是,一个位图加上k个独立的hash函数,通过k个hash函数,可以得到khash函数对应的值,


然后把这些hash函数对应的值存入位图中(把具体的某一位置为1),然后查找时,通过判断k个hash


函数对应的位是不是为1,如果有一个为1,就代表该元素不存在,如果全为1,则该元素很可能存在


结构体代码如下:

#define HASHFUNCMAXSIZE 2
#define BitMapCapacity 1024

typedef struct BitMap{
	uint64_t* data;
	size_t capacity;
}BitMap;


/*字符串哈希函数*/
typedef size_t(*HashFunc)(const char*);

typedef struct BloomFilter{
	BitMap bitmap;
	HashFunc hash_func[HASHFUNCMAXSIZE];
}BloomFilter;


接下来看具体怎么实现:


初始状态时,有一个包含m位的数组每一位都设置为0:



初始化代码如下:

/*哈希函数*/
size_t hashfunc0(const char* str) {
	assert(str);
	size_t hash = 0;
	size_t ch = 0;
	while (ch = (size_t)*str++) {
		hash = hash * 131 + ch;
	}
	return hash;
}

size_t hashfunc1(const char* str) {
	assert(str);
	size_t hash = 0;
	size_t ch = 0;
	while (ch = (size_t)*str++) {
		hash = hash * 65599 + ch;
	}
	return hash;
}

/*初始化布隆过滤器*/
void BloomFilterInit(BloomFilter* bf) {
	if (bf == NULL) {
		return;
	}
	/*初始化位图*/
	BitMapInit(&bf->bitmap, BitMapCapacity);
	/*初始化哈希函数*/
	bf->hash_func[0] = hashfunc0;
	bf->hash_func[1] = hashfunc1;
}

我这里只用了两个哈希函数,比较少,容易出现哈希冲突


继续解释布隆过滤器,为了表达S={x1, x2,…,xn}这样一个n个元素的集合,Bloom Filter使用k个相互独立的哈希函数(Hash Function),它们分别将集合中的每个元素映射到{1,…,m}的范围中,如图:



对任意一个元素x,第i个哈希函数映射的位置hi(x)就会被置为11ik)。注意,如果一个位置多次被置为1,那么只有第一次会起作用,后面几次将没有任何效果。


我实现的代码如下:

/*插入*/
void BloomFilterInsert(BloomFilter* bf, const char* str) {
	assert(bf);
	assert(str);
	/*
	**通过哈希函数,把要存储的字符串转换成一个哈希值,
	**然后在位图中再把这个值得位置为1
	**只有两个哈希函数,所以和其他值出现哈希冲突的可能性比较大
	**所以可以多设置一些哈希函数
	*/
	int i = 0;
	for (; i < HASHFUNCMAXSIZE; ++i) {
		size_t offset = bf->hash_func[i](str) % BitMapCapacity;
		BitMapSet(&bf->bitmap, offset);
	}
	return;
}


在判断 y 是否属于这个集合时,我们对 y 应用 k 次哈希函数,如果所有 hi(y) 的位置都是 1 1 i k ),那么我们就认为 y 是集合中的元素,否则就认为 y 不是集合中的元素。下图中 y1 就不是集合中的元素:



我的代码如下:

int BloomFilterExit(BloomFilter* bf, const char* str) {
	if (bf == NULL || str == NULL) {
		return 0;
	}
	int i = 0;
	for (; i < HASHFUNCMAXSIZE; ++i) {
		size_t offset = bf->hash_func[i](str) % BitMapCapacity;
		int n = BitMapTest(&bf->bitmap, offset);
		if (n == 0) {
			/*
			**表示在k个哈希函数中对应的位中
			**有一个位不是1,那么,这个元素肯定不存在
			*/
			return 0;
		}
	}
	return 1;
}


注意,在使用Bloom Filter判断一个元素是否属于某个集合时,会有一定的错误率,虽然如果哈希函数很多的话,


这个错误率会很低,但是也是会有的,所以布隆过滤器不适合那种需要零错误的情况


全部代码如下:


BloomFilter.h

#pragma once

#include<stddef.h>
#include"BitMap.h"

#define HASHFUNCMAXSIZE 2
#define BitMapCapacity 1024


/*字符串哈希函数*/
typedef size_t(*HashFunc)(const char*);

typedef struct BloomFilter{
	BitMap bitmap;
	HashFunc hash_func[HASHFUNCMAXSIZE];
}BloomFilter;



/*初始化布隆过滤器*/
void BloomFilterInit(BloomFilter* bf);

/*插入*/
void BloomFilterInsert(BloomFilter* bf, const char* str);

int BloomFilterExit(BloomFilter* bf, const char* str);

void BloomFilterDestory(BloomFilter* bf);

/*按照当前的设计是不允许删除的*/


BitMap.h

#pragma once

#include<stdint.h>
#include<stddef.h>

typedef struct BitMap{
	uint64_t* data;
	size_t capacity;
}BitMap;

/*初始化*/
void BitMapInit(BitMap* bm, size_t capacity);

/*把index位设置为1*/
void BitMapSet(BitMap* bm, size_t index);

/*把index位设置为0*/
void BitMapUnSet(BitMap* bm, size_t index);

/*测试index为1还是为0,如果是1,就返回1,否则返回0*/
int BitMapTest(BitMap* bm, size_t index);

/*把这个位图所有位都设置为1*/
void BitmapFill(BitMap* bm);

/*把整个位图所有位都设置为0*/
void BitMapClear(BitMap* bm);

/*销毁*/
void BitMapDestory(BitMap* bm);


BitMap.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"BitMap.h"
#include<stdlib.h>
#include<stdint.h>
#include<stddef.h>
#include<assert.h>
#include<string.h>

size_t DataSize(size_t capacity) {
	/*sizeof(uint64_t)是8个字节, capacity表示需要存储的数据类型*/
	return capacity / (sizeof(uint64_t)* 8) + 1;
}

/*初始化*/
void BitMapInit(BitMap* bm, size_t capacity) {
	if (bm == NULL) {
		return;
	}
	bm->capacity = capacity;
	/*得到的size表示需要容量的几个64位的存储空间*/
	size_t size = DataSize(capacity);
	bm->data = (uint64_t*)malloc(sizeof(uint64_t)* size);
	memset(bm->data, 0, sizeof(uint64_t)* size);
}

size_t GetOffset(size_t index, size_t* n, size_t* offset) {
	assert(n);
	assert(offset);
	*n = index / (sizeof(uint64_t)* 8);
	*offset = index % (sizeof(uint64_t)* 8);
}

/*把index位设置为1*/
/*
**用65为例子
**n = index / (sizeof(uint64_t) * 8);  -> 1
**offset = index % (sizeof(uint64_t) * 8)  -> 1
**data[n] |= (1lu << offset)
*/
void BitMapSet(BitMap* bm, size_t index) {
	if (bm == NULL) {
		return;
	}
	if (index >= bm->capacity) {
		/*表示已经超过现在所能存储的数据容量 */
		return;
	}
	size_t n = 0;
	size_t offset = 0;
	GetOffset(index, &n, &offset);
	bm->data[n] |= (1lu << offset);
}

/*把index位设置为0*/
void BitMapUnSet(BitMap* bm, size_t index) {
	if (bm == NULL) {
		return;
	}
	size_t n, offset;
	GetOffset(index, &n, &offset);
	/*
	**1左移offest位,然后求补,再与bm->data[n]一与
	*/
	bm->data[n] &= ~(1lu << offset);
}


/*测试index为1还是为0,如果是1,就返回1,否则返回0*/
int BitMapTest(BitMap* bm, size_t index) {
	if (bm == NULL) {
		return;
	}
	size_t n, offset;
	GetOffset(index, &n, &offset);
	int i = bm->data[n] & (1lu << offset);
	return i != 0 ? 1 : 0;
}

/*把这个位图所有位都设置为1*/
void BitmapFill(BitMap* bm) {
	if (bm == NULL) {
		return;
	}
	/*这里的size是总共有多少个字节*/
	size_t size = sizeof(uint64_t)* DataSize(bm->capacity);
	memset(bm->data, 0xff, size);
}

/*把整个位图所有位都设置为0*/
void BitMapClear(BitMap* bm) {
	if (bm == NULL) {
		return;
	}
	size_t size = sizeof(uint64_t)* DataSize(bm->capacity);
	memset(bm->data, 0, size);
}

/*销毁*/
void BitMapDestory(BitMap* bm) {
	if (bm == NULL) {
		return;
	}
	free(bm->data);
	bm->capacity = 0;
}


BloomFilter.c


#define _CRT_SECURE_NO_WARNINGS 1
#include"BloomFilter.h"
#include"BitMap.h"
#include<assert.h>


/*哈希函数*/
size_t hashfunc0(const char* str) {
	assert(str);
	size_t hash = 0;
	size_t ch = 0;
	while (ch = (size_t)*str++) {
		hash = hash * 131 + ch;
	}
	return hash;
}

size_t hashfunc1(const char* str) {
	assert(str);
	size_t hash = 0;
	size_t ch = 0;
	while (ch = (size_t)*str++) {
		hash = hash * 65599 + ch;
	}
	return hash;
}

/*初始化布隆过滤器*/
void BloomFilterInit(BloomFilter* bf) {
	if (bf == NULL) {
		return;
	}
	/*初始化位图*/
	BitMapInit(&bf->bitmap, BitMapCapacity);
	/*初始化哈希函数*/
	bf->hash_func[0] = hashfunc0;
	bf->hash_func[1] = hashfunc1;
}



int BloomFilterExit(BloomFilter* bf, const char* str) {
	if (bf == NULL || str == NULL) {
		return 0;
	}
	int i = 0;
	for (; i < HASHFUNCMAXSIZE; ++i) {
		size_t offset = bf->hash_func[i](str) % BitMapCapacity;
		int n = BitMapTest(&bf->bitmap, offset);
		if (n == 0) {
			/*
			**表示在k个哈希函数中对应的位中
			**有一个位不是1,那么,这个元素肯定不存在
			*/
			return 0;
		}
	}
	return 1;
}

void BloomFilterDestory(BloomFilter* bf) {
	if (bf == NULL) {
		return;
	}
	BitMapDestory(&bf->bitmap);
	int i = 0;
	for (; i < HASHFUNCMAXSIZE; ++i) {
		bf->hash_func[i] = NULL;
	}
}

///////////////////////////////////////////////////////////
//一下为测试代码
///////////////////////////////////////////////////////////

#if 1

#include<stdio.h>
#include<stdlib.h>

#define TESTHEAD printf("------------%s-------------\n",__FUNCTION__)

void TestInit() {
	BloomFilter bf;
	TESTHEAD;
	BloomFilterInit(&bf);
	printf("expect 1024, actual:%d\n", bf.bitmap.capacity);
	printf("expect %p, actual:%p\n", hashfunc0, bf.hash_func[0]);
	printf("expect %p, actual:%p\n", hashfunc1, bf.hash_func[1]);
}

void TestInsert() {
	BloomFilter bf;
	TESTHEAD;
	BloomFilterInit(&bf);

	BloomFilterInsert(&bf, "hello");
	BloomFilterInsert(&bf, "world");

	size_t offset1 = bf.hash_func[0]("hello") % BitMapCapacity;
	size_t offset2 = bf.hash_func[1]("world") % BitMapCapacity;

	int n1 = BitMapTest(&bf.bitmap, offset1);
	int n2 = BitMapTest(&bf.bitmap, offset2);

	printf("expect 1, actual:%d\n", n1);
	printf("expect 1, actual:%d\n", n2);
}

void TestExit() {
	BloomFilter bf;
	TESTHEAD;
	BloomFilterInit(&bf);

	BloomFilterInsert(&bf, "hello");
	BloomFilterInsert(&bf, "world");

	int n1 = BloomFilterExit(&bf, "hello");
	int n2 = BloomFilterExit(&bf, "world");
	int n3 = BloomFilterExit(&bf, "bit");
	printf("expect 1, acutal:%d\n", n1);
	printf("expect 1, acutal:%d\n", n2);
	printf("expect 0, acutal:%d\n", n3);
}



int main() {
	TestInit();
	TestInsert();
	TestExit();
	system("pause");
	return 0;
}

#endif



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值