1.哈希
顺序结构以及平衡树中,元素关键码与其存储位置之间没有对应的关系,因此在查找一个元素 时,必须要经过关键码的多次比较。顺序查找时间复杂度为O(N),平衡树中为树的高度,即 O($log_2 N$),搜索的效率取决于搜索过程中元素的比较次数。
理想的搜索方法:可以不经过任何比较,一次直接从表中得到要搜索的元素。 如果构造一种存储结构,通过某种函数(hashFunc)使元素的存储位置与它的关键码之间能够建立 一一映射的关系,那么在查找时通过该函数可以很快找到该元素。
当向该结构中: 插入元素: 根据待插入元素的关键码,以此函数计算出该元素的存储位置并按此 位置进行存放。
搜索元素: 对元素的关键码进行同样的计算,把求得的函数值当做元素的存储位 置,在结构中按此位置 取元素比较,若关键码相等,则搜索成功。
该方式即为哈希(散列)方法,哈希方法中使用的转换函数称为哈希(散列)函数,构造出来的结构称 为哈希表(Hash Table)(或者称散列表)。
1.2哈希冲突
不同关键字通过相同哈希哈数计算出相同的哈希地址,该种现象称为哈希冲突或哈希碰撞。
1.3哈希函数
引起哈希冲突的一个原因可能是:哈希函数设计不够合理。
哈希函数设计原则: 哈希函数的定义域必须包括需要存储的全部关键码,而如果散列表允许有m个地址时,其值域必须在0到m-1之间;哈希函数计算出来的地址能均匀分布在整个空间中;哈希函数应该比较简单。
常见哈希函数 1. 直接定址法--(常用) :取关键字的某个线性函数为散列地址:Hash(Key)= A*Key + B 优点:简单、均匀 缺点:需要事先知道关键字的分布情况使用场景:适合查找比较小且连续的情况。
2. 除留余数法--(常用) :设散列表中允许的地址数为m,取一个不大于m,但最接近或者等于m的质数p作为除数, 按照哈希函数:Hash(key) = key% p(p<=m),将关键码转换成哈希地址
3. 平方取中法--(了解) :假设关键字为1234,对它平方就是1522756,抽取中间的3位227作为哈希地址; 再比如关键字为4321,对它平方就是18671041,抽取中间的3位671(或710)作为哈希地址 平方取中法比较适合:不知道关键字的分布,而位数又不是很大的情况
4. 折叠法--(了解) :折叠法是将关键字从左到右分割成位数相等的几部分(最后一部分位数可以短些),然后将这 几部分叠加求和,并按散列表表长,取后几位作为散列地址。 折叠法适合事先不需要知道关键字的分布,适合关键字位数比较多的情况
5. 随机数法--(了解) 选择一个随机函数,取关键字的随机函数值为它的哈希地址,即H(key) = random(key),其中 random为随机数函数。 通常应用于关键字长度不等时采用此法
6. 数学分析法--(了解): 设有n个d位数,每一位可能有r种不同的符号,这r种不同的符号在各位上出现的频率不一定 相同,可能在某些位上分布比较均匀,每种符号出现的机会均等,在某些位上分布不均匀只 有某几种符号经常出现。可根据散列表的大小,选择其中各种符号分布均匀的若干位作为散 列地址。
哈希函数设计的越精妙,产生哈希冲突的可能性就越低,但是无法避免哈希冲突。
1.4哈希冲突解决
解决哈希冲突两种常见的方法是:闭散列和开散列。
1.4.1闭散列
闭散列:也叫开放定址法,当发生哈希冲突时,如果哈希表未被装满,说明在哈希表中必然还有 空位置,那么可以把key存放到冲突位置中的“下一个” 空位置中去。
查找空位置:
线性探测:从发生冲突的位置开始,依次向后探测,直到寻找到下一个空位置为止。但发生哈希冲突,所有的冲突连在一起,容易产生数据“堆积”,即:不同关键码占据了可利用的空位置,使得寻找某关键码的位置需要许多次比较,导致搜索效率降低。
插入:通过哈希函数获取待插入元素在哈希表中的位置 ;如果该位置中没有元素则直接插入新元素,如果该位置中有元素发生哈希冲突, 使用线性探测找到下一个空位置,插入新元素。
删除 :采用闭散列处理哈希冲突时,不能随便物理删除哈希表中已有的元素,若直接删除元素 会影响其他元素的搜索。比如删除元素4,如果直接删除掉,44查找起来可能会受影响。因此线性探测采用标记的伪删除法来删除一个元素。
二次探测:线性探测的缺陷是产生冲突的数据堆积在一块,这与其找下一个空位置有关系,因为找空位 置的方式就是挨着往后逐个去找,因此二次探测为了避免该问题,找下一个空位置的方法 为:hashi = ($H_0$ + i^2 )% m, 或者:hashi = ($H_0$ - i^2 )% m。其中:i = 1,2,3…, $H_0$是通过散列函数Hash(x)对元素的关键码 key 进行计算得到的位置,m是表的大小。
研究表明:当表的长度为质数且表装载因子a不超过0.5时,新的表项一定能够插入,而且任何一个位置都不会被探查两次。因此只要表中有一半的空位置,就不会存在表满的问题。在搜索时可以不考虑表装满的情况,但在插入时必须确保表的装载因子a不超过0.5,如果超出必须考虑增容。
1.4.2开散列
开散列法又叫链地址法(开链法),首先对关键码集合用散列函数计算散列地址,具有相同地址的关键码归于同一子集合,每一个子集合称为一个桶,各个桶中的元素通过一个单链表链 接起来,各链表的头结点存储在哈希表中。
开散列增容 : 桶的个数是一定的,随着元素的不断插入,每个桶中元素的个数不断增多,极端情况下,可 能会导致一个桶中链表节点非常多,会影响的哈希表的性能,因此在一定条件下需要对哈希表进行增容。开散列最好的情况是:每个哈希桶中刚好挂一个节点, 再继续插入元素时,每一次都会发生哈希冲突,因此,在元素个数刚好等于桶的个数时,可以给哈希表增容。
2.unordered_map和unordered_set模拟实现
这里注意:因为不知道key的类型所以模板和仿函数HashFunc转换成整数。
hashtable.h:
#pragma once
#include<iostream>
#include<vector>
#include<string>
using namespace std;
template<class K>
struct HashFunc
{
size_t operator()(const K& key)
{
return (size_t)key;
}
};
template<>
struct HashFunc<string>
{
size_t operator()(const string& key)
{
// BKDR
size_t hash = 0;
for (auto e : key)
{
hash *= 31;
hash += e;
}
//cout << key << ":" << hash << endl;
return hash;
}
};
namespace bit
{
template<class T>
struct HashBucketNode
{
HashBucketNode<T>* next;
T data;
HashBucketNode(const T& _data)
:next(nullptr),
data(_data)
{}
};
// 为了实现简单,在哈希桶的迭代器类中需要用到hashBucket本身,
template<class K, class T, class KeyOfT, class Hash=HashFunc<K>>
class HashBucket;
//哈希桶的迭代器
// 注意:因为哈希桶在底层是单链表结构,所以哈希桶的迭代器不需要--操作
template <class K, class T, class KeyOfT, class Hash = HashFunc<K>>
struct HBIterator
{
typedef HashBucket<K, T, KeyOfT, Hash> HashBucket;
typedef HashBucketNode<T>* PNode;
typedef HashBucketNode<T> Node;
typedef HBIterator<K, T, KeyOfT, Hash> Self;
HBIterator(PNode pNode = nullptr, HashBucket* pHt = nullptr)
:_pNode(pNode)
, _pHt(pHt)
{}
Self& operator++()
{
// 当前迭代器所指节点后还有节点时直接取其下一个节点
if (_pNode->next)
_pNode = _pNode->next;
else
{
KeyOfT koft;
Hash hf;
size_t i = hf(koft(_pNode->data)) % _pHt->ht.size();
++i;
for (; i < _pHt->ht.size(); ++i)
{
Node* cur = _pHt->ht[i];
if (cur)
{
_pNode = cur;
return *this;
}
}
_pNode = nullptr;
return *this;
}
}
T& operator*()
{
return _pNode->data;
}
T* operator->()
{
return &_pNode->data;
}
bool operator==(const Self& it) const
{
return it._pNode == _pNode;
}
bool operator!=(const Self& it) const
{
return it._pNode != _pNode;
}
PNode _pNode; // 当前迭代器关联的节点
HashBucket* _pHt; // 哈希桶--主要是为了找下一个空桶时候方便
};
//哈希表
template<class K, class T, class KeyOfT, class Hash>
class HashBucket
{
typedef struct HashBucketNode<T> Node;
template <class K, class T, class KeyOfT, class Hash>
friend struct HBIterator;
public:
typedef HBIterator<K, T, KeyOfT, Hash> iterator;
typedef HBIterator<K, T, KeyOfT, Hash> const_iterator;
iterator end()
{
return iterator(nullptr, this);
}
iterator begin()
{
for (size_t i = 0; i < ht.size(); i++)
{
if (ht[i] != nullptr)
{
return iterator(ht[i], this);
}
}
return end();
}
const_iterator end() const
{
return const_iterator(nullptr, this);
}
const_iterator begin() const
{
for (size_t i = 0; i < ht.size(); i++)
{
if (ht[i] != nullptr)
{
return const_iterator(ht[i], this);
}
}
return end();
}
HashBucket(size_t capacity = 10)
:ht(capacity)
, size(0)
{}
~HashBucket()
{
for (int i = 0; i < ht.size(); i++)
{
Node* cur = ht[i];
while (cur)
{
Node* next = cur->next;
delete cur;
cur = next;
}
ht[i] = nullptr;
}
}
pair<iterator, bool> Insert(const T& data)
{
Hash hf;
KeyOfT kf;
CheckCapacity();
size_t hashi = hf(kf(data)) % ht.size();
Node* cur = ht[hashi];
while (cur)
{
if (kf(cur->data) == kf(data))
{
return make_pair(iterator(cur, this), false);
}
cur = cur->next;
}
Node* newnode = new Node(data);
newnode->next = ht[hashi];
ht[hashi] = newnode;
++size;
return make_pair(iterator(newnode, this), true);
}
iterator Find(const K&key)
{
Hash hf;
KeyOfT kf;
size_t hashi = hf(key) % ht.size();
Node* cur = ht[hashi];
while (cur)
{
if (kf(cur->data) == key)
{
return iterator(cur, this);
}
cur = cur->next;
}
return end();
}
iterator Erase(const iterator data)
{
if (data == end()) return end();
Hash hf;
KeyOfT kf;
size_t hashi = hf(kf(data._pNode->data)) % ht.size();
Node* prev = nullptr;
Node* cur = ht[hashi];
while (cur)
{
if (kf(cur->data) == kf(data._pNode->data))
{
if (prev == nullptr)
{
ht[hashi] = cur->next;
}
else
{
prev->next = cur->next;
}
Node* next = cur->next;
delete cur;
--size;
return iterator(next, this);
}
prev = cur;
cur = cur->next;
}
return end();
}
bool empty() const
{
return size == 0;
}
size_t count(const K& key)
{
if (Find(key) != end())
{
return 1;
}
return 0;
}
size_t BucketCount()const { return ht.capacity(); }
private:
size_t HashFunc(const T& key)
{
return key % ht.capacity();
}
//检查是否需要扩容
void CheckCapacity()
{
if (size == ht.size())
{
HashBucket<K, T, KeyOfT> newht(ht.size() * 2);
for (size_t i = 0; i < ht.size(); i++)
{
Node* cur = ht[i];
//直接插入
while (cur)
{
newht.Insert(cur->data);
cur = cur->next;
}
}
newht.ht.swap(ht);//交换两表
}
}
size_t bucketSize(size_t bucketno)
{
Node* cur=ht[bucketno];
int count = 0;
for (;cur; cur = cur->next)
{
count++;
}
return count;
}
private:
vector<Node*> ht;
size_t size;// 哈希表中的所有元素
};
}
unordered_set.h:
#pragma once
#include"HashTable.h"
namespace myset
{
// unordered_set中存储的是K类型,HF哈希函数类型
// unordered_set在实现时,只需将hashbucket中的接口重新封装即可
template<class K, class Hash= HashFunc<K>>
class unordered_set
{
// 通过key获取value的操作
struct setKeyOfValue
{
const K& operator()(const K& key)
{
return key;
}
};
public:
typename typedef bit::HashBucket<K,K,setKeyOfValue,Hash>::iterator iterator;
typedef bit::HashBucket<K, K, setKeyOfValue, Hash> HT;
public:
unordered_set() : ht()
{}
iterator begin() { return ht.begin(); }
iterator end() { return ht.end(); }
// capacity
size_t size()const { return ht.size(); }
bool empty()const { return ht.empty(); }
///
// lookup
iterator find(const K& key) { return ht.Find(key); }
size_t count(const K& key) { return ht.Count(key); }
/
// modify
pair<iterator, bool> insert(const K& valye)
{
return ht.Insert(valye);
}
iterator erase(iterator position)
{
return ht.Erase(position);
}
// bucket
size_t bucket_count() { return ht.BucketCount(); }
size_t bucket_size(const K& key) { return ht.BucketSize(key); }
private:
HT ht;
};
}
unordered_map.h:
#pragma once
#include"HashTable.h"
namespace mymap
{
template<class K, class V, class HF = HashFunc<K>>
class unordered_map
{
// 通过key获取value的操作
struct mapKeyOfT
{
const K& operator()(const pair<K, V>& data)
{
return data.first;
}
};
//typedef bit::HashBucket<K, pair<K, V>, mapKeyOfT, HF> HT;
public:
typedef typename bit::HashBucket<K, pair<K, V>, mapKeyOfT, HF>::iterator iterator;
typedef typename bit::HashBucket<K, pair<K, V>, mapKeyOfT, HF>::const_iterator const_iterator;
public:
//
iterator begin() { return _ht.begin(); }
iterator end() { return _ht.end(); }
// //
// capacity
size_t size()const { return _ht.size; }
bool empty()const { return _ht.empty(); }
// /
// Acess
V& operator[](const K& key)
{
pair<iterator, bool> ret = _ht.Insert(pair<K, V>(key, V()));
iterator tmp = ret.first;
return (*tmp).second;
}
const V& operator[](const K& key)const
{
pair<const_iterator, bool> ret = _ht.Insert(pair<K, V>(key, V()));
return ret.fisrt->second;
}
// //
// // lookup
iterator find(const K& key) { return _ht.Find(key); }
size_t count(const K& key) { return _ht.Count(key); }
// /
// // modify
pair<iterator, bool> Insert(const pair<K, V>& value)
{
return _ht.Insert(value);
}
iterator erase(iterator position)
{
return _ht.Erase(position);
}
// //
// bucket
size_t bucket_count() { return _ht.BucketCount(); }
size_t bucket_size(const K& key) { return _ht.BucketSize(key); }
private:
bit::HashBucket<K, pair<K, V>, mapKeyOfT, HF> _ht;
};
}