手写高级数据结构《哈希表&布隆过滤器》

高级编程实战《哈希表&布隆过滤器》

哈希表

哈希表这个玩意除了面试题用,还有下面及个作用。

  • 判断一封邮件的发件人是否在黑名单里面
  • 搜索引擎的网络爬虫,每天要爬取几十亿网页,哪些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,空间分配
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

自由小冰儿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值