前言
- 以前的计数排序就是一种哈希结构,通过直接映射,或者间接映射;
- 通过对pair<K,V>中的K进行取模、直接定制法(一元一次方程:Hash(Key)= A*Key + B))、平方取中法、数学分析法、 随机数法、折叠法等等一系列方法,把K映射到一个vector数组上。
- 该方式即为哈希(散列)方法,哈希方法中使用的转换函数称为哈希(散列)函数,构造出来的结构称为哈希表(Hash Table)(或者称散列表)。
哈希冲突
- 发现不同的K值,取模后,可能会用同一个下标(哈希地址),该种现象称为哈希冲突或哈希碰撞。
哈希冲突的解决:闭散列,开散列。
闭散列
- 也叫开放定址法,当发生哈希冲突时,如果哈希表未被装满,说明在哈希表中必然还有空位置,那么可以把key存放到冲突位置中的“下一个” 空位置中去。如果要查找某个元素,找到空也没有找到,说明不存在该元素。
空:用一个枚举类型,表示三种状态:存在、删除、空
线性探测
- 线性探测:从发生冲突的位置开始,依次向后探测,直到寻找到下一个空位置为止。
- 如若走到头了,则和循环队列一样,%一个vector的长度可以回到下标 0 处。
- 我的位置被占了,我就去占别人的位置。。
- 负载因子:存放数据的个数,和数组的长度的比值;比值越低,说明冲突的可能性越小…应严格限制在0.7左右…
二次探测
- 二次探测:找下一个空位置的方法进行了改进,让有相同哈希地址的分散一点,不要太集中。
- 闭散列最大的缺陷就是空间利用率比较低,这也是哈希的缺陷。
ntspt = start+i*2;
namespace close_hash
{
enum status
{
EMPTY,
EXIST,
DEL
};
template<class K,class V>
struct hash_data
{
pair<K,V> _data;
status _st = EMPTY;
};
---
template<class K, class V>
class hash_table
{
typedef hash_data<K, V> hashdata;
public:
....
private:
vector<hashdata> _ht;
size_t _n= 0;//有效数据的个数
};
}
插入
bool Insert(const pair<K, V>& kv)
{
if (find(kv.first))//先看K值在不在数组里
{
return false;
}
if (_n==_ht.size() || _n*10 /_ht.size()>=7) //看是不是空的或者负载因子大于7 越大,说明越冲突
{
size_t newsize = _ht.size() == 0 ? 10 : _ht.size() * 2;
hash_table<K,V> newht;
newht._ht.resize(newsize);
for (auto& i : _ht)
{
if (i._st == EXIST)//把是存在状态的数据插入到新数组里。
{
newht.Insert(i._data);
}
}
_ht.swap(newht._ht);
}
size_t start = kv.first % _ht.size(); //数组的下标
size_t i = 0;
size_t inspt = start + i;//冲突了往后找空位置
while (_ht[inspt]._st == EXIST)
{
++i;
inspt = start + i;
//intspt = start+i*2;
inspt %= _ht.size();
}
_ht[inspt]._data = kv;
_ht[inspt]._st = EXIST;
_n++;
return true;
}
查找、删除
hashdata* find(const K& key)
{
if (_n==0)
{
return nullptr;
}
size_t findk = key%_ht.size();//找坐标
while (_ht[findk]._st != EMPTY)
{
if (_ht[findk]._st == EXIST && _ht[findk]._data.first == key