目录
前言
unordered系列关联式容器之所以处理数据的效率比较高,是因为底层使用了哈希结构,哈希结构的优点是:不经过任何比较,一次直接从表中得到要搜索的元素,通过某种函数(hashFunc)使元素的存储位置与它的关键码之间能够建立一一映射的关系,那么在查找时通过该函数可以很快找到该元素。
一、哈希的概念
顺序结构以及平衡树
中,元素关键码与其存储位置之间没有对应的关系,因此在
查找一个元素
时,必须要经过关键码的多次比较
。
搜索的效率取决于搜索过程中元素的比较次数。
理想的搜索方法:
可以
不经过任何比较,一次直接从表中得到要搜索的元素
。
如果构造一种存储结构,通过某种函数
(hashFunc)
使元素的存储位置与它的关键码之间能够建立
一一映射的关系,那么在查找时通过该函数可以很快找到该元素
。
向结构中插入元素:根据待插入元素的关键码,用hashFunc函数(通常是用除留余数法)算出该元素的存储位置并按此位置进行存放。
从结构中搜索元素:对元素的关键码进行同样的计算,把求得的函数值当作元素的存储位置,在结构中按此位置取元素比较,若关键码相等,责搜索成功。
该方法即为哈希(散列)方法,哈希方法中使用的转换函数称为哈希(散列)函数,构造出来的结构称
为哈希表(HashTable)或者叫作散列表。
例如:数据集合{1,7,6,4,5,9};
哈希函数设置为:
hash(key) = key % capacity
;(capacity为存储元素底层空间总的大小。)
用该方法进行搜索不必进行多次关键码的比较,因此搜索的速度比较快。
但是如果按照上述哈希方
式,向集合中插入元素55,就会发生哈希冲突。
二、哈希冲突以及解决冲突的方法(闭散列线性探测)
不同关键码通过相同哈希函数计算出相同的哈希地址,该种现象称为哈希冲突或哈希碰撞。
通常把具有不同关键码而具有相同哈希地址的数据元素称为
“
同义词
”
。
那么该如何解决这种冲突呢?
闭散列:也叫开放定址法,当发生哈希冲突时,如果哈希表未被装满,说明在哈希表中必然还有
空位置,那么可以把
key
存放到冲突位置中的
“
下一个
”
空位置中去。(“下一个”不一定只是隔了一个位置,因为闭散列中有两种方法:线性探测和二次探测。线性探测则是下一个,二次探测则不一定是下一个位置)。
下面我们介绍一下解决哈希冲突的一个方法闭散列线性探测。
线性探测:从发生冲突的位置开始,依次向后探测,直到寻找到下一个空位置为止
。
插入数据:
通过哈希函数获取待插入元素在哈希表中的位置。
如果该位置中没有元素则直接插入新元素,如果该位置中有元素发生哈希冲突,
使用线性探测找到
下一个空位置,插入新元素 。
删除元素:
采用闭散列处理哈希冲突时,不能随便物理删除哈希表中已有的元素,若直接删除元素
会影响其他元素的搜索
。比如删除元素5
,如果直接删除掉,
55查找起来可能会受影
响。因此
线性探测采用标记的伪删除法来删除一个元素
。
三、哈希表的模拟实现(哈希冲突的解决方法采用闭散列线性探测)
#include<vector>
#include<iostream>
using namespace std;
namespace keke
{
enum Status
{
EMPTY,
EXIST,
DELETE
};
template<class K,class V>
struct HashData
{
pair<K, V> _kv;
Status _s;
};
//仿函数
/*template<class K>
class HashFunc
{
public:
size_t operator()(const K& key)
{
size_t i = 0;
for (auto e : key)
{
i += e;
}
return i % _tables.size();
}
};*/
template<class K, class V>
class HashTable
{
public:
HashTable()
{
_tables.resize(10);
}
bool Insert(const pair<K, V>& kv)
{
//负载因子:已经存储数据个数/_tables大小
if (Find(kv.first))
return false;
//扩容
if ((double)_n / _tables.size() >= 0.7)
{
size_t newSize = _tables.size() * 2;
HashTable<K, V> newHashTable;//定义一个新的哈希表,为什么不将原来的哈希表扩容呢?这是
//因为如果扩容的话就会影响查找数据,比如:当原哈希表的空间大小为5在下标为3的位置插入关键码3时再插入13
//就要根据线性探测的方法将关键码13存入到关键码3的下一个位置处,但是如果原地2倍扩容后,在进行Find时关键码3正常
//但是当Find关键码13时就会出现问题
newHashTable._tables.resize(newSize);
for (size_t i = 0;i < _tables.size();++i)//遍历旧的哈希表
{
if (EXIST == _tables[i]._s)
{
newHashTable.Insert(_tables[i]._kv.first);
}
}
//将临时创建的扩容后的哈希表里的_tables与旧哈希表里的_tables互换
_tables.swap(newHashTable._tables);
}
size_t hashi = kv.first % _tables.size();
while (_tables[hashi]._s != EXIST)
{
hashi++;
hashi %= _tables.size();//当hashi的值等于_tables.size()的值则将hashi置为0
}
_tables[hashi]._kv = kv;
_tables[hashi]._s = EXIST;
++_n;
return true;
}
HashTable<K, V>* Find(const K& key)
{
size_t hashi = key % _tables.size();
while (_tables[hashi]._s != EMPTY)
{
if (_tables[hashi]._s == EXIST && _tables[hashi]._kv.first == key)
{
return &_tables[hashi];
}
hashi++;
hashi %= _tables.size();
}
return nullptr;
}
bool Erase(const K& key)
{
HashData<K, V>* ret = Find(key);
if (ret)
{
ret->_s = DELETE;
--_n;
return true;
}
return false;
}
void Print()
{
for (size_t i = 0;i < _tables.size();++i)
{
if (EXIST == _tables[i]._s)
printf("[&d]->%d\n", i, _tables[i]._kv.first);
else if (EMPTY == _tables[i]._s)
printf("[&d]->E\n", i);
else
printf("[&d]->D\n", i);
}
}
private:
vector<HashData<K, V>> _tables;
size_t _n = 0;
};
}
//范围小的向范围大的提升,有符号的向无符号的的提升