赞
踩
一.哈希(按某种规律将某个值存储到固定位置)
除留余数法 key % len
直接映射一定是有哈希碰撞/哈希冲突
针对的是值相对连续的做法,如果说值特别的散,那么效率会很低
方法一:线性探测,从发送冲突的位置开始,依次向后探测,直到找到下一个空位置为止
(图书馆抢位置,别人如果枪了我的,我就去抢别人的,强盗逻辑)
构建哈希表:
一个vector,一个记录有效个数的变量n
如果vector的最后一个数据冲突则返回到vector的第一个位置继续找空位
删除元素后查找遇到空时回暂停,如何解决这种问题呢?加标记,每个数据给一个枚举状态值,这样删数据只需要将其状态值修改
查找时遇到空EMPTY就停止了,遇到删除DELETE还要继续找
负载因子定义 α = 填入表中的元素个数 / 散列表的长度
α越大,冲突的概率越大,效率低,但是浪费少
α越小,冲突的概率越小,效率高,但是浪费严重
接口:
【 插入 】算插入位置,膜vector的size而不是capacity ,扩容的写法(α>0.7)
扩容时:直接new一个新的table,将原table中元素插入其中,交换指针即可,复用之前insert
重复插入的处理(通过Find接口)
插入的数据不一定是整数,可能是string…用仿函数的方式来解决,取模的地方就用仿函数处理一下
各种字符串Hash函数-博客园
注意:
vector的reserve以后为什么不能cin>>v[i],因为方括号自带检查,reserve只是开空间并未初始化,所以要用resize(默认初始化为0)
方法二:二次探测
引入二次探测背景:连续位置的冲突值比较多的话,引发踩踏洪水效应,按照方法一找邻居的话比较容易冲突
#pragma once #include<vector> #include<iostream> #include<cstdio> using namespace std; template<class K> struct Hash { size_t operator()(const K& key) { return key; } }; //特化版本 //传string要将它转化成整数,这边先使用BKDR的方法 template<> struct Hash <string> { size_t operator()(const string& s) { //BKDR size_t value = 0; for (auto ch : s) { value *= 31; value += ch; } return value; } }; //闭散列的方式 namespace CloseHash { enum Status { EXIST, EMPTY, DELETE }; template<class K,class V> struct HashData { //保存一对键值对 pair<K, V> _kv; //保存此数据的状态 Status _status = EMPTY; }; template<class K, class V,class HashFunc = Hash<K>> class HashTable { public: bool Erase(const K& key) { HashData<K, V>* res = Find(key); if (key) { //找到的情况就可以进行删除 //只需要将状态置Delete即可,有效个数减1 --_n; res->_status = DELETE; return true; } else { //没找到就返回false return false; } } HashData<K, V>* Find(const K& k) { if (_tables.size() == 0) return nullptr; HashFunc hf; //算起始点,往后线性探测/二次探测查找待插入位置 size_t start = hf(k) % _tables.size(); size_t index = start; size_t i = 0; //做的是一个连续的查找,不能中间是断的 //也就是说遇到空就会停止 while (_tables[index]._status != EMPTY) { if (_tables[index]._kv.first == k && _tables[index]._status == EXIST) { return &_tables[index]; } ++i; index = start + i; //防止越界 index %= _tables.size(); } return nullptr; } //Insert是插入一对键值对 bool Insert(const pair<K,V>& kv) { //如果本身就有这个元素的话,就避免重复插入 HashData<K, V>* res = Find(kv.first); if (res) { return false; } //负载因子到0.7就扩容 //负载因子越小,冲突的概率就越低,效率就越高,但是空间浪费多 //负载因子越大,冲突的概率就越高,效率就越低,但是空间浪费少 //没有元素时扩容,负载因子到时扩容 if (_tables.size() == 0 || _n * 10 / _tables.size() >= 7) { size_t newSize = _tables.size()== 0 ? 10 : _tables.size() * 2; HashTable<K, V, HashFunc> newHT; newHT._tables.resize(newSize); for (size_t i = 0; i < _tables.size(); i++) { newHT.Insert(_tables[i]._kv); } _tables.swap(newHT._tables); } HashFunc hf; size_t start = hf(kv.first) % _tables.size(); size_t i = 0; size_t index = start; while (_tables[index]._status == EXIST) { ++i; index = start + i; //防止下标出界 index %= _tables.size(); } //找到位置以后 _tables[index]._kv = kv; _tables[index]._status = EXIST; ++_n; return true; } private: //用一个vector来存储Hash元素,n表示tables中有效元素的个数 vector<HashData<K, V>> _tables; size_t _n = 0; }; void HashTableTest() { HashTable<int, int> ht; int a[] = { 2, 12, 22, 32, 42, 52, 62 }; for (auto e : a) { ht.Insert(make_pair(e, e)); } ht.Insert(make_pair(72, 72)); ht.Insert(make_pair(32, 32)); ht.Insert(make_pair(-1, -1)); ht.Insert(make_pair(-999, -999)); Hash<int> hs; cout << hs(9) << endl; cout << hs(-9) << endl; cout << ht.Find(12) << endl; ht.Erase(12); cout << ht.Find(12) << endl; } void HashTableTest2() { HashTable<string, string> ht; ht.Insert(make_pair("sort", "排序")); ht.Insert(make_pair("string", "字符串")); } }
是闭散列/开放定址法的优化:闭散列不同位置冲突数据会互相影响,相互位置的冲突会争抢位置,互相影响效率
开散列相邻位置冲突,不会再争抢位置,不会互相影响
控制负载因子 – 负载因子到了就扩容
极端场景防止一个桶冲突太多,链表太长:当一个桶长度超过一定长以后,转换为红黑树(Java中链表的长度超过了8会变成红黑树)
例子:
表长度100
极端场景:
1 .存了50个值,40个值是冲突的,挂再一个桶中
2 .存储了10000个值,平均每个桶长度100,阶段场景有些桶可能有上千个值
【插入】
扩容:这种方法中的扩容最好不要复用自己的Insert,因为会new新的节点,这样是没有必要的,可以将原表中的节点一个个链下来,
注意是一个个,而不是一个桶(因为桶中的元素映射到了新表中可能对应新的位置)
table[i]的节点都被拿走了,最好要把它置空一下
newtable是我们的table想要的,把他们的指针再交换一下
【删除】
namespace LinkHash { template<class K,class V> struct HashNode { pair<K, V> _kv; HashNode<K, V>* _next; HashNode(const pair<K,V>& kv) :_kv(kv), _next(nullptr) { } }; template<class K,class V,class HashFunc = Hash<K>> class HashTable { typedef HashNode<K, V> Node; public: bool Erase(const K& key) { //表空就不用删除 if (_tables.empty()) { return false; } HashFunc hf; size_t index = hf(key) % _tables.size(); Node* prev = nullptr; Node* cur = _tables[index]; while (cur) { if (cur->_kv.first == key) { if (prev == nullptr) { //头删 _tables[index] = cur->_next; } else { //中间删除 prev->_next = cur->_next; } --_n; delete cur; return true; } else { prev = cur; cur = cur->_next; } } return false; } Node* Find(const K& key) { //表为空直接返回false if (_tables.empty())
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。