模拟实现unordered_map&unordered_set
文章目录
1. std::unordered_map 的定义与特性
所在头文件:<unordered_map>
std::unorederd_map 类模板:
template < class Key, // unordered_map::key_type
class T, // unordered_map::mapped_type
class Hash = hash<Key>, // unordered_map::hasher
class Pred = equal_to<Key>, // unordered_map::key_equal
class Alloc = allocator< pair<const Key,T> > // unordered_map::allocator_type
> class unordered_map;
1. unordered_map是存储<key, value>键值对的关联式容器,其允许通过keys快速的索引到与其对应的value。
2. 在unordered_map中,键值通常用于惟一地标识元素,而映射值是一个对象,其内容与此键关联。键和映射值的类型可能不同。
3. 在内部,unordered_map没有对<kye, value>按照任何特定的顺序排序, 为了能在常数范围内找到key所对应的value,unordered_map将相同哈希值的键值对放在相同的桶中。
4. unordered_map容器通过key访问单个元素要比map快,但它通常在遍历元素子集的范围迭代方面效率较低。
5. unordered_maps实现了直接访问操作符(operator[]),它允许使用key作为参数直接访问value。
6. 它的迭代器至少是前向迭代器
7. 关联性:std::unorederd_map 是一个关联容器,其中的元素根据键来引用,而不是根据索引来引用。
8. 无序性:在内部,std::unordered_map中的元素不会根据其键值或映射值按任何特定顺序排序,而是根据其哈希值组织到桶中,以允许通过键值直接快速访问各个元素(常量的平均时间复杂度)。
9. 唯一性:std::unorederd_map中的元素的键是唯一的。
一些类型定义
:
类型成员 定义
key_type 第一个模板参数(Key)
mapped_type 第二个模板参数(T)
value_type pair<const key_type,mapped_type>
hasher 第三个模板参数(Hash)
key_equal 第四个模板参数(Pred)
2. 构造 std::unordered_map:
3. 赋值操作
赋值方式 | 函数声明 |
---|---|
复制 | unordered_map& operator= ( const unordered_map& ump ); |
移动 | unordered_map& operator= ( unordered_map&& ump ); |
初始化列表 | unordered_map& operator= ( intitializer_list<value_type> il ); |
4. 迭代器操作
4.1 指向整个容器中的元素
函数声明 | 解释 |
---|---|
iterator begin() noexcept;const_iterator begin() const noexcept; | 返回一个迭代器,指向第一个元素 |
iterator end() noexcept;const_iterator end() const noexcept; | 返回一个迭代器,指向尾后元素 |
const_iterator cbegin() const noexcept; | 返回一个常量迭代器,指向第一个元素 |
const_iterator cend() const noexcept; | 返回一个常量迭代器,指向尾后元素 |
4.2 指向某个桶中的元素
函数声明 | 解释 |
---|---|
local_iterator begin( size_type n );const_local_iterator begin ( size_type n ) const; | 返回一个迭代器,指向第n个桶内的第一个元素 |
local_iterator end(size_type n);const_local_iterator end (size_type n) const; | 返回一个迭代器,指向第n个桶内的尾后元素 |
const_local_iterator cbegin( size_type n ) const; | 返回一个常量迭代器,指向第n个桶内的第一个元素 |
const_local_iterator cend( size_type n ) const | 返回一个常量迭代器,指向第n个桶内的尾后元素 |
5. 容量操作
函数声明 | 解释 |
---|---|
bool empty() const noexcept; | unordered_map 是否为空 |
size_type size() const noexcept; | 获取unordered_map 中元素的数量 |
6. 访问操作
访问方式 | 函数声明 | 解释 |
---|---|---|
使用方括号([]) | mapped_type& operator[] (const key_type& k);mapped_type& operator[] (key_type&& k); | 如果 k 匹配容器中某个元素的键,则该函数返回该映射值的引用。如果 k 与容器中任何元素的键都不匹配,则该函数将使用该键插入一个新元素,并返回该映射值的引用。 |
使用 at() mapped_type& at (const key_type& k); | const mapped_type& at (const key_type& k) const; | 如果 k 匹配容器中某个元素的键,则该函数返回该映射值的引用。如果 k 与容器中任何元素的键都不匹配,则该函数将抛出 out_of_range 异常。 |
注意:const std::unordered_map 不能使用 operator[] 操作!
!
7. 插入操作
8. 删除操作
9. 查找操作
10. 桶操作
unordered_set的接口基本和unordered_map的接口一致,其没有operator[]操作
11. 模拟实现unordered_map
因为unordered_map的底层是使用的哈希表的结构,所以必须有一个哈希表
哈希原理
如果要按照前文当中的哈希表模拟实现unordered_map就需要进行改造。
11.1 哈希表的改造
:
- 模板参数列表的改造:
// K:关键码类型
// V: 不同容器V的类型不同,如果是unordered_map,V代表一个键值对,如果是
//unordered_set,V为 K
// KeyOfValue: 因为V的类型不同,通过value取key的方式就不同,详细见
//unordered_map/set的实现
// HF: 哈希函数仿函数对象类型,哈希函数使用除留余数法,需要将Key转换为整形数字才能取模
template<class K, class V, class KeyOfValue, class HF = DefHashF<T> >
class HashBucket;
- 增加迭代器操作:
// 为了实现简单,在哈希桶的迭代器类中需要用到hashBucket本身,
template<class K, class V, class KeyOfValue, class HF>
class HashBucket;
// 注意:因为哈希桶在底层是单链表结构,所以哈希桶的迭代器不需要--操作
template <class K, class V, class KeyOfValue, class HF>
struct HBIterator
{
typedef HashBucket<K, V, KeyOfValue, HF> HashBucket;
typedef HashBucketNode<V>* PNode;
typedef HBIterator<K, V, KeyOfValue, HF> Self;
HBIterator(PNode pNode = nullptr, HashBucket* pHt = nullptr);
Self& operator++()
{
// 当前迭代器所指节点后还有节点时直接取其下一个节点
if (_pNode->_pNext)
_pNode = _pNode->_pNext;
else
{
// 找下一个不空的桶,返回该桶中第一个节点
size_t bucketNo = _pHt->HashFunc(KeyOfValue()(_pNode->_data))+1;
for (; bucketNo < _pHt->BucketCount(); ++bucketNo)
{
if (_pNode = _pHt->_ht[bucketNo])
break;
}
}
return *this;
}
Self operator++(int);
V& operator*();
V* operator->();
bool operator==(const Self& it) const;
bool operator!=(const Self& it) const;
PNode _pNode; // 当前迭代器关联的节点
HashBucket* _pHt; // 哈希桶--主要是为了找下一个空桶时候方便
};
- 增加通过key获取value操作:
template<class K, class V, class KeyOfValue, class HF = DefHashF<T> >
class HashBucket
{
friend HBIterator<K, V, KeyOfValue, HF>;
// ......
public:
typedef HBIterator<K, V, KeyOfValue, HF> Iterator;
//
// ...
// 迭代器
Iterator Begin()
{
size_t bucketNo = 0;
for (; bucketNo < _ht.capacity(); ++bucketNo)
{
if (_ht[bucketNo])
break;
}
if (bucketNo < _ht.capacity())
return Iterator(_ht[bucketNo], this);
else
return Iterator(nullptr, this);
}
Iterator End(){ return Iterator(nullptr, this);}
Iterator Find(const K& key);
Iterator Insert(const V& data);
Iterator Erase(const K& key);
// 为key的元素在桶中的个数
size_t Count(const K& key)
{
if(Find(key) != End())
return 1;
return 0;
}
size_t BucketCount()const{ return _ht.capacity();}
size_t BucketSize(size_t bucketNo)
{
size_t count = 0;
PNode pCur = _ht[bucketNo];
while(pCur)
{
count++;
pCur = pCur->_pNext;
}
return count;
}
// ......
};
HashTable.hpp
#pragma once
#include<vector>
#include"Common.hpp"
template<class T>
struct HashNode {
HashNode(const T& data = T())
:_pNext(nullptr)
,_data(data)
{}
HashNode<T>* _pNext;
T _data;
};
template<class T, class KorV, class DF = DFStr>
class HashBucket;
template<class T, class KorV,class DF = DFStr>
struct Iterator {
typedef HashNode<T> Node;
typedef Iterator<T, KorV, DF> Self;
Iterator(Node* pNode = nullptr, HashBucket<T, KorV, DF>* ht = nullptr)
:_pNode(pNode)
,_ht(ht)
{}
T& operator*() {
return _pNode->_data;
}
T* operator->() {
return &(_pNode->_data);
}
Self& operator++() {
Next();
return *this;
}
Self operator++(int) {
Self tmp(_pNode);
Next();
return tmp;
}
bool operator==(Self& t) {
return _pNode == t._pNode;
}
bool operator!=(Self& t) {
return _pNode != t._pNode;
}
void Next() {
if (_pNode->_pNext) {
_pNode = _pNode->_pNext;
}
else {
size_t addr = _ht->HashFunc(_pNode->_data);
for (int i = addr + 1; i < _ht->_arr.size(); ++i) {
if (_ht->_arr[i]) {
_pNode = _ht->_arr[i];
return;
}
}
_pNode = nullptr;
}
}
Node* _pNode;
HashBucket<T, KorV, DF>* _ht;
};
template<class T, class KorV, class DF>
class HashBucket {
public:
typedef HashNode<T> Node;
typedef HashBucket<T, KorV, DF> Self;
typedef Iterator<T, KorV, DF> iterator;
friend struct Iterator<T, KorV, DF>;
public:
HashBucket(size_t size = 11)
:_size(0)
{
_arr.resize(size);
}
~HashBucket() {
Clear();
}
std::pair<iterator, bool> Insert(const T& data) {
CheckCapacity();
size_t addr = HashFunc(data);
//查找是否有重复节点
Node* ptr = _arr[addr];
while (ptr) {
if (KorV()(ptr->_data) == KorV()(data))
return make_pair(iterator(ptr, this), false);
ptr = ptr->_pNext;
}
ptr = new Node(data);
ptr->_pNext = _arr[addr];
_arr[addr] = ptr;
++_size;
return make_pair(iterator(ptr, this), true);
}
size_t Erase(const T& data) {
size_t addr = HashFunc(data);
Node* ptr = _arr[addr];
Node* pre = nullptr;
while (ptr) {
if (ptr->_data == data) {
if (!pre) //删除头结点
_arr[addr] = ptr->_pNext;
else //删除中间节点
pre->_pNext = ptr->_pNext;
delete ptr;
--_size;
return 1;
}
else {
pre = ptr;
ptr = ptr->_pNext;
}
}
return 0;
}
iterator find(const T& data) const{
size_t addr = HashFunc(data);
Node* ptr = _arr[addr];
while (ptr) {
if (KorV()(ptr->_data) == KorV()(data))
return iterator(ptr, this);
ptr = ptr->_pNext;
}
return iterator(nullptr, this);
}
size_t size() const{
return _size;
}
bool empty()const {
return _size == 0;
}
iterator begin() {
for (int i = 0; i < _arr.size(); ++i) {
if (_arr[i]) {
return iterator(_arr[i], this);
}
}
return iterator(nullptr, this);
}
iterator end() {
return iterator(nullptr, this);
}
void Clear() {
for (size_t i = 0; i < _arr.size(); ++i) {
Node* ptr = _arr[i];
while (ptr) {
_arr[i] = ptr->_pNext;
delete ptr;
ptr = _arr[i];
}
}
_size = 0;
}
size_t bucket_count() const{
return _arr.size();
}
size_t bucket_size(size_t n) const{
if (n >= _arr.size())
return 0;
size_t count = 0;
Node* ptr = _arr[n];
while (ptr) {
++count;
ptr = ptr->_pNext;
}
return count;
}
size_t bucket(const T& data) {
return HashFunc(data);
}
void Swap(Self& t) {
_arr.swap(t._arr);
std::swap(_size, t._size);
}
private:
void CheckCapacity() { //扩容
if (_size == _arr.size()) {
size_t newSize = GetNextPrime(_arr.size());
Self newBucket(newSize);
for (size_t i = 0; i < _arr.size(); ++i) {
Node* ptr = _arr[i];
while (ptr) {
size_t addr = newBucket.HashFunc(ptr->_data);
_arr[i] = ptr->_pNext;
ptr->_pNext = newBucket._arr[addr];
newBucket._arr[addr] = ptr;
++newBucket._size;
ptr = _arr[i];
}
}
Swap(newBucket);
}
}
size_t HashFunc(const T data) const{
return DF()(KorV()(data)) % _arr.size();
}
private:
std::vector<Node*> _arr;
size_t _size;
};
unordered_map
#pragma once
#include"HashBucket.hpp"
template<class K,class V>
class Unordered_map {
typedef std::pair<K, V> ValueType;
typename typedef HashBucket<ValueType, KORV>::iterator iterator;
struct KORV
{
const K& operator()(const ValueType& data)const {
return data.first;
}
};
public:
Unordered_map(size_t size = 11)
:_ht(size)
{}
iterator begin(){
return _ht.begin();
}
iterator end(){
return _ht.end();
}
bool empty()const {
return _ht.empty();
}
size_t size()const {
return _ht.size();
}
std::pair<iterator,bool> insert(const ValueType& data){
return _ht.Insert(data);
}
size_t erase(const K& key){
return _ht.Erase(ValueType(key,V()));
}
iterator find(const K& key) const{
return _ht.find(ValueType(key, V()));
}
void clear() {
_ht.Clear();
}
void swap(const Unordered_map<K,V>& m) {
_ht.Swap(m._ht);
}
V& operator[](const K& k) {
return (*(insert(ValueType(k, V())).first)).second;
}
size_t buck_count()const {
return _ht.bucket_count();
}
size_t buck_size(size_t n)const {
return _ht.bucket_size(n);
}
size_t bucket(const K& k) {
return _ht.bucket(ValueType(k, V()));
}
private:
HashBucket<ValueType, KORV> _ht;
};
12. 模拟实现unordered_set
#pragma once
#include"HashBucket.hpp"
template<class K>
class Unordered_set {
typedef K ValueType;
typename typedef HashBucket<ValueType, KORV>::iterator iterator;
struct KORV
{
const ValueType& operator()(const ValueType& data)const {
return data;
}
};
public:
Unordered_set(size_t size = 11)
:_ht(size)
{}
iterator begin() {
return _ht.begin();
}
iterator end() {
return _ht.end();
}
bool empty()const {
return _ht.empty();
}
size_t size()const {
return _ht.size();
}
std::pair<iterator, bool> insert(const ValueType& data) {
return _ht.Insert(data);
}
size_t erase(const ValueType& key) {
return _ht.Erase(key);
}
iterator find(const ValueType& key) const {
return _ht.find(key);
}
void clear() {
_ht.Clear();
}
void swap(const Unordered_set<K>& m) {
_ht.Swap(m._ht);
}
size_t buck_count()const {
return _ht.bucket_count();
}
size_t buck_size(size_t n)const {
return _ht.bucket_size(n);
}
size_t bucket(const ValueType& k) {
return _ht.bucket(k);
}
private:
HashBucket<ValueType, KORV> _ht;
};