首页 > 其他分享 >哈希表与布隆过滤器

哈希表与布隆过滤器

时间:2023-05-02 13:33:33浏览次数:47  
标签:return int 布隆 表与 哈希 ind hash data

一、哈希的整体思想

最简单的哈希表其实就是数组,从数组中取出一个数的时间复杂度是O(1)的。但是数组下标类型是整型的,万一我的下标类型不是整型了该怎么办呢?比如说字符串型,典型的就是我想查找某个单词存不存在。还有些更复杂的数据类型,比如自定义的类型。那么问题就来了,如何满足任意数据类型的索引需求呢?最简单直接的想法,其实就是先对任意数据类型与整型的数组下标做一个映射,往后就又回到数组取数的环节了。这种映射是一种高维空间到低维空间的映射。低维空间是有限的,而高维空间的变化情况要复杂得多。如果采用一个萝卜一个坑的思想,必然会产生重复占位,这就是哈希冲突。当我们在设计哈希表的时候一定要考虑到哈希冲突。为什么说哈希表的设计感极强,其实就在于,解决哈希冲突的方法是多种多样的。还有一个问题,哈希表装满了怎么办?有一个概念叫做装填因子(=存储元素数/哈希表size大小)一般来说,装填因子达到0.75的情况下,意味着哈希表可能要进行扩容了。

布隆过滤器

好了,说回布隆过滤器,传统哈希表有一个缺点,就是存储空间与元素数量有关,在大数据的场景下,经常需要判重操作。如果用一张哈希表存,数据量可能过于庞大。而布隆过滤器,可以实现存储空间与元素数量无关。它是通过几组不同的哈希函数,映射到不同的数组下标,然后在对应位置的数组上标记为1,默认是0,如果这些位置上的数组值一旦只要出现一个0,那么都可以直接判断出,该元素不存在,但是如果全为1,也不能说明它就一定存在哈希表中,只能说明它大概率存在。布隆过滤器经常应用于大数据和信息安全相关的场景中。

二、哈希冲突解决办法

1、开放地址法

思想:如果7的位置发生冲突了,那就试一试8,8也不行,那就试一试9,依次往后“探测”,直到找到空位。(线性探测法)
注意:往后探测位置的计算规则是很灵活的,这里只是举了最简单的线性探测法。

平方探测法也有两种,可以简要看看找找区别,做题的时候格外注意,到底是哪一种:
第一种:7->8->12->21->37 (a[i+1] = a[i] + i^2)
第二种:7->8->11->16->23 (a[i+1] = a[0] + i^2)
值得注意的是,第二种只需要判断 i 从0-Msize-1 即可,因为(key+Msize*Msize)%Msize代表搜索回到了原地,这时可以认为无法搜索到这个数字

2、再哈希法

思想:比方说可以设计三种不同的哈希函数,如果第一种冲突了,就试试第二种,第二种也冲突了就试试第三种,依次类推。
注意:这种方法治标不治本,一般配合其他的哈希冲突解决方法使用。

3、建立公共溢出区

思想:另外建立一个公共溢出区,我想找的元素哈希表中找不到,就去公共溢出区再找找,其实就是用另外一种数据结构来维护,比方说红黑树。

4、链式地址法(拉链法)

思想:把哈希表的每一个坑看成链表的头节点。如果两个元素都想占用这个坑,直接在该坑上往后形成一个链表即可。

三、哈希表代码实现:

#include<iostream>
#include<vector>
using namespace std;

// 开放寻址法
class HashTable {
public:
    HashTable(int n = 100) : data(n), flag(n), cnt(0) {} 
    void insert(string s) {
        int ind = hash_func(s) % data.size(); // 计算哈希值
        recalc_ind(ind, s); // 冲突处理
        if (!flag[ind]) {
            data[ind] = s;
            flag[ind] = true;
            cnt++;
            if (cnt * 100 > data.size() * 75) {
                expand();
            }
        }
    }
    bool find(string s) {
        int ind = hash_func(s) % data.size(); // 计算哈希值
        recalc_ind(ind, s); // 冲突处理
        return flag[ind];
    }
private:
    int cnt; // 记录有多少元素
    vector<string> data;
    vector<bool> flag; // 记录相应位置是否存数据

    void expand() {
        int n = data.size() * 2;
        HashTable h(n);
        for (int i = 0; i < data.size(); i++) { // 看似很高的时间复杂度,其实分摊到每一个元素,扩容所造成的时间复杂度是O(1)的。
            if (flag[i] == false) continue;
            h.insert(data[i]);
        }
        *this = h;
        return ;
    }
    int hash_func(string &s) {
        int seed = 131, hash = 0;
        for (int i = 0; s[i]; i++) {
            hash = hash * seed + s[i];
        }
        return hash & 0x7fffffff; // 最高位是0,最后肯定是一个正数
    }
    void recalc_ind(int &ind, string s) {
        int t = 1;
        while (flag[ind] && data[ind] != s) {
            ind += t * t;
            t += 1;
            ind %= data.size();
        }
        return ;
    }

};


// 公共溢出区法:
class HashTable {
public:
    HashTable(int n = 100) : flag(n), data(n), cnt(0) {}
    void insert(string s) {
        int ind = hash_func(s) % data.size(); // 计算哈希值
        recalc_ind(ind, s); // 冲突处理
        if (flag[ind] == false) {
            data[ind] = s;
            flag[ind] = true;
            cnt += 1;
            if (cnt * 100 > data.size() * 75) {
                expand();
            }
        } else if (data[ind] != s) {
            buff.insert(s);
        }
        return ;
    }
    bool find(string s) {
        int ind = hash_func(s) % data.size(); // 计算哈希值
        recalc_ind(ind, s); // 冲突处理
        if (flag[ind] == false) return false;
        if (data[ind] == s) return true;
        return buff.find(s) != buff.end();
    }

private:
    int cnt;
    vector<string> data;
    vector<bool> flag;
    set<string> buff;
    
    void expand() {
        int n = data.size() * 2;
        HashTable h(n);
        for (int i = 0; i < data.size(); i++) {
            if (flag[i] == false) continue;
            h.insert(data[i]);
        }
        for (auto x : buff) {
            h.insert(x);
        }
        *this = h;
        return ;
    }
    int hash_func(string &s) {
        int seed = 131, hash = 0;
        for (int i = 0; s[i]; i++) {
            hash = hash * seed + s[i];
        }
        return hash & 0x7fffffff;
    }
    void recalc_ind(int &ind, string &s) {
        return ;
    }
};

// 拉链法
class Node {
public :
    Node(string data = "", Node *next = nullptr) : data(), next(nullptr) {}
    string data;
    Node *next;
    void insert(Node *node) {
        node->next = this->next;
        this->next = node;
        return ;
    }
};

class HashTable {
public:
    HashTable(int n = 100) : data(n), cnt(0) {}
    void insert(string s) {
        int ind = hash_func(s) % data.size(); // 计算哈希值
        recalc_ind(ind, s); // 冲突处理
        Node *p = &data[ind];
        while (p->next && p->next->data != s) p = p->next;
        if (p->next == nullptr) {
            p->insert(new Node(s));
            cnt += 1;
            if (cnt > data.size() * 3) expand();
        }
        return ;
    }
    bool find(string s) {
        int ind = hash_func(s) % data.size(); // 计算哈希值
        recalc_ind(ind, s); // 冲突处理
        Node *p = data[ind].next;
        while (p && p->data != s) p = p->next;
        return p != nullptr;
    }

private:
    int cnt;
    vector<Node> data;
    
    void expand() {
        int n = data.size() * 2;
        HashTable h(n);
        for (int i = 0; i < data.size(); i++) {
            Node *p = data[i].next;
            while (p) {
                h.insert(p->data);
                p = p->next;
            }
        }
        *this = h;
        return ;
    }
    int hash_func(string &s) {
        int seed = 131, hash = 0;
        for (int i = 0; s[i]; i++) {
            hash = hash * seed + s[i];
        }
        return hash & 0x7fffffff;
    }
    void recalc_ind(int &ind, string &s) {
        return ;
    }
};


int main() {
    int op;
    string s;
    HashTable h;
    while (cin >> op >> s) {
        switch (op) {
            case 1: h.insert(s); break;
            case 2: cout << "find " << s << " : " << h.find(s) << endl; break;
        }
    }

    return 0;
}

看到这里,不妨来一道PAT的题目练练手吧,PAT题目链接:https://pintia.cn/problem-sets/994805342720868352/exam/problems/994805343236767744
题解代码:

#include<iostream>
#include<vector>
#include<cmath>
using namespace std;
#define MAX_N 20000

int Msize, n, m;
int a[MAX_N + 5], flag[MAX_N + 5];
int cnt;

bool insert(int s) {
    if (cnt == Msize) return false;
    for (int t = 0; t < Msize; t++) {
        int ind = (s + t * t) % Msize;
        if (!flag[ind]) {
            a[ind] = s;
            flag[ind] = 1;
            cnt++;
            return true;
        }
    }
    return false;
}
bool is_prime(int x) {
    if (x < 2) return false;
    for (int i = 2, I = sqrt(x); i <= I; i++) {
        if (x % i == 0) return false;
    }
    return true;
}
int main() {
    // 初始化
    int x;
    scanf("%d%d%d",&Msize, &n, &m);
    while (!is_prime(Msize)) Msize++;

    // 插入部分
    for (int i = 0 ; i < n; i++) {
        scanf("%d", &x);
        bool insert_status = insert(x);
        if (!insert_status) printf("%d cannot be inserted.\n", x);
    }

    // 查找部分
    int ans = 0;
    for (int i = 0; i < m; i++) {
        scanf("%d", &x);
        int t;
        for (t = 0; t <= Msize; t++) {
            ans += 1;
            int ind = (x + t * t) % Msize;
            if (a[ind] == x || flag[ind] == 0) break;
        }
    }
    printf("%.1f\n", ans * 1.0 / m);

    return 0;
}

标签:return,int,布隆,表与,哈希,ind,hash,data
From: https://www.cnblogs.com/jby3303/p/17367406.html

相关文章

  • 哈希表
    题目:给定两个数组 nums1 和 nums2 ,返回 它们的交集 。输出结果中的每个元素一定是 唯一 的。我们可以 不考虑输出结果的顺序 。  foreach的语句格式:for(元素类型t元素变量x:遍历对象obj){   } classSolution{public:vector<int>intersection(......
  • 哈希表总结
    哈希表总结常用数据结构总结数组有些时候,使用数组可以直接充当简单的哈希表,数组元素的下标作为key值,元素的值作为value值比如统计一个单词各个字符出现的次数,因为字母26个数目是有限的,所以数组的下标也是有限的,可以轻松实现。使用数组的情况,数组的下标一般都是有......
  • 哈希表
    \[哈希表\begin{cases}存储结构\begin{cases}开放寻址法\\[2ex]拉链法\\[2ex]\end{cases}\\[3ex]字符串哈希方式\\[3ex]\end{cases}\]一般哈希开放寻址法//开放寻址法模板constintN=(数据范围的2~3倍,质数),null=(数据范围外的数,常用0x3f3f3f3f);inth[N];......
  • VC中实现哈希Hash算法
       Hash函数我们可以自己用C来编写,但是如果在VC中就不必了,因为在VC中有实现hash算法的函数可以调用,就是CryptAcquireContext函数,这个函数的定义在wincrypt.h头文件中。下面是我在MFC中实现的,因为想要结果输出到messagebox中,所以就在视类里定义和实现了GetHash函数来计算哈希值......
  • 【哈希表】LeetCode 895. 最大频率栈
    题目链接895.最大频率栈思路很容易想到使用map:valToFreq来记录每个值出现的频率,这是没问题的,但关键是如何通过频率寻找到应该返回的数。这时候我想到再加一个map:freqToVal来记录每个频率中出现的数字,为了符合题目返回最接近栈顶的元素的要求,freqToVal的键值对类型选择<......
  • 【哈希表】LeetCode 767. 重构字符串
    题目链接767.重构字符串思路先用哈希表统计出出现次数最多的字符,如果这个次数大于一半,说明这个字符总会挨在一起,直接返回""。如果不超过一半,则先把字符填在偶数位置(先填出现次数最多的字符),偶数位置填满了再填奇数位置。代码classSolution{publicStringreorganize......
  • 哈希表
    哈希表1.哈希表基本介绍散列表(HashTable,也叫哈希表),是根据关键码值(KeyValue)而直接进行访问的数据结构。也即是说,它通过把关键码值映射到表中的一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。哈希表的内存布局(数组+链表)2.......
  • 算法学习day07哈希表part02-454、383、15、18
    packageLeetCode.hashpart02;importjava.util.HashMap;importjava.util.Map;/***454.四数相加II*给你四个整数数组nums1、nums2、nums3和nums4,数组长度都是n,请你计算有多少个元组(i,j,k,l)能满足:*0<=i,j,k,l<n*nums1[i]+nums2[j]+nums......
  • Python中的哈希表
    哈希表是一种常用的数据结构,广泛应用于字典、散列表等场合。它能够在O(1)时间内进行查找、插入和删除操作,因此被广泛应用于各种算法和软件系统中。哈希表的实现基于哈希函数,将给定的输入映射到一个固定大小的表格中,每个表项存储一个关键字/值对。哈希函数是一个将任意长度的输入映......
  • 如何实现一个工业级哈希表
    1、散列表碰撞攻击 在极端情况下,攻击者通过精心构造的数据,使得所有的数据都散列到同一个槽里,如果使用链表冲突解决方法,散列表就会退化为链表,查询时间复杂度就从O(1)退化为O(n)。可能因为查询操作消耗大量CPU或者线程资源,导致系统无法响应其他请求,从而达到拒绝服务攻击(DoS......