开放定址法也叫闭散列,是解决哈希冲突的一种方法,当发生哈希冲突之后,如果哈希表没有被装满(正常情况哈希表不会被装满的),那就向后移动,寻找一个没有元素的地址,然后插入。下面介绍移动寻找合适地址的方法:线性探测。
线性探测: 当发生哈希冲突时,依次向后探测,直到寻找到下一个空位置为止。
通过代码来实现插入:
enum State
{
EMPTY,
EXIST,
DELETE
};
template<class K, class V>
struct Hash
{
pair<K, V> _kv;
State _state = EMPTY;
};
template<class K, class V>
class hashTable
{
public:
bool Insert(const pair<K, V>& val)
{
}
private:
vector<Hash<K, V>> _tables;
int _size = 0;
};
template<class K, class V>
class hashTable
{
public:
bool Insert(const pair<K, V>& val)
{
//需要注意的是,一定是除余size,而不是capacity
size_t hashi = val.first % _tables.size();
size_t j = 1;
size_t index = hashi;
//如果发生了哈希冲突,也就是此处地址上存在元素,那就向后探测
while (_tables[index]._state == EXIST)
{
index = hashi + j;//每次都是向后探测一位,j++.
index = index % _tables.size();//保证了向后探测时如果到了空间末尾,那就从空间开
//头处继续探测,保证了能够探测到一个合适的位置
j++;
}
//此时index处不存在元素:
_tables[index]._kv = val;
_tables[index]._state = EXIST;
_size++;
return true;
}
private:
vector<Hash<K, V>> _tables;
int _size = 0;
};
写完上面代码会发现以下问题:
template<class K, class V>
class hashTable
{
public:
bool Insert(const pair<K, V>& val)
{
//负载因子超过0.7就扩容
if (_tables.size() == 0 || _size * 10 / _tables.size() >= 7)
{
size_t newSize = _tables.size() == 0 ? 10 : _tables.size() * 2;
hashTable<K, V> newTables;
newTables._tables.resize(newSize);
//扩容以后由于size的改变,部分元素的位置也会改变,所以需要重新计算位置并插入
//将原数组中的数组重新插入新数组
if (_tables.size())
{
int i = 0;
while (_tables[i]._state == EXIST)
{
newTables.Insert(_tables[i]._kv);
i++;
}
}
//重新插入新数组也可以这么实现
/*for (auto& data : _tables)
{
if (data._state == EXIST)
{
newTables.Insert(data._kv);
}
}*/
_tables.swap(newTables._tables);
}
//需要注意的是,一定是除余size,而不是capacity
size_t hashi = val.first % _tables.size();
size_t j = 1;
size_t index = hashi;
//如果发生了哈希冲突,也就是此处地址上存在元素,那就向后探测
while (_tables[index]._state == EXIST)
{
index = hashi + j;//每次都是向后探测一位,j++.
index = index % _tables.size();//保证了向后探测时如果到了空间末尾,那就从空间开
//头处继续探测,保证了能够探测到一个合适的位置
j++;
}
//此时index处不存在元素:
_tables[index]._kv = val;
_tables[index]._state = EXIST;
_size++;
return true;
}
private:
vector<Hash<K, V>> _tables;
int _size = 0;
};
Hash<K, V>* Find(const K& key)
{
//如果数组为空就没必要查找了
if (_tables.size() == 0)
{
return nullptr;
}
size_t hashi = key % _tables.size();
size_t i = 1;
size_t index = hashi;
//之前考虑过如果数组没有装满,元素之间肯定有很多的空隙会为EMPTY,
//如果这么写的话会不会明明目标位置就在后面,但是早早停下了,但是
//仔细一想,插入的时候,就是向后探测,只要遇到EMPTY就插入了,所以
//绝对不会出现这种情况。
while (_tables[index]._state != EMPTY)
{
if (_tables[index]._kv.first == key
&& _tables[index]._state == EXIST)
{
return &_tables[index];
}
index = hashi + i;
index %= _tables.size();
i++;
//查找了一圈还是没有找到,那就是没有这个元素
if (i == _tables.size())
{
break;
}
}
return nullptr;
}
bool Erase(const K& key)
{
Hash<K, V>* Eindex = Find(key);
if (Eindex)
{
//注意的是Eindex是一个Hash类型的指针,而不是下标
Eindex->_state = DELETE;
_size--;
}
else
{
return false;
}
return true;
}
#pragma once
#include
#include
using namespace std;
enum State
{
EMPTY,
EXIST,
DELETE
};
template<class K, class V>
struct Hash
{
pair<K, V> _kv;
State _state = EMPTY;
};
template<class K, class V>
class hashTable
{
public:
bool Insert(const pair<K, V>& val)
{
//负载因子超过0.7就扩容
if (_tables.size() == 0 || _size * 10 / _tables.size() >= 7
)
{
size_t newSize = _tables.size() == 0 ? 10 : _tables.size() * 2;
hashTable<K, V> newTables;
newTables._tables.resize(newSize);
//将原数组中的数组重新插入新数组
/*for (auto& data : _tables)
{
if (data._state == EXIST)
{
newTables.Insert(data._kv);
}
}*/
if (_tables.size())
{
int i = 0;
while (_tables[i]._state == EXIST)
{
newTables.Insert(_tables[i]._kv);
i++;
}
}
_tables.swap(newTables._tables);
}
//插入val
size_t hashi = val.first % _tables.size();
size_t j = 1;
size_t index = hashi;
while (_tables[index]._state == EXIST)
{
index = hashi + j;
index = index % _tables.size();
j++;
}
_tables[index]._kv = val;
_tables[index]._state = EXIST;
_size++;
return true;
}
Hash<K, V>* Find(const K& key)
{
if (_tables.size() == 0)
{
return nullptr;
}
size_t hashi = key % _tables.size();
size_t i = 1;
size_t index = hashi;
while (_tables[index]._state != EMPTY)
{
if (_tables[index]._kv.first == key
&& _tables[index]._state == EXIST)
{
return &_tables[index];
}
index = hashi + i;
index %= _tables.size();
i++;
if (i == _tables.size())
{
break;
}
}
return nullptr;
}
bool Erase(const K& key)
{
Hash<K, V>* Eindex = Find(key);
if (Eindex)
{
Eindex->_state = DELETE;
_size--;
}
else
{
return false;
}
return true;
}
private:
vector<Hash<K, V>> _tables;
int _size = 0;
};
- 线性探测优点:实现非常简单,
- 线性探测缺点:一旦发生哈希冲突,所有的冲突连在一起,容易产生数据“堆积”,即:不同
关键码占据了可利用的空位置,使得寻找某关键码的位置需要许多次比较,导致搜索效率降
低。
可以通过二次探测缓解。
线性探测的缺陷是产生冲突的数据堆积在一块,这与其找下一个空位置有关系,因为找空位置的方式就是挨着往后逐个去找,因此二次探测为了避免该问题.
二次探测是向后探测是一次向后移动两个单位,这能够缓解很多哈希冲突都发生在邻近的一片位置时效率低下的问题。
研究表明:当表的长度为质数且表装载因子a不超过0.5时,新的表项一定能够插入,而且任何一个位置都不会被探查两次。因此只要表中有一半的空位置,就不会存在表满的问题。在搜索时可以不考虑表装满的情况但在插入时必须确保表的装载因子a不超过0.5,如果超出必须考虑增容。
创作不易,你的点赞和关注都是对我莫大的鼓励,再次感谢您的观看