一、unordered系列容器的认识
在C++98中,STL提供了底层为红黑树的一系列关联式容器,查询效率为log2N,即便在最差情况下也仅需要比较红黑树的高度次,所以当树中的节点非常多时,查询效率也不是很理想。因此在C++11中,STL又提供了4个unordered系列的关联式容器,这四个容器与红黑树结构的关联式容器使用方式基本类似,只是其底层结构改成了哈希表。
1.1unordered_map
1. unordered_map是存储<key, value>键值对的关联式容器,允许通过key快速的索引到与其对应的value。
2. 在unordered_map中,键值通常用于唯一地标识元素,而映射值是一个对象,其内容与此键关联。键和映射值的类型可能不同。
3. 在内部,unordered_map没有对<key, value>按照任何特定的顺序排序, 为了能在常数范围内找到key所对应的value,unordered_map将相同哈希值的键值对放在相同的哈希桶中。
4. unordered_map容器通过key访问单个元素要比map快,但在遍历元素子集的范围迭代方面效率较低。
5. unordered_map重载了操作符[ ],它允许使用key作为参数直接访问value。
6. 它的迭代器至少是前向迭代器。
使用细节和map基本一致
1.2unordered_set
1.3unordered_set和set的性能对比
void testop() //测试 底层是红黑树和哈希表的效率比对
{
const size_t N = 1000000;
unordered_set<int> us;
set<int> s;
vector<int> v;
v.reserve(N);
srand((unsigned int)time(0));
for (size_t i = 0; i < N; ++i)
{
v.push_back(rand());
//v.push_back(rand()+i);
//v.push_back(i);
}
size_t begin1 = clock();
for (auto e : v)
{
s.insert(e);
}
size_t end1 = clock();
cout << "set insert:" << end1 - begin1 << endl;
size_t begin2 = clock();
for (auto e : v)
{
us.insert(e);
}
size_t end2 = clock();
cout << "unordered_set insert:" << end2 - begin2 << endl;
size_t begin3 = clock();
for (auto e : v)
{
s.find(e);
}
size_t end3 = clock();
cout << "set find:" << end3 - begin3 << endl;
size_t begin4 = clock();
for (auto e : v)
{
us.find(e);
}
size_t end4 = clock();
cout << "unordered_set find:" << end4 - begin4 << endl << endl;
cout << s.size() << endl;
cout << us.size() << endl << endl;;
size_t begin5 = clock();
for (auto e : v)
{
s.erase(e);
}
size_t end5 = clock();
cout << "set erase:" << end5 - begin5 << endl;
size_t begin6 = clock();
for (auto e : v)
{
us.erase(e);
}
size_t end6 = clock();
cout << "unordered_set erase:" << end6 - begin6 << endl << endl;
}
随机加大量重复:
随机加少量重复:
有序:
从这三组数据可以看出整体而言unordered的效率更高。
二、哈希表
unordered系列的关联式容器整体效率比较高的因为是其底层使用了哈希结构。
2.1 哈希表的概念
哈希表,又称为散列表,是一种根据键来直接访问内存位置的一种数据结构。它通过一个计算键值的函数(散列函数)来将所查询的数据映射到哈希表中的一个位置来查找该位置的内容,从而达到快速查找的目的。(存放记录的数组就称为哈希表)。
2.2哈希函数(散列函数)
若关键字为k,则其值就存放在f(k)对应的位置上,这样可以实现不用比较就可以直接找到要求的记录,其中f(k)就是哈希函数。若对于关键字集合中的任意一个关键字,经哈希函数映射到地址集合中的任意一个地址的概率是相等的则称此类哈希函数为:均匀哈希函数
常见哈希函数:
(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种不同的符号在各位上出现的频率不一定
相同,可能在某些位上分布比较均匀,每种符号出现的机会均等,在某些位上分布不均匀只有某几种符号经常出现。可根据散列表的大小,选择其中各种符号分布均匀的若干位作为散列地址。
2.3哈希冲突(哈希碰撞)
对于不同的关键字可能得到相同的散列地址,这就是冲突。
2.4开放定址法实现哈希表
2.4.1基本结构
enum State//设置哈希地址状态
{
EMPTY,//空
EXIST,//存在
DELETE //删除
};
template<class K, class V>
struct HashData
{
pair<K, V> _kv;
State _state = EMPTY;
};
template<class K, class V, class Hash = HashFunc<K>>
class HashTable
{
private:
vector<HashData<K, V>> _tables;
size_t _n = 0; //记录有效的数据个数
};
2.4.2Insert
bool Insert(const pair<K, V>& kv)
{
if (Find(kv.first))//若存在即插入失败
return false;
if (_n >= _tables.size()*0.8)//扩容保证哈希表不会满
{
HashTable<K, V, Hash> hsls;
hsls._tables.resize(_tables.size()*2);
for (int i = 0; i < _tables.size(); i++)
{
if (_tables[i]._state == EXIST)
{
hsls.Insert(_tables[i]._kv);
}
}
swap(hsls._tables, _tables);
}
Hash hs;//哈希函数
size_t hashi = hs(kv.first);
hashi %= _tables.size();
while (_tables[hashi]._state == EXIST)
{
hashi++;
hashi %= _tables.size();//防止越界
}
_tables[hashi]._kv = kv;
_tables[hashi]._state = EXIST;
++_n;
return true;
}
2.4.3find
HashData<K, V>* Find(const K& key)
{
if (_tables.size() == 0) return nullptr;
Hash hs;
size_t hashi = hs(key) % _tables.size();
size_t index = hashi;
while (_tables[hashi]._state != EMPTY)
{
if (_tables[hashi]._state == EXIST
&& _tables[hashi]._kv.first == key)
{
return &_tables[hashi];
}
hashi++;
hashi %= _tables.size();
if(hashi==index)break;//极端情况
}
return nullptr;
}
不等于空就继续向后线性探测,因为设置delete状态就是想实现伪删除,这样在删除某些元素的时候不会影响线性探测。极端情况:如果所有的位置都是存在+删除,那么循环永远不会停止,这个时候我们就要去判断如果正好走了一圈了,就可以break了。
2.4.4erase
bool Erase(const K& key)
{
HashData<K, V>* HT = Find(key);//复用find
if (HT == nullptr)
return false;
else
HT->_state = DELETE;//伪删除
return true;
}
2.4.5整体代码
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)
{
size_t hash = 0;
for (auto e : key)
{
hash *= 31;
hash += e;
}
return hash;
}
};
namespace open_address
{
enum State
{
EXIST,
EMPTY,
DELETE
};
template<class K, class V>
struct HashData
{
pair<K, V> _kv;
State _state = EMPTY;
};
template<class K, class V, class Hash = HashFunc<K>>
class HashTable
{
public:
HashTable()
{
_tables.resize(10);
}
bool Insert(const pair<K, V>& kv)
{
if (Find(kv.first))
return false;
Hash hs;
size_t hashi = hs(kv.first);
hashi %= _tables.size();
while (_tables[hashi]._state == EXIST)
{
hashi++;
hashi %= _tables.size();
}
_tables[hashi]._kv = kv;
_tables[hashi]._state = EXIST;
_n++;
if (_n >= _tables.size()*0.8)
{
HashTable<K, V,Hash> hsls;
hsls._tables.resize(_tables.size()*2);
for (int i = 0; i < _tables.size(); i++)
{
if (_tables[i]._state == EXIST)
{
hsls.Insert(_tables[i]._kv);
}
}
swap(hsls._tables, _tables);
}
return true;
}
HashData<K, V>* Find(const K& key)
{
Hash hs;
size_t hashi = hs(key) % _tables.size();
while (_tables[hashi]._state != EMPTY)
{
if (_tables[hashi]._state == EXIST
&& _tables[hashi]._kv.first == key)
{
return &_tables[hashi];
}
hashi++;
hashi %= _tables.size();
}
return nullptr;
}
bool Erase(const K& key)
{
HashData<K, V>* HT = Find(key);
if (HT == nullptr)
return false;
else
HT->_state = DELETE;
return true;
}
private:
vector<HashData<K, V>> _tables;
size_t _n = 0; // 表中存储数据个数
};
}
三、unordered系列容器的封装
3.1拉链法哈希表
template<class T>
struct HashNode
{
T _data;
HashNode<T>* _next;
HashNode(const T& data)
:_data(data)
, _next(nullptr)
{}
};
template<class K, class T, class KeyOfT, class Hash>//前置声明
class HashTable;
// KeyOfT: 从T中提取key
template<class K,class T,class Ptr, class Ref ,class KeyOfT,class Hash>
struct Iterator
{
typedef HashNode<T> Node;
typedef Iterator<K, T, Ptr, Ref, KeyOfT, Hash> Self;
Ref operator*()
{
return _node->_data;
}
Ptr operator->()
{
return &(_node->_data);
}
bool operator!=(const Self& s)
{
return _node != s._node;
}
Self& operator++()
{
if (_node->_next)
{
_node = _node->_next;
}
else
{
Hash hs;
KeyOfT kot;
size_t hashi = hs(kot(_node->_data))%(_t->_tables.size());
hashi++;
while (hashi < _t->_tables.size())
{
if (_t->_tables[hashi])
{
_node = _t->_tables[hashi];
break;
}
hashi++;
}
if (hashi == _t->_tables.size())
{
_node = nullptr;
}
}
return *this;
}
Iterator(Node* node, const HashTable<K, T, KeyOfT, Hash>* t)
:_node(node),
_t(t)
{}
Node* _node;
const HashTable<K, T, KeyOfT, Hash>* _t;
};
template<class K, class T, class KeyOfT, class Hash>
class HashTable
{
template<class K, class T, class Ptr, class Ref, class KeyOfT, class Hash>
friend struct Iterator;
typedef HashNode<T> Node;
public:
typedef Iterator<K, T, T*, T& , KeyOfT , Hash> HTIterator;
typedef Iterator<K, T, const T*, const T&, KeyOfT, Hash> Const_HTIterator;
HTIterator Begin()
{
if (_n == 0)
return End();
for (int i = 0; i < _tables.size(); i++)
{
Node* cur = _tables[i];
if (cur)
{
return HTIterator(cur, this);
}
}
return End();
}
HTIterator End()
{
return HTIterator(nullptr, this);
}
HashTable()
{
_tables.resize(10, nullptr);
}
// 哈希桶的销毁
~HashTable()
{
// 依次把每个桶释放
for (size_t i = 0; i < _tables.size(); i++)
{
Node* cur = _tables[i];
while (cur)
{
Node* next = cur->_next;
delete cur;
cur = next;
}
_tables[i] = nullptr;
}
}
// 插入值为data的元素,如果data存在则不插入
pair<HTIterator,bool> Insert(const T& data)
{
Hash hs;
KeyOfT kot;
HTIterator it = Find(kot(data));
if (it != End())
return make_pair(it, false);
size_t hashi = hs(kot(data))%_tables.size();
Node* cur = new Node(data);
Node* newnode = cur;
cur->_next=_tables[hashi];
_tables[hashi] = cur;
_n++;
if (_n >= _tables.size() * 0.8)
{
vector<Node*> newtables(_tables.size() * 2 , nullptr);
for (int i = 0; i < _tables.size(); i++)
{
Node* cur = _tables[i];
while (cur)
{
Node* next = cur->_next;
//更新
hashi = hs(kot(cur->_data)) % newtables.size();
cur->_next = newtables[i];
newtables[i] = cur;
cur = next;
}
_tables[i] = nullptr;
}
swap(newtables, _tables);
}
return make_pair(HTIterator(newnode, this), true);
}
// 在哈希桶中查找值为key的元素,存在返回true否则返回false
HTIterator Find(const K& key)
{
Hash hs;
KeyOfT kot;
size_t hashi = hs(key)%_tables.size();
Node* cur = _tables[hashi];
while (cur)
{
if (kot(cur->_data) == key)
{
return HTIterator(cur, this);
}
cur = cur->_next;
}
return HTIterator(nullptr, this);
}
// 哈希桶中删除key的元素,删除成功返回true,否则返回false
bool Erase(const K& key)
{
Hash hs;
KeyOfT kot;
size_t hashi = hs(key)%_tables.size();
Node* prev = nullptr;
Node* cur = _tables[hashi];
while (cur)
{
if (kot(cur->_data) == key)
{
if (prev)
prev->_next = cur->_next;
else
_tables[hashi] = cur->_next;
delete cur;
--_n;
return true;
}
prev = cur;
cur = cur->_next;
}
return false;;
}
void clear() //清空桶中的元素
{
for (Node*& cur : _buckets)
{
while (cur)
{
Node* next = cur->_next;
delete cur;
cur = next;
}
cur = nullptr;
}
}
bool empty() const
{
return size() == 0;
}
private:
vector<Node*> _tables; // 指针数组
size_t _n = 0; // 表中存储数据个数
};
3.2unordered_map的封装
#pragma once
#include"Hash.h"
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)
{
size_t count = 0;
for (int i = 0; i < key.size(); i++)
{
count += (i + 1) * key[i];
}
return count;
}
};
template<class K, class V>
class unordered_map
{
struct KeyOfTmap
{
const K& operator()(const pair<K, V>& kv)
{
return kv.first;
}
};
public:
typedef typename hash_bucket::HashTable<K, pair<const K, V>, KeyOfTmap, hashfunc<K>>::HTIterator iterator;
typedef typename hash_bucket::HashTable<K, pair<const K, V>, KeyOfTmap, hashfunc<K>>::Const_HTIterator const_iterator;
iterator begin()
{
return _t.Begin();
}
iterator end()
{
return _t.End();
}
const_iterator begin() const
{
return _t.Begin();
}
const_iterator end()const
{
return _t.End();
}
pair<iterator,bool> insert(const pair<K,V>& kv)
{
return _t.Insert(kv);
}
V& operator[](const K& key)
{
return (_t.Insert(make_pair(key,V()))).first->second;
}
iterator Find(const K& key)
{
return _t.Find(key);
}
bool erase(const K& key)
{
return _t.Erase(key);
}
void clear()
{
_ht.clear();
}
bool empty() const
{
return _ht.empty();
}
private:
hash_bucket::HashTable<K, pair<const K, V>, KeyOfTmap, hashfunc<K>> _t;
};
3.3unordered_set的封装
#pragma once
#include"Hash.h"
template<class K>
class unordered_set
{
struct KeyOfTset
{
const K& operator()(const K& key)
{
return key;
}
};
public:
typedef typename hash_bucket::HashTable<K,const K, KeyOfTset, hashfunc<K>>::HTIterator iterator;
typedef typename hash_bucket::HashTable<K,const K, KeyOfTset, hashfunc<K>>::Const_HTIterator const_iterator;
iterator begin()
{
return _t.Begin();
}
iterator end()
{
return _t.End();
}
const_iterator begin() const
{
return _t.Begin();
}
const_iterator end()const
{
return _t.End();
}
pair<iterator, bool> insert(K& key)
{
return _t.Insert(key);
}
iterator Find(const K& key)
{
return _t.Find(key);
}
bool erase(const K& key)
{
return _t.Erase(key);
}
void clear()
{
_ht.clear();
}
bool empty() const
{
return _ht.empty();
}
private:
hash_bucket::HashTable<K, const K, KeyOfTset, hashfunc<K>> _t;
};