高级编程实战《哈希表&布隆过滤器》
哈希表
哈希表这个玩意除了面试题用,还有下面及个作用。
- 判断一封邮件的发件人是否在黑名单里面
- 搜索引擎的网络爬虫,每天要爬取几十亿网页,哪些URL是爬过的?
- 快速判断某串字符串是否是一个学生的学号
哈希表最重要的特征就是可以在O(1)的时间内进行查找数据。
对于一个数据,我们对其进行哈希,得到哈希值;让数组array【哈希值】=1。当我们查找位置时,只需要判断Array【哈希值】是否为1就可以了。如果为1,说明该数据存在。
哈希表关键
哈希表有两个重要的东西,分别是哈希函数和冲突解决方法。对于哈希函数已经有很多人帮我们想出来了。对于冲突解决方法,通常由两种方法。
- 拉链法:在数组的每个位置开一个链表。假设字符串s1和字符串s2的哈希值都为k,因此我们只需要在数组的位置k开一个链表,将两个值串起来即可。
- 开放寻址法(蹲坑法):如果数组的位置k已经由值了,我们只需要将字符串存入k+1的位置即可。
哈希表的实现
本次哈希表采用C++实现,采用拉链法解决哈希冲突;结构体如下。
class HashMap {
private:
int ELFHash(const char *str);
list<const char *> _hash[M];
public:
void add(const char *str);
bool search(const char *str);
};
对象由4部分构成:
- ELFHash:哈希函数,将字符串哈希到一个整数;
- _hash[M]:大小为M的链表数组,表示哈希表,所有的数据都存储到这个链表数组中去;
- add:完成哈希表的添加操作;
- search:完成哈希表的搜索操作;
完整代码
#include <iostream>
#include <list>
#include <cstring>
using namespace std;
const int M = 103418;
class HashMap {
private:
// 哈希函数,用来计算字符串的哈希值
int ELFHash(const char *str) {
unsigned int hash = 0;
unsigned int x = 0;
while (*str) {
hash = (hash << 4) + (*str++);
if ((x = hash & 0xF0000000L) != 0) {
hash ^= (x >> 24);
hash &= ~x;
}
}
return (hash & 0x7FFFFFFF) % M;
}
list<const char *> _hash[M];
public:
void add(const char *str) {
int k = ELFHash(str);
// 拷贝str字符串到tmp变量中去
int len = strlen(str);
char *tmp = (char*) malloc(sizeof(char) * (len + 1));
strncpy(tmp, str, len);
tmp[len] = '\0';
// 将字符串tmp放入哈希表中。如果将str放入哈希表中
// 会出现字符串str空间释放后,链表中的字符串str也会释放。
_hash[k].push_front(tmp);
}
bool search(const char *str){
int k = ELFHash(str);
for(auto x: _hash[k]) {
if( strcmp(x, str) == 0) return true;
}
return false;
}
};
int main() {
HashMap* hashMap = new HashMap();
char *p;
int _size = strlen("你好");
p = (char *) malloc(sizeof(p) * (_size+1));
strncpy(p, "你好", _size);
p[_size] = '\0';
hashMap->add(p);
free(p);
cout << hashMap->search("你好") << endl;
return 0;
}
布隆过滤器
哈希表有个缺点就是需要把所有的字符串都存入表中。如果存储的字符串很多,则需要的空间就很大。因此就有了布隆过滤器。
布隆过滤器:由一个很长的二进制向量和一系列的随机的hash函数构成。
核心思想是:准确率换空间
优点:空间效率和查询时间都远远超过一般的算法;
缺点:有一定的误识别率和删除困难;
布隆过滤器的实现
插入:对于字符串s1,使用K个哈希函数,分别计算K个哈希值,假设计算结果为(1,4,5),则把哈希表的第1,4,5的位置都设置为1。
查找:对于字符串s1,使用K个哈希函数,分别计算K个哈希值,假设计算结果为(1,4,5),如果哈希表的第1,4,5位置都是1,则说明查找成功。
因此布隆过滤器需要许多哈希函数,因此我们采用murmurhash
通过传入不同的随机种子,作为不同的哈希函数。
布隆过滤器的实现
本次哈希表采用C++实现,Hashtable的每个位置都存储了32个bit。
class BloomFilter {
private:
int M, K;
unsigned int *hashtable;
unsigned int murMurHash(const void *key, int len, const int seed);
public:
BloomFilter(int _M, int _K)
void insert(char *s);
bool search(char *s);
};
对象由6部分构成:
- M 分别表示哈希表的大小
- K 采用哈希函数的数量;
- murMurHash:哈希函数,如果传入不同的随机种子,则生成不同的哈希值;
- _hash[M]:大小为M的链表数组,表示哈希表,所有的数据都存储到这个链表数组中去;
- add:完成布隆过滤器的添加操作;
- search:完成布隆过滤器的搜索操作;
完整代码
#include <iostream>
#include <cstring>
using namespace std;
class BloomFilter {
private:
int M, K;
unsigned int *hashtable;
unsigned int murMurHash(const void *key, int len, const int seed) {
const unsigned int m = 0x5bd1e995;
const int r = 24;
unsigned int h = seed ^ len;
// Mix 4 bytes at a time into the hash
const unsigned char *data = (const unsigned char *) key;
while (len >= 4) {
unsigned int k = *(unsigned int *) data;
k *= m;
k ^= k >> r;
k *= m;
h *= m;
h ^= k;
data += 4;
len -= 4;
}
// Handle the last few bytes of the input array
switch (len) {
case 3:
h ^= data[2] << 16;
case 2:
h ^= data[1] << 8;
case 1:
h ^= data[0];
h *= m;
};
// Do a few final mixes of the hash to ensure the last few
// bytes are well-incorporated.
h ^= h >> 13;
h *= m;
h ^= h >> 15;
return h % M;
};
public:
BloomFilter(int _M, int _K) {
M = _M;
K = _K;
hashtable = new unsigned int[M];
}
void insert(char *s) {
for (int i = 0; i < K; i++) {
int k = murMurHash(s, strlen(s), i);
int slot = k / 32;
int pos = k % 32;
hashtable[slot] |= (1 << pos);
}
}
bool search(char *s) {
int count = 0;
for (int i = 0; i < K; i++) {
int k = murMurHash(s, strlen(s), i);
int slot = k / 32;
int pos = k % 32;
int x = (hashtable[slot] >> pos) & 1;
count += x;
}
return count == K;
}
};
int main() {
BloomFilter *bf = new BloomFilter(100000007, 16);
bf->insert("123123");
cout << bf->search("1223") << endl;
cout << bf->search("123123") << endl;
return 0;
}
收获
char*
数据只是一个指针,当指向的空间释放了,char*的值就是空了。
对于char*
数据来说,必须要通过下面的方式,分配空间。
char *p;
int _size = strlen("你好");
p = (char *) malloc(sizeof(p) * (_size+1));
strncpy(p, "你好", _size);
p[_size] = '\0';
同时char*在比较大小时,不能直接使用==
而应该使用strcmp(s1,s2)==0
- strncpy,字符串拷贝
- malloc,空间分配