unordered系列的关联式容器之所以效率比较高,是因为底层使用了哈希结构。那么问题来了,什么是哈希呢?
顺序结构和平衡树中,元素关键码与其存储位置之间没有对应关系,因此在查找一个元素时,必须要经过关键码的多次比较。顺序查找时间复杂度为O(N),平衡树中为树的高度,即O(logN),搜索的效率取决于搜索过程中元素的比较次数。
理想的搜索方法:不经过任何比较,一次直接从表中得到要搜索的元素。如果构造一种存储结构,通过某种函数(HashFunc)使元素的存储位置与它的关键码之间能建立起一一对应的映射关系,那么在查找时通过该函数可以很快找到该元素。该方法即为哈希(散列)方法,哈希方法中使用的转换函数称为哈希(散列)函数,构造出来的结构称为哈希表(HashTable)(或者称为散列表)。
哈希函数设置为:hash(key) = key % size size为存储元素底层空间的总大小。
哈希冲突
按照以上方式进行搜索不必进行多次关键码的比较,因此搜索的速度比较快。但是问题来了,如果向集合中插入元素14,会出现什么问题呢?很显然,14%10等于4,那么4和14会在同一个位置,即不同的关键字通过相同哈希函数计算出相同的哈希,这种现象称为哈希冲突或哈希碰撞。
引起哈希冲突的一个原因是:哈希函数的设计不够合理。哈希函数的设计原则:
常见的哈希函数
1.直接定址法(常用)(小范围限定的局部问题)
取关键字的某个线性函数为散列地址:Hash(key)= A*key+B 优点:简单、均匀。缺点:需要事先知道关键字的分布情况。使用场景:适合查找比较小且连续的情况。
2.除留余数法(常用)(大范围的广泛问题)
设散列表中允许的地址数为m,取一个不大于m,但最接近或等于m的质数p作为除数,按照哈希函数:Hash(key) = key % p(p <=m),将关键码转换成哈希地址。
不常用的有平方取中法、折叠法、随机数法、数学分析法。(了解)
注:哈希函数设计的越精妙,产生哈希冲突的可能性就越低,但是无法避免哈希冲突。
解决哈希冲突
解决哈希冲突的两种方法:闭散列和开散列。
闭散列:也叫开放定址法,当发生哈希冲突时,如果哈希表未被装满,说明在哈希表中必然还有空位置,那就可以把key存放到冲突位置的下一个空位置中去。找下一个空位置的方法:
1.线性探测:从发生冲突的位置开始,依次向后探测,知道找到下一个空位置为止。
插入操作
通过哈希函数获取待插入元素在哈希表中的位置。如果该位置中没有元素,则直接插入新函数,如果该位置已经有元素则发生了哈希冲突,使用线性探测找到下一个空位置,插入新函数。
删除
采用闭散列处理哈希冲突,不能随便物理处理删除哈希表中已有的元素,若直接删除元素会影响其他元素的搜索。因此线性探测采用标记的伪删除法来删除元素。
用闭散列实现一个哈希结构
#include
namespace CLOSE
{
enum State
{
EMPTY,
EXIST,
DELETE,
};
template<class K,class V>
struct HashNode
{
std::pair<K, V> _kv;
State _state;
};
template<class K,class V>
struct HashTable
{
typedef HashNode<K, V> HashNode;
public:
HashTable(size_t N = 10)
{
_table.resize(N);
for (size_t i = 0; i < _table.size(); ++i)
{
_table[i]._state = EMPTY;
}
_size = 0;
}
bool Insert(const std::pair<K, V>& kv)
{
//先检查哈希表底层空间是否充足
CheckCapacity();
size_t index = kv.first % _table.size();
while (_table[index]._state == EXIST)
{
if (_table[index]._kv.first == kv.first)
{
return false;
}
//线性探测,解决哈希冲突
++index;
if (index == _table.size())
index = 0;
}
//插入元素
_table[index]._kv = kv;
_table[index]._state = EXIST;
++_size;
return true;
}
void CheckCapacity()
{
//什么时机增容,如何增容
//散列表的荷载因子定义为:a = 填入表中的元素个数/散列表的长度
//a是散列表装满程度的标志因子。由于表长是定值,a与填入表中的元素个数成正比,所以,a越大,表明填入表中的元素越多,产生冲突的可能性就越大;
//反之,a越小。标明填入表中的元素越少,产生冲突的可能性就越小。实际上,散列表的平均查找长度是荷载因子a的函数,只是不同处理冲突的方法有不同的函数。
//对于开放定址法,荷载因子是特别重要的因素,应该严格限制在0.7~0.8,超过0.8,查表时的CPU缓存不命中,按照指数曲线上升。
if (_table.size() == 0 || _size * 10 / _table.size() >= 7)
{
size_t newsize = _table.size() == 0 ? 10 : _table.size() * 2;
HashTable<K, V> _newht(newsize);
//将旧表中的数据重新在新表中算其所在的位置
for (size_t i = 0; i < _table.size(); ++i)
{
if (_table[i]._state == EXIST)
{
_newht.Insert(_table[i]._kv);
}
}
_table.swap(_newht._table);
}
}
HashNode* Find(const K& key)
{
size_t index = key % _table.size();
while (_table[index]._state != EMPTY)
{
if (_table[index]._kv.first == key && _table[index]._state == EXIST)
{
return &_table[index];
}
++index;
if (index == _table.size())
{
index = 0;
}
}
return nullptr;
}
bool Erase(const K& key)
{
HashNode* node = Find(key);
if (node == nullptr)
{
return false;
}
else
{
node->_state = DELETE;
--_size;
return true;
}
}
private:
std::vector<HashNode> _table;
size_t _size;//有效数据的个数
};
void TestHashTable()
{
HashTable<int, int> ht;
ht.Insert(std::make_pair(1, 1));
ht.Insert(std::make_pair(3, 3));
ht.Insert(std::make_pair(5, 5));
ht.Insert(std::make_pair(2, 2));
ht.Insert(std::make_pair(3, 3));
ht.Find(5);
ht.Erase(3);
}
}
线性探测优点:实现非常简单。缺点:一旦发生哈希冲突,所有的冲突可能连在一起,容易产生数据“堆积”,即:不同关键码占据了可利用的空位置,使得寻找某关键码的位置需要多次比较,导致效率降低。我们还可以通过二次探测来缓解哈希冲突。
2.二次探测
线性探测的缺陷是产生冲突的数据堆积到一块,这与其找下一个空位置有关系,因为找空位置的方式就是挨着往后逐个去找。二次探测的方法是:Hi = (H0 + i^2) % m,或Hi = (H0 - i^2) % m。其中i=1,2,3…,H0是通过散列函数Hash(x)对元素的关键码key进行计算得到的位置,m表示表的大小。
研究表明:当表的长度为质数且表装载因子a不超过0.5时,新的表项一定能够插入,而且任何一个位置都不会被探查两次。因此只要表中有一半的空位置,就不会存在表满的问题。在搜索时可以不考虑表装 满的情况,但在插入时必须确保表的装载因子a不超过0.5,如果超出必须考虑增容。,因此闭散列最大的缺陷就是空间利用率比较低。
开散列
开散列法又叫链地址法(开链法或哈希桶),首先对关键码用散列函数计算散列地址,具有相同地址的关键码归于同一个子集,每个子集合称为一个桶,各个桶中的元素通过单链表链接起来,各链表的头结点存储在哈希表中。
unordered_map和unordered_set底层封装的是哈希结构。以下代码封装了哈希表,并巧妙的运用了模板模拟实现了unordered_map和unordered_set.
1.用开散列法实现一个哈希表
template<class V>
struct HashNode
{
HashNode<V>* _next;
V _value;
HashNode(const V& v)
: _next(nullptr)
, _value(v)
{
}
};
template<class K,class V,class KeyOfValue,class HashFunc>
class HashTable
{
typedef HashNode<V> Node;
template<class K,class V,class KeyOfValue,class HashFunc>
friend struct HTIterator;
public:
typedef HTIterator<K, V, KeyOfValue, HashFunc> iterator;
HashTable()
: _size(0)
{
}
~HashTable()
{
Clear();
}
KeyOfValue kov;
iterator Begin()
{
for (size_t i = 0; i < _table.size(); i++)
{
if (_table[i] != nullptr)
{
return iterator(_table[i], this);//this代表哈希表的指针
}
}
return iterator(nullptr,this);
}
iterator End()
{
return iterator(nullptr, this);
}
pair<iterator, bool> Insert(const V& v)
{
CheckCapacity();
size_t index = HashIndex(kov(v), _table.size());
Node* cur = _table[index];
while (cur)
{
if (kov(cur->_value) == kov(v))
return make_pair(iterator(cur, this), false);
cur = cur->_next;
}
Node* newnode = new Node(v);
newnode->_next = _table[index];
_table[index] = newnode;
++_size;
return make_pair(iterator(newnode, this), true);
}
iterator Find(const K& key)
{
size_t index = HashIndex(key, _table.size());
Node* cur = _table[index];
while (cur)
{
if (kov(cur->_value) == key)
{
return iterator(cur,this);
}
cur = cur->_next;
}
return iterator(nullptr, this);
}
bool Erase(const K& key)
{
size_t index = HashIndex(key, _table.size());
Node* prev = nullptr;
Node* cur = _table[index];
while (cur)
{
if (kov(cur->_value) == key)
{
if (prev == nullptr)
_table[index] = cur->_next;
else
prev->_next = cur->_next;
--_size;
delete cur;
return true;
}
prev = cur;
cur = cur->_next;
}
return false;
}
size_t Size()const
{
return _size;
}
void Clear()
{
for (size_t i = 0; i < _table.size(); i++)
{
Node* cur = _table[i];
while (cur)
{
_table[i] = cur->_next;
delete cur;
cur = _table[i];
}
}
_size = 0;
}
bool Empty()const
{
return 0 == _size;
}
size_t Count(const K& key)
{
size_t index = HashIndex(key, _table.size());
Node* cur = _table[i];
while (cur)
{
if (kov(cur->_value) == key)
break;
cur = cur->_next;
}
size_t count = 0;
while (cur)
{
if (kov(cur->_value) == key)
{
count++;
cur = cur->_next;
}
else
break;
}
return count;
}
private:
//开散列增容的方法是,在元素个数刚好等于桶的个数时,可以给哈希表增容,每次以二倍的方式增容
void CheckCapacity()
{
if (_size == _table.size())
{
size_t newsize = _table.size() == 0 ? 10 : _table.size() * 2;
vector<Node*> _newtable;
_newtable.resize(newsize,nullptr);
for (size_t i = 0; i < _table.size(); i++)
{
Node* cur = _table[i];//将旧表结点插入到新表
while (cur)
{
Node* next = cur->_next;
size_t index = HashIndex(kov(cur->_value), _newtable.size());
//头插到新表
cur->_next = _newtable[index];
_newtable[index] = cur;
cur = next;
}
_table[i] = nullptr;
}
_table.swap(_newtable);
}
}
size_t HashIndex(const K& key, size_t size)
{
HashFunc hf;
return hf(key) % size;
}
private:
vector<Node*> _table;
size_t _size = 0;
};
这里需要解释一下模板中各个参数的意义:
K:关键码类型。 V:不同容器的V的类型不同,如果是unoredered_map,V代表一个键值对,如果是unordered_set,V为K
KeyOfValue:因为V的类型不同,通过value取key的方式也就不同。
HashFunc:哈希函数仿函数对象类型,哈希函数使用除留余数法,需要将key转换成整形数字才能取模。
2.实现一个哈希表的迭代器类
//为了实现简单,在哈希表的迭代器类中需要用到HashTable本身,所以采用前置声明
template<class K,class V,class KeyOfValue,class HashFunc>
class HashTable;
template<class K, class V, class KeyOfValue,class HashFunc>
struct HTIterator
{
typedef HashNode<V> Node;
typedef HTIterator<K, V, KeyOfValue, HashFunc> Self;
Node* _node;
HashTable<K, V, KeyOfValue, HashFunc>* _ht;
HTIterator(Node* node, HashTable<K, V, KeyOfValue, HashFunc>* ht)
: _node(node)
, _ht(ht)
{
}
//因为哈希表在底层是单链表结构,所以哈希表的迭代器不需要实现--操作
Self& operator++()
{
if (_node->_next)
{
_node = _node->_next;
}
else
{
KeyOfValue kov;
size_t index = _ht->HashIndex(kov(_node->_value), _ht->_table.size());
++index;
while (index < _ht->_table.size())
{
if (_ht->_table[index])
{
_node = _ht->_table[index];
break;
}
++index;
}
if (index == _ht->_table.size())
_node = nullptr;
}
return *this;
}
Self operator++(int)
{
Self temp(*this);
++(*this);
return temp;
}
V& operator*()
{
return _node->_value;
}
V* operator->()
{
return &_node->_value;
}
bool operator==(const Self& s)
{
return _node == s._node && _ht == s._ht;
}
bool operator!=(const Self& s)
{
return _node != s._node;
}
};
3.封装哈希表,简单实现unordered_map
unordered_map中存储的是pair
template<class K, class V,class HashFunc = HashFunc<K>>
class UnorderedMap
{
typedef pair<K, V> ValueType;
struct KeyOfValue
{
const K& operator()(const ValueType& kv)
{
return kv.first;
}
};
public:
typedef typename HashTable<K, ValueType, KeyOfValue, HashFunc>::iterator iterator;
UnorderedMap()
: _ht()
{
}
//iterator
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(make_pair(key, V()));
return ret.first->second;
}
iterator find(const K& key)
{
return _ht.Find(key);
}
size_t count(const K& key)
{
return _ht.Count();
}
//modify
pair<iterator, bool> insert(const ValueType& kv)
{
return _ht.Insert(kv);
}
private:
HashTable<K,ValueType, KeyOfValue,HashFunc> _ht;
};
void TestUnorderedMap()
{
UnorderedMap<int, int> m;
m.insert(make_pair(1, 1));
m.insert(make_pair(3, 3));
m.insert(make_pair(4, 4));
m.insert(make_pair(1, 1));
m.insert(make_pair(5, 5));
m.insert(make_pair(2, 2));
m.insert(make_pair(15, 15));
m.find(5);
UnorderedMap<int, int>::iterator it = m.begin();
while (it != m.end())
{
cout << it->first << " ";
++it;
}
cout << endl;
UnorderedMap<string, string> dict;
dict.insert(make_pair("string", "字符串"));
dict.insert(make_pair("left", "左边"));
dict["left"] = "剩余";
UnorderedMap<string, string>::iterator it2 = dict.begin();
while (it2 != dict.end())
{
cout << it2->first << " ";
++it2;
}
cout << endl;
}
4.封装哈希表,简单实现unordered_set
class UnorderSet
{
typedef K ValueType;
struct KeyOfValue
{
const K& operator()(const ValueType& v)
{
return v;
}
};
public:
typedef typename HashTable<K, ValueType, KeyOfValue, HashFunc>::iterator iterator;
pair<iterator, bool> insert(const ValueType& kv)
{
return _ht.Insert(kv);
}
iterator find(const K& key)
{
return _ht.Find(key);
}
size_t count(const K& key)
{
return _ht.Count();
}
iterator begin()
{
return _ht.Begin();
}
iterator end()
{
return _ht.End();
}
private:
HashTable<K, ValueType, KeyOfValue, HashFunc> _ht;
};
void TestUnorderedSet()
{
UnorderSet<int> s;
s.insert(2);
s.insert(6);
s.insert(4);
s.insert(14);
s.insert(16);
s.find(2);
UnorderSet<int>::iterator it = s.begin();
while (it != s.end())
{
cout << *it << " ";
++it;
}
cout << endl;
}
至此,我们就成功用封装了哈希表,实现了一个简单的unordered_map和unordered_set容器。
HashTable.h
#pragma once
#include
#include
#include
using namespace std;
template<class V>
struct HashNode
{
HashNode<V>* _next;
V _value;
HashNode(const V& v)
: _next(nullptr)
, _value(v)
{
}
};
template<class K,class V,class KeyOfValue,class HashFunc>
class HashTable;
template<class K, class V, class KeyOfValue,class HashFunc>
struct HTIterator
{
typedef HashNode<V> Node;
typedef HTIterator<K, V, KeyOfValue, HashFunc> Self;
Node* _node;
HashTable<K, V, KeyOfValue, HashFunc>* _ht;
HTIterator(Node* node, HashTable<K, V, KeyOfValue, HashFunc>* ht)
: _node(node)
, _ht(ht)
{
}
//因为哈希表在底层是单链表结构,所以哈希表的迭代器不需要实现--操作
Self& operator++()
{
if (_node->_next)
{
_node = _node->_next;
}
else
{
KeyOfValue kov;
size_t index = _ht->HashIndex(kov(_node->_value), _ht->_table.size());
++index;
while (index < _ht->_table.size())
{
if (_ht->_table[index])
{
_node = _ht->_table[index];
break;
}
++index;
}
if (index == _ht->_table.size())
_node = nullptr;
}
return *this;
}
Self operator++(int)
{
Self temp(*this);
++(*this);
return temp;
}
V& operator*()
{
return _node->_value;
}
V* operator->()
{
return &_node->_value;
}
bool operator==(const Self& s)
{
return _node == s._node && _ht == s._ht;
}
bool operator!=(const Self& s)
{
return _node != s._node;
}
};
template<class K,class V,class KeyOfValue,class HashFunc>
class HashTable
{
typedef HashNode<V> Node;
template<class K,class V,class KeyOfValue,class HashFunc>
friend struct HTIterator;
public:
typedef HTIterator<K, V, KeyOfValue, HashFunc> iterator;
HashTable()
: _size(0)
{
}
~HashTable()
{
Clear();
}
KeyOfValue kov;
iterator Begin()
{
for (size_t i = 0; i < _table.size(); i++)
{
if (_table[i] != nullptr)
{
return iterator(_table[i], this);//this代表哈希表的指针
}
}
return iterator(nullptr,this);
}
iterator End()
{
return iterator(nullptr, this);
}
pair<iterator, bool> Insert(const V& v)
{
CheckCapacity();
size_t index = HashIndex(kov(v), _table.size());
Node* cur = _table[index];
while (cur)
{
if (kov(cur->_value) == kov(v))
return make_pair(iterator(cur, this), false);
cur = cur->_next;
}
Node* newnode = new Node(v);
newnode->_next = _table[index];
_table[index] = newnode;
++_size;
return make_pair(iterator(newnode, this), true);
}
iterator Find(const K& key)
{
size_t index = HashIndex(key, _table.size());
Node* cur = _table[index];
while (cur)
{
if (kov(cur->_value) == key)
{
return iterator(cur,this);
}
cur = cur->_next;
}
return iterator(nullptr, this);
}
bool Erase(const K& key)
{
size_t index = HashIndex(key, _table.size());
Node* prev = nullptr;
Node* cur = _table[index];
while (cur)
{
if (kov(cur->_value) == key)
{
if (prev == nullptr)
_table[index] = cur->_next;
else
prev->_next = cur->_next;
--_size;
delete cur;
return true;
}
prev = cur;
cur = cur->_next;
}
return false;
}
size_t Size()const
{
return _size;
}
void Clear()
{
for (size_t i = 0; i < _table.size(); i++)
{
Node* cur = _table[i];
while (cur)
{
_table[i] = cur->_next;
delete cur;
cur = _table[i];
}
}
_size = 0;
}
bool Empty()const
{
return 0 == _size;
}
size_t Count(const K& key)
{
size_t index = HashIndex(key, _table.size());
Node* cur = _table[i];
while (cur)
{
if (kov(cur->_value) == key)
break;
cur = cur->_next;
}
size_t count = 0;
while (cur)
{
if (kov(cur->_value) == key)
{
count++;
cur = cur->_next;
}
else
break;
}
return count;
}
private:
void CheckCapacity()
{
if (_size == _table.size())
{
size_t newsize = _table.size() == 0 ? 10 : _table.size() * 2;
vector<Node*> _newtable;
_newtable.resize(newsize,nullptr);
for (size_t i = 0; i < _table.size(); i++)
{
Node* cur = _table[i];//将旧表结点插入到新表
while (cur)
{
Node* next = cur->_next;
size_t index = HashIndex(kov(cur->_value), _newtable.size());
//头插到新表
cur->_next = _newtable[index];
_newtable[index] = cur;
cur = next;
}
_table[i] = nullptr;
}
_table.swap(_newtable);
}
}
size_t HashIndex(const K& key, size_t size)
{
HashFunc hf;
return hf(key) % size;
}
private:
vector<Node*> _table;
size_t _size = 0;
};
common.h
#pragma once
template<class K>
struct HashFunc
{
const K& operator()(const K& key)
{
return key;
}
};
template<>
struct HashFunc<string>
{
size_t operator()(const std::string& s)
{
size_t hash = 0;
for (size_t i = 0; i < s.size(); i++)
{
hash = hash * 131 + s[i];
}
return hash;
}
};
unordered_map.h
#pragma once
#include"HashTable.h"
#include"common.h"
template<class K, class V,class HashFunc = HashFunc<K>>
class UnorderedMap
{
typedef pair<K, V> ValueType;
struct KeyOfValue
{
const K& operator()(const ValueType& kv)
{
return kv.first;
}
};
public:
typedef typename HashTable<K, ValueType, KeyOfValue, HashFunc>::iterator iterator;
UnorderedMap()
: _ht()
{
}
//iterator
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(make_pair(key, V()));
return ret.first->second;
}
iterator find(const K& key)
{
return _ht.Find(key);
}
size_t count(const K& key)
{
return _ht.Count();
}
//modify
pair<iterator, bool> insert(const ValueType& kv)
{
return _ht.Insert(kv);
}
private:
HashTable<K,ValueType, KeyOfValue,HashFunc> _ht;
};
void TestUnorderedMap()
{
UnorderedMap<int, int> m;
m.insert(make_pair(1, 1));
m.insert(make_pair(3, 3));
m.insert(make_pair(4, 4));
m.insert(make_pair(1, 1));
m.insert(make_pair(5, 5));
m.insert(make_pair(2, 2));
m.insert(make_pair(15, 15));
m.find(5);
UnorderedMap<int, int>::iterator it = m.begin();
while (it != m.end())
{
cout << it->first << " ";
++it;
}
cout << endl;
UnorderedMap<string, string> dict;
dict.insert(make_pair("string", "字符串"));
dict.insert(make_pair("left", "左边"));
dict["left"] = "剩余";
UnorderedMap<string, string>::iterator it2 = dict.begin();
while (it2 != dict.end())
{
cout << it2->first << " ";
++it2;
}
cout << endl;
}
unordered_set.h
#pragma once
#include"HashTable.h"
#include"common.h"
template<class K,class HashFunc = HashFunc<K>>
class UnorderSet
{
typedef K ValueType;
struct KeyOfValue
{
const K& operator()(const ValueType& v)
{
return v;
}
};
public:
typedef typename HashTable<K, ValueType, KeyOfValue, HashFunc>::iterator iterator;
pair<iterator, bool> insert(const ValueType& kv)
{
return _ht.Insert(kv);
}
iterator find(const K& key)
{
return _ht.Find(key);
}
size_t count(const K& key)
{
return _ht.Count();
}
iterator begin()
{
return _ht.Begin();
}
iterator end()
{
return _ht.End();
}
private:
HashTable<K, ValueType, KeyOfValue, HashFunc> _ht;
};
void TestUnorderedSet()
{
UnorderSet<int> s;
s.insert(2);
s.insert(6);
s.insert(4);
s.insert(14);
s.insert(16);
s.find(2);
UnorderSet<int>::iterator it = s.begin();
while (it != s.end())
{
cout << *it << " ";
++it;
}
cout << endl;
}
main.h
#include"unordered_map.h"
#include"unordered_set.h"
int main()
{
//TestUnorderedMap();
TestUnorderedSet();
return 0;
}