目录
前言
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);elseprintf("[&d]->D\n", i);}}private:vector<HashData<K, V>> _tables;size_t _n = 0;};
}
//范围小的向范围大的提升,有符号的向无符号的的提升