仿函数
为了实现自定义类型放入哈希表中,我们只需要自己写一个仿函数,这个仿函数只需要得到你传入类型的哈
希值就可以放入到哈希表中
例如string类型
template<>
struct HashFunc<string>
{
size_t operator()(const string& s)
{
size_t hash = 0;
for (auto e : s)
{
hash += e;
hash *= 131;
}
return hash;
}
};
我们这里让hash每次都*131,目的是为了减少不同字符串的hash值相同的情况,如果hash值相同,我们就无法同时将两个值都放入hash表中
template<class K>
struct HashFunc
{
size_t operator()(const K& key)
{
return (size_t)key;
}
};
如果我们传入的是整数类型(无论是long long 还是int)直接取正就可以了,这里我们让这个仿函数为类型的缺省值
数据类型
enum State
{
EMPTY,
EXIST,
DELETE
};
template<class K, class V>
struct HashData
{
pair<K, V> _kv;
State _state = EMPTY; // 标记
};
由于我们使用的是开放定址法,所以我们需要在哈希表中的每个元素都加一个状态来控制整个哈希表的插入删除搜索等等,需要EMPTY EXIST DELETE 三个状态即可
初始状态设置为EMPTY 如果插入新的元素,状态改为EXIST (存在),在查找的时候只需要访问状态为EXIST的节点即可
删除元素之后,将状态改为DELETE 之后的操作不访问该元素就可以了
哈希表
template<class Key_type, class Val_type,class Hash = HashFunc<Key_type>>
class HashTable
{
public:
HashTable(size_t n = 10)
{
_table.resize(n);
}
private:
vector<HashData<Key_type, Val_type>> _table;
size_t _n = 0;
};
我们直接用vector当作哈希表即可,_n用来标记我们一共插入了多少个节点,因为我们在hash表中存放的数据是不连续的,所以_table.size()或者_table.capacity()完不成任务
初始开10个空间,我们这里保证存放数据的个数不超过总空间的70% ,如果这个数过大的话,就会影响插入数据的效率
插入
bool Insert(const pair<Key_type, Val_type>& kv)
{
if (Find(kv.first))
{
return false;
}
if (_n * 10 / _table.size() >= 7) //0.7//扩容
{
HashTable<Key_type, Val_type, Hash> NewTable(_table.size() * 2);
for (auto& e : _table)
{
if (e._sta = EXIST)
{
NewTable.Insert(e._kv);
}
}
_table.swap(NewTable._table);
}
Hash hs
size_t hashi = hs(kv.first) % _table.size();
while (_table[hashi]._sta == EXIST)//找到合适位置
{
++hashi;
hashi %= _table.size();
}
//修改需要修改的值
_table[hashi]._kv = kv;//赋值
_table[hashi]._sta = EXIST;//改状态
++_n;//改数量
return true;
}
一提到插入,我们首先想到的就是是否会超出空间,空间是否会不足,就一定要扩容,这里,①将数据拷贝到新的表中,②然后交换新旧表,③等NewTable到了生命周期会自动销毁,我们的旧表就顺便销毁了。不会内存泄漏
如果我们需要插入的元素已经存在,那么直接返回false即可
接下来是插入,①我们需要找到要插入数据的key 在hash表中的合适位置,②如果位置被占用,则往后线性搜索空位置即可,③找到了合适位置,先赋值,然后该状态,然后改一下_n
删除
bool Erase(const Key_type& key)
{
HashData<Key_type, Val_type>* ret = Find(key);//先找到要删除节点的地址
if (ret)//找到了
{
_n--;
_ret->_sta = DELETE;//只需要改状态就可以,因为状态控制着访问
return true;
}
else//没找到
return false;
}
删除相对简单,我们只需要找到我们需要删除节点的地址,然后将该节点的状态改为DELETE即可,记得修改_n
搜索
HashData<Key_type, Val_type>* Find(const Key_type & key)
{
Hash hs;
size_t hashi = hs(key) % _table.size();
while (_table[hashi]._sta != EMPTY)
{
if (key == _table[hashi]._kv.first &&
_table[hashi]._sta == EXIST)
{
return &_table[hashi];
}
++hashi;
hashi %= _table.size();
}
return nullptr;
}
由于我们插入的时候,如果合适位置被占用,会线性向后查找空位置,所以我们查找的时候采用相同策略即可,①先去合适的位置找,②如果没有的话,③线性向后访问,④直到后边出现空节点位置,说明后边一定没有我们需要的key,这里如果遇到DELETE的话不会影响搜索过程,直接会跳出分支语句