在C++98中,STL提供了底层为红黑树结构的一系列关联式容器,在查询时的效率可达到 O ( logN ) ,即最差情况下需要比较红黑树的高度次,当树中的结点非常多时,查询效率也不理想。最好的查询是,进行很少的比较次数就能够将元素找到,因此在C++11中,STL又提供了4个unordered系列的关联式容器,这四个容器与红黑树结构的关联式容器使用方式基本类似,只是其底层结构不同。
容器 | 底层数据结构 | 是否有序 | 实现版本 | 增删查改的效率 | 迭代器类型 |
---|---|---|---|---|---|
unordered_map/unordered_set | 哈希表/散列表 | 遍历无序 | C++11 | O ( 1 ) | 单向迭代器 |
map/set | 红黑树 | 遍历有序 | C++98 | O ( l o g N ) | 双向迭代器 |
所以当处理数据量小时,map/set容器与unordered_map/unordered_set容器增删查改的效率差异不大。当处理数据量大时,map/set容器与unordered_map/unordered_set容器增删查改的效率相比,unordered系列容器的效率更高。
unordered_map,unordered_set在用法和接口上与map,set相同,当然他们也有允许键值冗余的unordered_multimap和unordered_multiset版本。
不过unordered_multimap和unordered_multiset没有反向迭代器,只有正向迭代器。
unordered_set当中常用的成员函数如下:
成员函数 | 功能 |
---|---|
insert | 插入指定元素 |
erase | 删除指定元素 |
find | 查找指定元素 |
size | 获取容器中元素的个数 |
empty | 判断容器是否为空 |
clear | 清空容器 |
swap | 交换两个容器中的数据 |
count | 获取容器中指定元素值的元素个数 |
begin | 获取容器中第一个元素的正向迭代器 |
end | 获取容器中最后一个元素下一个位置的正向迭代器 |
unordered_map当中常用的成员函数如下:
成员函数 | 功能 |
---|---|
insert | 插入键值对 |
erase | 删除指定key值的键值对 |
find | 查找指定key值的键值对 |
size | 获取容器中元素的个数 |
empty | 判断容器是否为空 |
clear | 清空容器 |
swap | 交换两个容器中的数据 |
count | 获取容器中指定key值的元素个数 |
begin | 获取容器中第一个元素的正向迭代器 |
end | 获取容器中最后一个元素下一个位置的正向迭代器 |
模拟实现unordered_map和unordered_set的本质是调用哈希表的接口,这和用红黑树模拟实现set和map是一样的。
由于开散列(哈希桶)的性能比闭散列要高,因此使用哈希桶来进行模拟实现。
有几点需要注意:
template<class K>
struct Hash
{
size_t operator()(const K& key) //返回键值key
{
return key;
}
};
//string类型的特化
template<>
struct Hash<string>
{
size_t operator()(const string& s) //BKDRHash算法
{
size_t value = 0;
for (auto ch : s)
{
value = value * 131 + ch;
}
return value;
}
};
//正向迭代器
template<class K, class T, class KeyOfT, class HashFunc = Hash<K>>
struct __HTIterator
{
typedef HashNode<T> Node; //哈希结点的类型
typedef HashTable<K, T, KeyOfT, HashFunc> HT; //哈希表的类型
typedef __HTIterator<K, T, KeyOfT, HashFunc> Self; //正向迭代器的类型
Node* _node; //结点指针
HT* _pht; //哈希表的地址
//构造函数
__HTIterator(Node* node, HT* pht)
:_node(node) //结点指针
, _pht(pht) //哈希表地址
{}
};
#include"HashTable.h"
namespace hjl
{
template<class K, class V>
class unordered_map
{
//仿函数,用来获取K值
struct MapKeyOfT
{
const K& operator()(const pair<K, V>& kv)
{
return kv.first;
}
};
public:
typedef typename OpenHash::HashTable<K, pair<K, V>, MapKeyOfT>::iterator iterator;
iterator begin()
{
return _ht.begin();
}
iterator end()
{
return _ht.end();
}
//插入函数
pair<iterator, bool> insert(const pair<K, V>& kv)
{
return _ht.Insert(kv);
}
//赋值运算符重载
V& operator[](const K& key)
{
pair<iterator, bool> ret = insert(make_pair(key, V()));
iterator it = ret.first;
return it->second;
}
//删除函数
void erase(const K& key)
{
_ht.Erase(key);
}
//查找函数
iterator find(const K& key)
{
return _ht.Find(key);
}
private:
OpenHash::HashTable<K, pair<K, V>, MapKeyOfT> _ht;
};
}
#include"HashTable.h"
namespace hjl
{
template<class K>
class unordered_set
{
//仿函数,用来获取K值
struct SetKeyOfT
{
const K& operator()(const K& key)
{
return key;
}
};
public:
typedef typename OpenHash::HashTable<K, K, SetKeyOfT>::iterator iterator;
iterator begin()
{
return _ht.begin();
}
iterator end()
{
return _ht.end();
}
//插入函数
pair<iterator, bool> insert(const K& key)
{
return _ht.Insert(key);
}
//删除函数
void erase(const K& key)
{
_ht.Erase(key);
}
//查找函数
iterator find(const K& key)
{
return _ht.Find(key);
}
private:
OpenHash::HashTable<K, K, SetKeyOfT> _ht;
};
}
namespace OpenHash//开散列
{
template<class T>
struct HashNode
{
HashNode<T>* _next;
T _data;
HashNode(const T& data)
:_next(nullptr)
, _data(data)
{}
};
template<class K>
struct Hash
{
size_t operator()(const K& key)
{
return key;
}
};
template<>
struct Hash<string>
{
size_t operator()(const string& s)
{
size_t value = 0;
for (auto ch : s)
{
value += ch;
value *= 131;
}
return value;
}
};
//前置声明,因为迭代器中用到了HashTable,HashTable中又用到了迭代器,
//所以如果要先实现迭代器,必须要先声明HashTable
template<class K, class T, class KeyOfT, class HashFunc>
struct HashTable;
//迭代器
template<class K, class T, class KeyOfT, class HashFunc = Hash<K>>
struct _HTIterator
{
typedef HashNode<T> Node;
typedef _HTIterator<K, T, KeyOfT, HashFunc> Self;
typedef HashTable<K, T, KeyOfT, HashFunc> HT;
Node* _node; //当前结点的指针
HT* _pht;//哈希表的地址地址,为了实现++重载
_HTIterator(Node* node, HT* pht)
:_node(node)
,_pht(pht)
{}
Self& operator++()
{
//如果当前桶中还有数据,就在当前桶往后走
//当前桶走完了,继续往后找下一个桶
if (_node->_next)
{
_node = _node->_next;
}
else//该结点是当前哈希桶中的最后一个结点
{
size_t index = HashFunc()( KeyOfT()(_node->_data)) % _pht->_table.size();
//开始找后面的桶
++index;
while (index<_pht->_table.size())
{
//当前哈希桶非空,直接返回头结点
if (_pht->_table[index])
{
_node = _pht->_table[index];
return *this;
}
//为空则继续找后面的桶
else
{
++index;
}
}
_node = nullptr;
}
return *this;
}
T& operator*()
{//返回哈希结点中数据的引用
return _node->_data;
}
T* operator->()
{ //返回哈希结点中数据的地址
return &_node->_data;
}
bool operator!=(const Self& s)const
{
return _node != s._node;
}
bool operator==(const Self& s)const
{
return _node == s._node;
}
};
template<class K, class T, class KeyOfT,class HashFunc = Hash<K>>
struct HashTable
{
public:
template<class K, class T, class KeyOfT, class HashFunc>
friend struct _HTIterator;
typedef HashNode<T> Node;
typedef _HTIterator<K, T, KeyOfT, HashFunc> iterator;
HashTable() = default;//显示指定生成默认构造
HashTable(const HashTable& ht)
{
//1、将哈希表的大小调整为ht._table的大小
_n = ht._n;
_table.resize(ht._table.size());
//2、将ht._table每个桶当中的结点一个个拷贝到自己的哈希表中
for (size_t i = 0; i < ht._table.size(); i++)
{
Node* cur = ht._table[i];
while (cur)
{
Node* copy = new Node(cur->_data);
copy->_next = _table[i];
_table[i] = copy;
//取下一个待拷贝结点
cur = cur->_next;
}
}
}
HashTable& operator=(HashTable ht)
{
_table.swap(ht._table);
swap(_n, ht._n);
//返回哈希表的引用,从而实现连续赋值
return *this;
}
~HashTable()
{
//将哈希表当中的结点一个个释放
for (size_t i = 0; i < _table.size(); i++)
{
Node* cur = _table[i];
while (cur)
{
Node* next = cur->_next;
delete cur;
cur = next;
}
_table[i] = nullptr;
}
}
iterator begin()
{
size_t i = 0;
//找到第一个非空哈希桶
while (i < _table.size())
{
if (_table[i])
{
//返回该哈希桶中的第一个结点的正向迭代器
return iterator(_table[i], this);
}
++i;
}
return end();
}
iterator end()
{
return iterator(nullptr, this);
}
iterator Find(const K& key)
{
HashFunc hf;
KeyOfT kot;
if (_table.size() == 0) return end();
size_t index = hf(key) % _table.size();
Node* cur = _table[index];
while (cur)
{
if (kot(cur->_data)== key)
{
return iterator(cur,this);
}
else
{
cur = cur->_next;
}
}
return end();
}
//获取本次增容后哈希表的大小
size_t GetNextPrime(size_t prime)
{
const int PRIMECOUNT = 28;
//素数序列
const size_t primeList[PRIMECOUNT] =
{
53ul, 97ul, 193ul, 389ul, 769ul,
1543ul, 3079ul, 6151ul, 12289ul, 24593ul,
49157ul, 98317ul, 196613ul, 393241ul, 786433ul,
1572869ul, 3145739ul, 6291469ul, 12582917ul, 25165843ul,
50331653ul, 100663319ul, 201326611ul, 402653189ul, 805306457ul,
1610612741ul, 3221225473ul, 4294967291ul
};
size_t i = 0;
for (i = 0; i < PRIMECOUNT; i++)
{
if (primeList[i] > prime)
return primeList[i];
}
return primeList[i];
}
pair<iterator, bool> Insert(const T& data)
{
HashFunc hf;
KeyOfT kot;
auto ret=Find(kot(data));
if (ret != end())
{
return make_pair(ret, false);
}
//负载因子到1时进行增容
if (_n == _table.size())
{
vector<Node*>newtable;
//size_t newSize = _table.size() == 0 ? 10 : _table.size() * 2;
//newtable.resize(newSize);
newtable.resize(GetNextPrime(_table.size()));
//遍历旧表中的节点,重新计算映射位置,挂到新表中
for (size_t i = 0; i < _table.size(); ++i)
{
if (_table[i])
{
Node* cur = _table[i];
while (cur)
{
//记录原来cur后面的节点
Node* next = cur->_next;
size_t index = hf(kot(cur->_data)) % newtable.size();
//头插
cur->_next = _table[index];
_table[index] = cur;
cur = next;
}
_table[i] = nullptr;
}
}
_table.swap(newtable);
}
//将键值对插入哈希表
size_t index = hf(kot(data)) % _table.size();
Node* newnode = new Node(data);
newnode->_next = _table[index];
_table[index] = newnode;
++_n;
return make_pair(iterator(newnode, this), true);
}
bool Erase(const K& key)
{
HashFunc hf;
size_t index = hf(key) % _table.size();
Node* prev = nullptr;
Node* cur = _table[index];
while (cur)
{
//找到了待删除结点,则删除该结点
if (kot(cur->_data) == key)
{
if (_table[index] == cur)
{
_table[index] = cur->_next;
}
else
{
prev->_next = cur->_next;
}
--_n;
delete cur;
return true;
}
prev = cur;
cur = cur->_next;
}
//要删的节点不存在
return false;
}
private:
vector<Node*> _table;
size_t _n = 0;//有效数据的个数
};
}
资料参考:
STL详解(十三)—— 用一个哈希表同时封装出unordered_map和unordered_set
哈希表底层探索