当前位置: 首页 > news >正文

CPP学习之哈希表

一、哈希概念

顺序结构以及平衡树中,我们查找某个元素往往需要从头开始往后进行多次比较,最好的查询时间复杂度是O(logN),其中N为元素个数,查询速度较慢。
为了解决这个问题,我们想出将元素的内容以及其在容器中的存储位置建立起映射关系,这样就不用从头开始一个个地比较,由此引申出映射关系建立的函数 – 哈希函数。
这种方法叫做哈希方法(也称为散列方法),以此构造出来的结构叫做哈希表(散列表)。
如这个数组 A[] = {1,4,5,6,7,9},设置哈希表的容量为capacity,将哈希函数设置为:
hash_i = key % capacity;
在这里插入图片描述

二、哈希冲突

假如capacity为10,想要存储2和22这两个元素,根据上面的哈希函数就会算出这两个元素处于同一个位置,由此造成哈希冲突。

不同关键字通过相同哈希哈数计算出相同的哈希地址,该种现象称为哈希冲突
或哈希碰撞。

把具有不同关键码(元素内容通过哈希函数算出来的哈希值)而具有相同哈希地址的数据元素称为“同义词”

三、哈希函数

引起哈希冲突的一个重要原因是:哈希函数设计不够合理。

哈希函数设计原则

  1. 哈希函数的定义域要包含所有需要存储元素的关键码,如果哈希表有m个地址时,哈希函数的值域为[0, m-1];
  2. 哈希函数计算出来的哈希值能均匀分布到整个空间中;
  3. 哈希函数应该易于理解。

常见哈希函数

  1. 直接定址法:
    取元素关键字中某个线性函数作为哈希函数:hash_i = A*key + B;其中斜率和截距根据实际情况来取值;
    优点:简单易懂;
    缺点:需要事先知道关键字的分布情况;
    适用场景:适合查找比较小且分布连续的关键字。

  2. 除留余数法:
    设哈希表的地址数为m,取一个<=m但跟m最接近的质数p作为模数,哈希函数设置为:hash_i(key) = key % p; (p<=m),将关键码转化为哈希地址hash_i。

  3. 平方取中法:
    假设元素的关键码为1234,平方后为1522756,取平方后的数值的中间三位227作为哈希地址。
    平方取中法比较适合不知道关键字的分布,而位数又不是很大的情况。

  4. 数学分析法:

设有n个d位数,每一位可能有r种不同的符号,这r种不同的符号在各位上出现的频率不一定相同,可能在某些位上分布比较均匀,每种符号出现的机会均等,在某些位上分布不均匀只有某几种符号经常出现。可根据散列表的大小,选择其中各种符号分布均匀的若干位作为散列地址。
在这里插入图片描述

四、哈希冲突解决

解决哈希冲突的常见方法有两种,分别是:闭散列开散列

  1. 闭散列:
    闭散列也称为开放定址法,假设表内还有剩余空间,此时如果出现哈希冲突,就将出现冲突的元素往后面的空位置存储。
    找空位置又有两种方式:
    方式1:线性探测
    插入元素:从冲突位置开始,往后依次探测位置状态,找空位置,将元素插入。
    删除元素:不能物理地删除闭散列中的元素,而应该将待删除元素的所在位置状态标记为“DELETE”,以免影响其它元素的查找。这种元素删除方法称为伪删除法。
    优点:实现起来简单;
    缺点:一旦发生哈希冲突,且冲突的数据堆积起来,占据了其它元素的映射位置,查找元素的效率就会变慢。

    方式2:二次探测
    线性探测是一个个地往后找空位置,为了避免这样的问题,二次探测的方式为:hash_i(key) = (key + i^2)%m 或者 hash_i(key) = (key - i^2)%m,其中i = 1,2,3…;key为元素值,m为哈希表大小(地址数)。

研究表明:当表的长度为质数且 装载因子α(α = 表中元素个数size / 表的空间大小capacity) 不超过0.5时,新的表项一定能够插入,而且任何一个位置都不会被探查两次。因此只要表中有一半的空位置,就不会存在表满的问题。在搜索时可以不考虑表装满的情况,但在插入时必须确保表的装载因子a不超过0.5,如果超出必须考虑增容。

闭散列的最大缺陷:空间利用率不高。

  1. 开散列:
    开散列又称为链地址法(开链法),将所有元素用哈希函数计算出哈希值,具有相同哈希值的元素作为一个子集合,这个子集合称为,用单链表将子集合中的元素链接起来,哈希表则存储各个单链表的头节点。这种方法又称为哈希桶

在这里插入图片描述
发生冲突的元素就存在桶中。

关于扩容:在载荷因子α达到一定数值时就要对哈希表进行扩容,扩容后哈希函数的模数变大了,那么就需要将原来哈希表中每一个元素按照新的哈希函数重新映射到新的哈希表中。

闭散列:
在这里插入图片描述

开散列最好的情况是:每个哈希桶中刚好挂一个节点,再继续插入元素时,每一次都会发生哈希冲突,因此,在任意一个桶内的元素个数刚好等于哈希表中桶的个数时,可以给哈希表增容。

五、模拟实现哈希表

  1. 闭散列(线性探测版):
#pragma once#include<iostream>
#include<vector>using namespace std;enum state //状态标识
{EXIST,DELETE,EMPTY
};template<class K, class V>
struct hash_data //节点
{pair<K, V> _kv;state _state = EMPTY;
};template<class K>
class hash_func_default //用作仿函数,返回pair中的键
{
public:size_t operator()(const K& key){return (size_t)key;}
};template<> //模板特化,针对字符串的哈希函数
class hash_func_default<string>
{
public:size_t operator()(const string& key){//这个字符串哈希函数称为:BKDR,还有很多不同的字符串哈希函数,可以在网上搜索size_t hash_i = 0;for (auto& e : key){hash_i *= 131;hash_i += e;}return hash_i;}
};template<class K, class V, class hash_func = hash_func_default<K>>
class hash_table
{
private:vector<hash_data<K, V>> _table;size_t _n; //存储有效数据个数public:hash_table():_n(0){_table.resize(10); //用resize可以使vector的size与capacity保持一致}bool insert(const pair<K, V> kv){if ((float)_n / _table.size() >= 0.7) //定义载荷因子,根据载荷因子来判定是否需要扩容,载荷因子一般定义0.7 - 0.8{size_t new_capacity = _table.size() * 2;hash_table<K, V> new_space;new_space._table.resize(new_capacity);for (int i = 0; i < _table.size(); i++){if (_table[i]._state == EXIST) //只插入存在的点,映射关系会重新建立{new_space.insert(_table[i]._kv);}}_table.swap(new_space._table); //交换vector}hash_func hf; //仿函数size_t hash_i = hf(kv.first) % _table.size(); //除留余数法,从第hash_i个位置开始查找// 线性探测while (_table[hash_i]._state == EXIST) //如果走到状态为空或已删除节点,就停下插入{hash_i++;hash_i %= _table.size();}_table[hash_i]._kv = kv;_table[hash_i]._state = EXIST;_n++;return true;}hash_data<const K, V>* find(const K& key) //为了不让kv的first可更改,将其修饰为const{hash_func hf;size_t hash_i = hf(key) % _table.size(); //从第hash_i个位置开始找while(_table[hash_i]._state != EMPTY) //注意:走到EMPTY位置意味着当前位置到第一个EXIST位置之间,都是EMPTY状态的,就不用继续往下走了!{if (_table[hash_i]._state == EXIST && key == _table[hash_i]._kv.first){return (hash_data<const K, V>*)&_table[hash_i]; //强制类型转换(指针层面上),返回其地址。这里与非const到const迭代器的转换不同(类层面),迭代器转换本质上是构建新类的对象}hash_i++;hash_i %= _table.size();}return nullptr;}bool erase(const K& key) //伪删除法{hash_data<const K, V>* ret = find(key);if (ret){ret->_state == DELETE;_n--;return true;}return false;}void print(){for (auto& e : _table){if (e._kv.first != ""){cout << e._kv.first << " " << e._kv.second << endl;}}}
};
  1. 开散列(哈希桶):
    下面代码的class key_of_T 用来实现仿函数,作用是取unordered_map和unordered_set的key,在实现unordered_map和unordered_set的时候会用到。
#pragma once#include<iostream>
#include<vector>using namespace std;template<class T>
struct hash_node
{T _data;hash_node* _next;hash_node(T data):_data(data),_next(nullptr){ }
};template<class K>
struct hash_func_default
{size_t operator()(const K& key){return (size_t)key;}
};template<>
struct hash_func_default<string>
{size_t operator()(const string& key){size_t hash_i = 0;for (auto& e : key) //BKDR{hash_i *= 131;hash_i += e;}return hash_i;}
};template<class K, class T, class key_of_T,class hash_func>
class hash_table
{typedef hash_node<T> node;private:vector<node*> _table;size_t _n;public:hash_table():_n(0){_table.resize(10, nullptr);}~hash_table(){for (int i = 0; i < _table.size(); i++){node* cur = _table[i];while (cur){node* next = cur->_next;delete cur;cur = next;}}}bool insert(T data){hash_func hf;key_of_T kot;if (find(kot(data))) //去重{return false;}if (_n % _table.size() == 1) //扩容判断,载荷因子为1时扩容{size_t new_size = _table.size() * 2;vector<node*> new_table;new_table.resize(new_size, nullptr);for (int i = 0; i < _table.size(); i++) //将旧vector的链子(桶)挂到新vector中{node* cur = _table[i];while (cur){node* next = cur->_next;size_t hash_i = hf(kot(cur->_data)) % new_table.size();cur->_next = new_table[hash_i];new_table[hash_i] = cur;cur = next;}_table[i] = nullptr;}_table.swap(new_table);}size_t hash_i = hf(kot(data)) % _table.size();node* new_node = new node(data);new_node->_next = _table[hash_i];_table[hash_i] = new_node;_n++;return true;}node* find(const K& key){hash_func hf;key_of_T kot;size_t hash_i = hf(key) % _table.size();node* cur = _table[hash_i];while (cur){if (hf(kot(cur->_data)) == hf(key)){return cur;}cur = cur->_next;}return nullptr;}bool erase(const K& key){hash_func hf;key_of_T kot;size_t hash_i = hf(key) % _table.size();node* cur = _table[hash_i];node* prev = nullptr;while (cur){if (kot(cur->_data) == key){if (prev == nullptr) //一开始就找到的情况{_table[hash_i] = cur->_next;}else //其他情况{prev->_next = cur->_next;}delete cur;return true;}prev = cur;cur = cur->_next;}return false;}};

六、哈希应用

1. unordered_map 与 unordered_set 封装

改造后的哈希表:hash_table_2.h

#pragma once#include<iostream>
#include<vector>using namespace std;template<class T>
struct hash_node
{T _data;hash_node* _next;hash_node(T data):_data(data),_next(nullptr){ }
};template<class K>
struct hash_func_default
{size_t operator()(const K& key){return (size_t)key;}
};template<>
struct hash_func_default<string>
{size_t operator()(const string& key){size_t hash_i = 0;for (auto& e : key) //BKDR{hash_i *= 131;hash_i += e;}return hash_i;}
};template<class K, class T, class key_of_T, class hash_func = hash_func_default<K>>
class hash_table; //_hash_iterator用到了hash_table,需要提前声明有hash_table表这个类template<class K, class T,class ptr, class ref, class key_of_T, class hash_func>
struct _hash_iterator
{typedef hash_table<K, T, key_of_T, hash_func> table; typedef _hash_iterator<K, T, ptr, ref, key_of_T, hash_func> self; //任意迭代器类型,取决于给的模板参数typedef _hash_iterator<K, T, T*, T&, key_of_T, hash_func> iterator; //普通迭代器类型const table* _htp; //哈希表指针hash_node<T>* _node; //哈希表节点的修改是通过这个节点指针来修改的,而不是哈希表的指针!_hash_iterator(hash_node<T>* node, const table* tb):_node(node),_htp(tb){}_hash_iterator(const iterator& it) //传的参数是普通迭代器//可以从非const转换成const迭代器,传进去的和接收的是同类型就是拷贝构造,如果不一样就是构造。这个构造函数很重要!!!在文章后面会讲到原因。:_node(it._node), _htp(it._htp){}ref operator*() //返回引用{return _node->_data;}ptr operator->() //返回指针{return &_node->_data;}bool operator!=(const self& it){return _node != it._node;}bool operator==(const self& it){return _node == it._node;}self& operator++(){key_of_T kot;hash_func hf;if (_node->_next) //当前桶没走完{_node = _node->_next;}else //要走下一个桶{size_t hash_i = hf(kot(_node->_data)) % _htp->_table.size(); //找到当前桶的位置 //这里用到了哈希表的私有成员,需要声明友元hash_i++;while (hash_i < _htp->_table.size()){if (_htp->_table[hash_i]){_node = _htp->_table[hash_i];break;}else{hash_i++;}}if(hash_i == _htp->_table.size())_node = nullptr; //走完了就说明没有,返回空的指针}return *this;}};template<class K, class T, class key_of_T, class hash_func>
class hash_table
{typedef hash_node<T> node;template<class K, class T, class ptr, class ref, class key_of_T, class hash_func>friend struct _hash_iterator; //带模板类的友元声明需要把模板也写下来才行private:vector<node*> _table;size_t _n;public:hash_table():_n(0){_table.resize(10, nullptr);}~hash_table(){for (int i = 0; i < _table.size(); i++){node* cur = _table[i];while (cur){node* next = cur->_next;delete cur;cur = next;}}}typedef _hash_iterator<K, T, T*, T&, key_of_T, hash_func> iterator;typedef _hash_iterator<K, T, const T*, const T&, key_of_T, hash_func> const_iterator;iterator begin(){for (int i = 0; i < _table.size(); i++){if (_table[i]){return iterator(_table[i], this); //找到第一个不为空的桶}}return iterator(nullptr, this); //找不到就返回一个空的迭代器}iterator end(){return iterator(nullptr, this);}const_iterator begin() const{for (int i = 0; i < _table.size(); i++){if (_table[i]){return const_iterator(_table[i], this); //找到第一个不为空的桶}}return const_iterator(nullptr, this); //找不到就返回一个空的迭代器}const_iterator end() const //此处的this是const修饰过的{return const_iterator(nullptr, this);}pair<iterator, bool> insert(T data){hash_func hf;key_of_T kot;iterator it = find(kot(data));if (it != end()) //去重{return make_pair(it, false);}if (_n % _table.size() == 1) //扩容判断{size_t new_size = _table.size() * 2;vector<node*> new_table;new_table.resize(new_size, nullptr);for (int i = 0; i < _table.size(); i++) //将旧vector的链子挂到新vector中{node* cur = _table[i];while (cur){node* next = cur->_next;size_t hash_i = hf(kot(cur->_data)) % new_table.size();cur->_next = new_table[hash_i];new_table[hash_i] = cur;cur = next;}_table[i] = nullptr;}_table.swap(new_table);}size_t hash_i = hf(kot(data)) % _table.size();node* new_node = new node(data);new_node->_next = _table[hash_i];_table[hash_i] = new_node;_n++;return make_pair(iterator(new_node, this), true);}iterator find(const K& key){hash_func hf;key_of_T kot;size_t hash_i = hf(key) % _table.size();node* cur = _table[hash_i];while (cur){if (hf(kot(cur->_data)) == hf(key)){return iterator(cur, this);}cur = cur->_next;}return iterator(nullptr, this);}bool erase(const K& key){hash_func hf;key_of_T kot;size_t hash_i = hf(key) % _table.size();node* cur = _table[hash_i];node* prev = nullptr;while (cur){if (kot(cur->_data) == key){if (prev == nullptr) //一开始就找到的情况{_table[hash_i] = cur->_next;}else //其他情况{prev->_next = cur->_next;}delete cur;return true;}prev = cur;cur = cur->_next;}_n--;return false;}};

my_unordered_set.h:

#include"hash_table_2.h"template<class K>
class my_unorder_set
{
private:struct set_key_of_T //用于返回key{const K& operator()(const K& key){return key;}};hash_table<K, K, set_key_of_T> _ht;public:typedef typename hash_table<K, K, set_key_of_T>::const_iterator iterator; //告诉编译器iterator是一个内嵌类型typedef typename hash_table<K, K, set_key_of_T>::const_iterator const_iterator;bool insert(const K& key){pair<iterator, bool> ret = _ht.insert(key);return ret.second;}iterator begin(){return _ht.begin();}iterator end(){return _ht.end();}const_iterator begin() const{return _ht.begin();}const_iterator end() const{return _ht.end();}};

my_unordered_map.h:

#include"hash_table_2.h"template<class K, class V>
class my_unorder_map
{
private:struct map_key_of_T //用于返回key{const K& operator()(const pair<K, V>& kv){return kv.first;}};hash_table<K, pair<const K, V>, map_key_of_T> _ht;public:bool insert(const pair<const K, V>& kv){auto ret = _ht.insert(kv);return ret.second;}typedef typename hash_table<K, pair<const K, V>, map_key_of_T>::iterator iterator; //告诉编译器iterator是一个内嵌类型typedef typename hash_table<K, pair<const K, V>, map_key_of_T>::const_iterator const_iterator;iterator begin(){return _ht.begin();}iterator end(){return _ht.end();}const_iterator begin() const{return _ht.begin();}const_iterator end() const{return _ht.end();}V& operator[](const K& key) //为了实现方括号重载,需要重新设计哈希表的insert函数,hash_table_2.h的insert函数与上面开散列部分的insert函数的返回值不一样了{pair<iterator, bool> ret = _ht.insert(make_pair(key, V()));return ret.first->second;}
};

关于如何理解非const迭代器到const迭代器的转换(重点!!!):
举一个例子:

my_set.h内

	typedef typename rb_tree<K, K, set_key_of_T>::const_iterator iterator; //因为set的元素都不可更改,所以迭代器都是用的const//类模板还没实例化,编译器不确定iterator是内嵌类型还是静态成员变量,所以加typename确定iterator是内嵌类型typedef typename rb_tree<K, K, set_key_of_T>::const_iterator const_iterator;iterator begin(){return _t.begin();}iterator end(){return _t.end();}

RB_tree.h内:

	typedef __iterator<T, T*, T&> iterator;typedef __iterator<T, const T*, const T&> const_iterator;const_iterator begin() const //this指向对象不可更改{node* left_most = _root;while (left_most && left_most->_left) //空树也是红黑树{left_most = left_most->_left;}return const_iterator(left_most);}const_iterator end() const //this指向对象不可更改{return const_iterator(nullptr);}

迭代器的构造:

template<class T, class ptr, class ref >
struct __iterator
{typedef rb_tree_node<T> node;typedef __iterator self;typedef __iterator<T, T*, T&> iterator;node* _node; //迭代器的成员变量就是容器元素的指针__iterator(const iterator& it) //这个不是标准的拷贝构造函数,而是转换构造,其既可以是构造函数也可以用作拷贝构造函数。:_node(it._node){}__iterator(node* new_node):_node(new_node){}.......
}

问题:为什么如果没有这个转换构造函数,my_set.h内返回值为const迭代器的begin和end函数就会出错?

答:按照顺序一步步来:
在my_set.h中,iterator和const_iterator都是同一类型:rb_tree<K, K, set_key_of_T>::const_iterator;my_set的begin()和end()函数内调用的是rb_tree类的begin和end。
在RB_tree.h中,rb_tree类的begin函数内用一个node类型的参数构造一个迭代器,并返回这个迭代器,而且迭代器的类型是const_iterator,即__iterator<T, const T, const T&>类型,如果没有转换拷贝构造函数,那么将node类型的参数传给迭代器的构造函数,构造函数只能构造出非const版本迭代器,而rb_tree的begin要求的返回值却是const_iterator类型,编译器找不到从“非const 迭代器”到“const 迭代器”的合法转换,这就造成了转换出错,于是编译器报错。
为了解决这个问题,文件内需要编写一个将非const迭代器转换成const迭代器的转换构造函数。首先用node
类型的参数传进迭代器__iterator的构造函数中构造出一个非const的普通迭代器,然后再将这个普通迭代器传进转换构造函数中(这一步就是关键!!!),进行非const到const类型的转换,最后返回给begin函数,begin函数的返回值类型就匹配了。虽然在这个过程中我们并没有主动调用构造函数,但编译器却走了这一步,这叫做:通过构造函数进行的隐式转换。

转换构造函数:允许跨模板实参转换的构造函数,从非const迭代器到const迭代器。(用户定义隐式转换)

那什么时候构造出非const迭代器什么时候又构建出const迭代器?
要明白,无论是构造函数还是拷贝构造函数,在构造对象时不看传的参数是什么类型的,而取决接收这个结果的对象的类型。
附: 这样的话自动类型推导auto会受影响,这种情况建议不要用。
为什么非要这么绕?
因为 STL 的设计哲学是:const 迭代器必须能从非 const 迭代器构造,但不能反过来。

说了一大堆,这么做的主要原因就是set的迭代器无论是const还是非const,用的都是const迭代器。

2. 位图

有这么一个问题:给40亿个不重复的无符号整数,没排过序。给一个无符号整数,如何快速判断一个数是否在这40亿个数中。
要求:
1. 遍历,时间复杂度O(N)
2. 排序(O(NlogN)),利用二分查找: logN

这个问题的关键在于查找一个数在不在这堆海量的数据中,如果真开40亿个整数的空间未免也太大了(4 * 10^9 * 4 Byte ≈ 16 * 10^3 MB ≈ 16 GB),而且一般游戏本的内存也就16GB,直接将这些数据塞进内存中处理是不现实的。

按照哈希表的思想来走,我们是否可以将每个整数按照比特位来进行映射呢?
假设1个整数对应1个比特位,40亿整数对应40亿比特位:

4 * 10^9 bit ≈ 5 * 10^8 Byte ≈ 5 * 10^2 MB ≈ 0.5 GB
瞬间减少了15.5 GB

位图:用每一位来存放某种状态,适用于海量数据,数据无重复的场景。通常是用
来判断某个数据存不存在。

小端存储这里是引用

位图实现:

#include<iostream>
#include<vector>using namespace std;namespace my_space
{template<size_t N>class bit_map{private:vector<int> _a;public:bit_map(){_a.resize(N / 32 + 1, 0); //一个整形4字节,一字节8bit}void set(size_t x) //设置对应整数的比特位{//整数对应比特位映射size_t i = x / 32; //找到所属字节size_t j = x % 32; //找到字节中的对应位//对应比特位置1_a[i] |= (1 << j); //让1左移j位找到对应位并按位或}void reset(size_t x) //重置为0{size_t i = x / 32; //找到所属字节size_t j = x % 32; //找到字节中的对应位 _a[i] &= ~(1 << j); //重置对应位}bool test(size_t x){size_t i = x / 32; //找到所属字节size_t j = x % 32; //找到字节中的对应位return (_a[i] & (1 << j)); //找到待查找数字x在位图中对应的位的位置,让1左移到对应位置然后按位与,若找到了就是非零,找不到就是0}};
}void test_1()
{my_space::bit_map<100> bt;bt.set(1);bt.set(10);bt.set(100);cout << bt.test(1) << endl; //找到就返回truecout << bt.test(10) << endl;cout << bt.test(99) << endl;cout << bt.test(100) << endl << endl;bt.reset(100);bt.set(99);cout << bt.test(1) << endl; //找到就返回truecout << bt.test(10) << endl;cout << bt.test(99) << endl;cout << bt.test(100) << endl << endl;}void test_2()
{my_space::bit_map<-1> bt;//因为模板参数类型是无符号整形,想找40亿个整数中的一个,就需要开2的32次方bit的空间,约42亿位bt.set(999);bt.set(86);bt.set(24000000);cout << bt.test(999) << endl; //找到就返回truecout << bt.test(86) << endl;cout << bt.test(24000000) << endl;cout << bt.test(100) << endl << endl;}int main()
{//test_1();test_2();return 0;
}

3. 布隆过滤器

在处理海量数据时,哈希表的缺陷是空间耗费极大,位图的缺陷是只能处理整形,如果是字符串就无法处理。

如果我们要在海量字符串中查找某一字符串在不在其中,该怎么解决?
结合哈希表和位图,对于任意一个字符串,我们用多个哈希函数得到多个哈希地址,将这些哈希地址在位图中置1。查找某个字符串时,我们先查找位图中其对应的多个位置的状态是否为1,如果都为1,则该字符串极有可能存在于这堆海量数据中。(为什么不能确定其一定存在?因为不同字符串可能映射到完全相同的多个位置上)

布隆过滤器是由布隆(Burton Howard Bloom)在1970年提出的 一种紧凑型的、比较巧妙的概率型数据结构,特点是高效地插入和查询,可以用来告诉你 “某样东西一定不存在或者可能存在”,它是用多个哈希函数,将一个数据映射到位图结构中。此种方式不仅可以提升查询效率,也可以节省大量的内存空间。
在这里插入图片描述

布隆过滤器实现:

#pragma once
#include"my_bit_set.h"// 设置多个哈希函数
struct BDKR_hash
{size_t operator()(const string& str){size_t hashi = 0;for (auto& e : str){hashi *= 131;hashi += e;}return hashi;}
};struct APHash
{size_t operator()(const string& str){size_t hash = 0;for (size_t i = 0; i < str.size(); i++){size_t ch = str[i];if ((i & 1) == 0){hash ^= ((hash << 7) ^ ch ^ (hash >> 3));}else{hash ^= (~((hash << 11) ^ ch ^ (hash >> 5)));}}//cout << "APHash:" << hash << endl;return hash;}
};struct DJBHash
{size_t operator()(const string& str){size_t hash = 5381;for (auto ch : str){hash += (hash << 5) + ch;}//cout << "DJBHash:" << hash << endl;return hash;}
};template<size_t N, class T = string, class hash_1 = BDKR_hash, class hash_2 = APHash, class hash_3 = DJBHash>
class bloom_filter
{
private:bit_map<N> _bm;public:void set(const T& key){size_t hashi_1 = hash_1()(key) % N; //匿名对象+使用仿函数size_t hashi_2 = hash_2()(key) % N;size_t hashi_3 = hash_3()(key) % N;_bm.set(hashi_1);_bm.set(hashi_2);_bm.set(hashi_3);}bool test(const T& key){size_t hashi_1 = hash_1()(key) % N;if (!_bm.test(hashi_1)){return false;}size_t hashi_2 = hash_2()(key) % N;if (!_bm.test(hashi_2))return false;size_t hashi_3 = hash_3()(key) % N;if (!_bm.test(hashi_3))return false;return true;}
};

七、总结

本文讲述了哈希表的原理,实现方法和注意事项以及相关应用,内容较多,难免有错误的地方,如有发现,请批评指正,感谢阅读!

http://www.dtcms.com/a/445492.html

相关文章:

  • Java “并发工具类”面试清单(含超通俗生活案例与深度理解)
  • 2025 AI伦理治理破局:从制度设计到实践落地的探索
  • 力扣1984. 学生分数的最小差值
  • Android studio -kt构建一个app
  • 4.数据类型
  • Spring Boot SSE 流式输出,智能体的实时响应
  • Linux系统性能监控—sar命令
  • PostgreSQL备份不是复制文件?物理vs逻辑咋选?误删还能精准恢复到1分钟前?
  • 网站开发主管招聘wordpress 手机悬浮
  • 描述逻辑对人工智能自然语言处理中深层语义分析的影响与启示
  • 首屏加载耗时从5秒优化到1秒内:弱网与低端安卓机下的前端优化秘笈
  • 【新版】Elasticsearch 8.15.2 完整安装流程(Linux国内镜像提速版)
  • LeetCode 分类刷题:74. 搜索二维矩阵
  • 网站建设项目职责memcache安装wordpress
  • MySQL查看数据表锁定情况
  • sq网站推广用jsp做的网站源代码下载
  • 玩转ClaudeCode:通过Chrome DevTools MCP实现高级调试与反反爬策略
  • 国内做焊接机器人平台网站网络营销的方法是什么
  • 网站建设一般用什么软件敏捷模型是软件开发模型吗
  • 做网站好的品牌泰安房产网签查询
  • No商业网站建设wordpress 调用插件
  • 免费模板网站都有什么区别合肥网络seo
  • 什么是网站地址云服务器上放多个网站
  • 电子商务网站费用预算必须在当地网站备案
  • 遵义市播州区建设厅网站镇江网站建设和优化推广多少钱
  • 安阳建设网站哪家好久久项目咨询有限公司
  • 3g门户网站无锡企业网站制作
  • 手表回收网网站个人网页设计作品介绍
  • 如何用vps系统搭建企业网站以及邮箱系统网站建设运营预算
  • 贵阳网站建设app开发修仙网页游戏大全