首先理解一下什麼是位圖什麼是布隆過濾器??
- 位圖:用來快速判斷一個整數是否在一堆整數中
布隆過濾器:用來判斷一個字符串是否在一堆字符串裏
下面對布隆過濾器進行一些詳細的解釋
- 布隆過濾器其實是結合了位圖與哈希表,先將字符串用字符串哈希算法映射到哈希表中,但是由於哈希衝突,我們可以一個字符串使用多個不同的字符串哈希算法同時映射在整個哈希表中,判斷一個字符串是否在這堆字符串中,我們可以算出這個字符串的位置,當且僅當這個字符串每個映射位置都是1才表示存在,只要有一個位置爲0,就表示不存在
但是布隆過濾器判斷不存在是準確的,存在可能不準確因爲存在哈希衝突 - 布隆過濾器的優點:
相比於其它的數據結構,布隆過濾器在空間和時間方面都有巨大的優勢。布隆過濾器存儲空間和插入/查詢時間都是常數。另外, Hash函數相互之間沒有關係,方便由硬件並行實現。布隆過濾器不需要存儲元素本身,在某些對保密要求非常嚴格的場合有優勢。
布隆過濾器可以表示全集,其它任何數據結構都不能。 - 布隆過濾器的缺點:
誤算率是其中之一。隨着存入的元素數量增加,誤算率隨之增加。但是如果元素數量太少,則使用散列表足矣。
另外,一般情況下不能從布隆過濾器中刪除元素。我們很容易想到把位列陣變成整數數組,每插入一個元素相應的計數器加1, 這樣刪除元素時將計數器減掉就可以了。然而要保證安全的刪除元素並非如此簡單。首先我們必須保證刪除的元素的確在布隆過濾器裏面. 這一點單憑這個過濾器是無法保證的。另外計數器迴繞也會造成問題。
在降低誤算率方面,有不少工作,使得出現了很多布隆過濾器的變種。 - 布隆過濾器的應用:
A.需要檢查一個英語單詞是否拼寫正確
B.一個嫌疑人的名字是否已經在嫌疑名單上
C.網絡爬蟲裏,一個網站是否被訪問過等
1.實現位圖
.h文件
#pragma once
#include<stdint.h>
typedef uint64_t BitmapType;
#define BitmapMaxSize 1000
typedef struct Bitmap
{
uint64_t* data;
uint64_t capacity;//位圖最多能容納多少位
}Bitmap;
//初始化
void BitmapInit(Bitmap* bm,uint64_t capacity);
//銷燬
void BitmapDestroy(Bitmap* bm);
//將某一位設置爲1
void BitmapSet(Bitmap* bm,uint64_t index);
//將某一位設置爲0
void BitmapUnset(Bitmap* bm,uint64_t index);
//將所有位設置爲1
void BitmapFill(Bitmap* bm);
//將所有位設置爲0
void BitmapClear(Bitmap* bm);
//測試某一位是否爲1
int BitmapTest(Bitmap* bm,uint64_t index);
(1)檢測位圖中的某一位是否爲1
int BitmapTest(Bitmap* bm,uint64_t index)
{
if(bm == NULL || index >= bm->capacity)
{
//非法輸入
return 0;
}
uint64_t n,offset;
GetOffset(index,&n,&offset);
uint64_t ret = bm->data[n] & (0x1ul << offset);
return ret > 0 ? 1 : 0;
}
(2)將位圖中的所有位都設置爲0
void BitmapClear(Bitmap* bm)
{
if(bm == NULL)
{
return;
}
uint64_t size = Getsize(bm->capacity);
memset(bm->data,0x0,(sizeof(BitmapType)*size));
return;
}
(3)初始化位圖
uint64_t Getsize(uint64_t capacity)
{
uint64_t size = capacity / (sizeof(BitmapType)*8)+1;
return size;
}
void BitmapInit(Bitmap* bm,uint64_t capacity)
{
if(bm == NULL)
{
return;
}
//capacity表示該位圖能保存的最大的數
//比如 capacity = 100,2個元素
//比如 capacity = 200,4個元素
//比如 capacity = 300,5個元素
//比如 capacity = N,N/(sizeof(uint64_t) * 8)+ 1
bm->capacity = capacity;
//size 是我們申請內存時對應的數組元素個數
uint64_t size = Getsize(capacity);
bm->data = (BitmapType*)malloc(sizeof(BitmapType)*size);
memset(bm->data,0,sizeof(BitmapType)*size);
return;
}
(4)銷燬位圖
void BitmapDestroy(Bitmap* bm)
{
if(bm == NULL)
{
return;
}
bm->capacity = 0;
free(bm->data);
return;
}
(5)將位圖的某一位設置爲1
void GetOffset(uint64_t index,uint64_t* n,uint64_t* offset)
{
*n = index / (sizeof(BitmapType)*8);
*offset = index % (sizeof(BitmapType)*8);
return;
}
void BitmapSet(Bitmap* bm,uint64_t index)
{
if(bm == NULL || index >= bm->capacity)
{
return;
}
uint64_t n,offset;
GetOffset(index,&n,&offset);
bm->data[n] |= (0x1ul << offset);
return;
}
void TestSet()
{
TEST_HEADER;
Bitmap bm;
BitmapInit(&bm,100);
BitmapSet(&bm,50);
int ret = BitmapTest(&bm,50);
printf("ret expected 1,actual %d\n",ret);
ret = BitmapTest(&bm,20);
printf("ret expected 0,actual %d\n",ret);
}
(6)將位圖中的某一位設置爲0
void BitmapUnset(Bitmap* bm,uint64_t index)
{
if(bm == NULL || index >= bm->capacity)
{
return;
}
uint64_t n,offset;
GetOffset(index,&n,&offset);
bm->data[n] &= ~(0x1ul << offset);
return;
}
void TestUnset()
{
TEST_HEADER;
Bitmap bm;
BitmapInit(&bm,100);
BitmapSet(&bm,50);
int ret = BitmapTest(&bm,50);
printf("ret expected 1,actual %d\n",ret);
BitmapUnset(&bm,50);
ret = BitmapTest(&bm,50);
printf("ret expected 0,actual %d\n",ret);
}
(7)將位圖的所有位都設置爲1
void TestFill()
{
TEST_HEADER;
Bitmap bm;
BitmapInit(&bm,100);
BitmapFill(&bm);
int ret = BitmapTest(&bm,50);
printf("ret expected 1,actual %d\n",ret);
ret = BitmapTest(&bm,0);
printf("ret expected 1,actual %d\n",ret);
ret = BitmapTest(&bm,99);
printf("ret expected 1,actual %d\n",ret);
}
void BitmapFill(Bitmap* bm)
{
if(bm == NULL)
{
return;
}
uint64_t size = Getsize(bm->capacity);
memset(bm->data,0xff,(sizeof(BitmapType)*size));
return;
}
2.實現布隆過濾器
.h文件
#pragma once
#include"bitmap.h"
//此處定義了布隆過濾器的哈希函數,把字符串轉成下標
typedef uint64_t (*BloomHash)(const char*);
#define BloomHashCount 2
typedef struct BloomFilter
{
Bitmap bm;
BloomHash bloom_hash[BloomHashCount];
}BloomFilter;
void BloomFilterInit(BloomFilter* bf);
void BloomFilterDestroy(BloomFilter* bf);
void BloomFilterInsert(BloomFilter* bf,const char* str);
int BloomFilterIsExist(BloomFilter* bf,const char* str);
(1)hash_func.c
#include <stdio.h>
#include<stddef.h>
size_t BKDRHash(const char* str)
{
size_t hash = 0;
size_t ch = 0;
while(ch = (size_t)*str++)
{
hash = hash * 131 +ch;
}
return hash;
}
size_t SDBMHash(const char* str)
{
size_t hash = 0;
size_t ch = 0;
while(ch = (size_t)*str++)
{
hash = hash * 65599 +ch;
}
return hash;
}
(2)初始化布隆過濾器
void BloomFilterInit(BloomFilter* bf)
{
if(bf == NULL)
{
return;
}
BitmapInit(&bf->bm,10000);
bf->bloom_hash[0] = SDBMHash;
bf->bloom_hash[1] = BKDRHash;
return;
}
(3)銷燬布隆過濾器
void BloomFilterDestroy(BloomFilter* bf)
{
if(bf == NULL)
{
return;
}
bf->bloom_hash[0] = NULL;
bf->bloom_hash[1] = NULL;
BitmapDestroy(&bf->bm);
return;
}
(4)向布隆過濾器中插入一個字符串
void BloomFilterInsert(BloomFilter* bf,const char* str)
{
if(bf == NULL || str == NULL)
{
//非法輸入
return;
}
size_t i = 0;
for(;i < BloomHashCount;++i)
{
uint64_t hash = bf->bloom_hash[i](str) % BitmapMaxSize;
BitmapSet(&bf->bm,hash);
}
return;
}
(5)查看布隆過濾器中是否存在一個字符串
int BloomFilterIsExist(BloomFilter* bf,const char* str)
{
if(bf == NULL || str == NULL)
{
//非法輸入
return 0;
}
size_t i = 0;
for(;i < BloomHashCount;++i)
{
uint64_t hash = bf->bloom_hash[i](str) % BitmapMaxSize;
int ret = BitmapTest(&bf->bm,hash);
if(ret == 0)
{
return 0;
}
}
return 1;
}
(6)整體測試函數
void TestBloom()
{
TEST_HEADER;
BloomFilter bf;
BloomFilterInit(&bf);
BloomFilterInsert(&bf,"nihao");
BloomFilterInsert(&bf,"haha");
int ret = BloomFilterIsExist(&bf,"nihao");
printf("ret expected 1,actual %d\n",ret);
ret = BloomFilterIsExist(&bf,"hehe");
printf("ret expected 0,actual %d\n",ret);
}
上邊這個布隆算法節省空間但是不支持刪除算法,因爲上邊那個算法有可能一個位置映射了幾個數,刪除了一個數可能會影響到別的數;
如果我們想要使用刪除算法,我們可以使用引用計數的方法,那麼存放一個數的位置就不能用一個比特位了,而是可以用一個無符號整數來存放,當刪除一個數的時候,如果它映射到的每個位置都大於0,就表明這個數存在,那麼就讓這幾個數同時減1;