unordered_map关联式容器
1. 文档介绍
- unorder_map是存储<key, value>键值对的关联式容器,其允许通过key快速的索引到与其对应的value
- 键和映射值的类型可能不同,键值通常用于唯一的标识元素,而映射值是一个对象
- 在内部unorder_map没有对<key, value>按照任何特定的顺序排序,为了在常数范围内找到key所对应的value,unorder_map将相同哈希值的键值对放在相同的桶中
- unorder_map容器通过key访问单个元素要比map快,但它通常在遍历元素子集的范围迭代方面效率较低
- unorder_map实现了直接访问操作符(operator[]),它允许使用key作为参数直接访问value,map也可以
- 它的迭代器至少是前向迭代器
- hash的性能非常出色:拥有高达O(1)的插入和查找复杂度.这比map(平衡二叉树)的O(logn)要快
2. 接口说明
2.1 构造
#include<unordered_map>
unordered_map<T1, T2> mp;
2.2 容量
函数声明 | 功能介绍 |
---|
bool empty() const | 检测容器是否为空 |
size_t size() const | 获取容器中的有效元素个数 |
2.3 迭代器
函数声明 | 功能介绍 |
---|
begin | 返回第一个元素的迭代器 |
end | 返回最后一个元素下一个位置的迭代器 |
cbegin | 返回第一个元素的const迭代器 |
cend | 返回最后一个元素下一个位置的const迭代器 |
2.4 元素访问
函数声明 | 功能介绍 |
---|
operator[] | 返回与key对应的value,没有一个默认值 |
2.5 查询
函数声明 | 功能介绍 |
---|
iterator find(const K& key) | 返回key在哈希桶中的位置 |
size_t count(const K& key) | 返回哈希桶中关键码为key的键值对的个数,查看是否存在 |
unordered_map中key是不能重复的,因此count函数的返回值最大为1
2.6 修改操作
函数声明 | 功能介绍 |
---|
insert | 向容器中插入键值对 |
erase | 删除容器中的键值对 |
void clear() | 清空容器中有效元素个数 |
void swap(unorder_map&) | 交换两个容器中的元素 |
2.7 桶操作
函数声明 | 功能介绍 |
---|
size_t bucket_count() const | 返回哈希桶中桶的总个数 |
size_t bucket_size(size_t n) const | 返回n号桶中有效元素的总个数 |
size_t bucket(const K& key) | 返回元素key所在的桶号 |
3. 底层结构
unorder系列的关联式容器之所以效率比较高,是因为其底层使用了哈希结构
map/multimap属于关联式容器,底层结构是用二叉树实现
4. 哈希表代码实现
1. 开放定址法中的删除
enum Status{EMPTY, EXIST, DELETE};
2. 线性探测的实现
#include<iostream>
#include<vector>
using namespace std;
static const int num_prime = 32;
static const unsigned long prime_list[num_prime] =
{
3, 7, 13, 19,
53, 97, 193, 389, 769,
1543, 3079, 6151, 12289, 24593,
49157, 98317, 196613, 393241, 786433,
1572869, 3145739, 6291469, 12582917, 25165843,
50331653, 100663319, 201326611, 402653189, 805306457,
1610612741, 3221225473ul, 4294967291ul
};
unsigned long GetNextPrime(size_t num)
{
for(int i=0; i<num_prime; i++)
{
if(prime_list[i] > num)
{
return prime_list[i];
}
}
return prime_list[num_prime - 1];
};
enum Status
{
EMPTY,
EXIST,
DELETE
};
template<class key, class value>
class HashTable{
struct Elem
{
pair<key, value> _val;
Status _status;
};
public:
HashTable(size_t capacity = 3) : _ht(capacity), _size(0)
{
for(size_t i= 0; i<capacity; ++i)
_ht[i]._status = EMPTY;
}
public:
bool Insert(const pair<key, value>& val)
{
CheckCapacity();
size_t hashAddr = HashFunc(val.first);
while(_ht[hashAddr]._status != EMPTY)
{
if(_ht[hashAddr]._status == EXIST && _ht[hashAddr]._val.first == val.first)
return false;
if(_ht[hashAddr]._status == DELETE)
break;
hashAddr++;
if(hashAddr == _ht.capacity())
hashAddr = 0;
}
_ht[hashAddr]._status = EXIST;
_ht[hashAddr]._val = val;
_size++;
return true;
}
size_t Size() const
{
return _size;
}
bool Empty() const
{
return _size == 0;
}
int Find(const key& t)
{
size_t hashAddr = HashFunc(t);
while(_ht[hashAddr]._status != EMPTY)
{
if(_ht[hashAddr]._val.first == t && _ht[hashAddr]._val.second != DELETE)
return hashAddr;
hashAddr++;
}
return -1;
}
bool Erase(const key& k)
{
int index = Find(k);
if(index == -1)
return false;
_ht[index]._status = DELETE;
_size--;
return true;
}
void Swap(HashTable<key, value>& ht)
{
swap(_ht, ht._ht);
swap(_size, ht._size);
}
private:
void CheckCapacity()
{
if(_size * 10 / _ht.capacity() >= 7)
{
HashTable<key, value> newHt(GetNextPrime(_ht.capacity()));
for(size_t i=0; i<_ht.capacity(); i++)
{
if(_ht[i]._status == EXIST)
newHt.Insert(_ht[i]._val);
}
Swap(newHt);
}
}
size_t HashFunc(const key& k)
{
return k % _ht.capacity();
}
private:
vector<Elem> _ht;
size_t _size;
};
int main()
{
cout << "111" << endl;
HashTable<int, int> ht;
int ar[] = { 4, 6, 8, 3, 6, 13, 1, 2, 9};
int n = sizeof(ar) / sizeof(int);
cout << n << endl;
for(int i=0; i<n; i++)
{
cout << "222" << endl;
ht.Insert(pair<int, int>(ar[i], ar[i]));
cout << "333" << endl;
}
cout << ht.Size() << endl;
ht.Erase(8);
cout << ht.Size() << endl;
cout <<"key = 6 : index = "<< ht.Find(6) << endl;
cout << "key = 8 : index = " << ht.Find(8) << endl;
cout <<"key = 13 : index = " << ht.Find(13) << endl;
return 0;
}
3. 链地址法
#include<iostream>
#include<algorithm>
#include<vector>
#include<string>
using namespace std;
static const int num_prime = 28;
static const unsigned long prime_list[num_prime] =
{
53, 97, 193, 389, 769,
1543, 3079, 6151, 12289, 24593,
49157, 98317, 196613, 393241, 786433,
1572869, 3145739, 6291469, 12582917, 25165843,
50331653, 100663319, 201326611, 402653189, 805306457,
1610612741, 3221225473ul, 4294967291ul
};
inline unsigned long next_prime(unsigned long n)
{
const unsigned long* first = prime_list;
const unsigned long* last = prime_list + num_prime;
const unsigned long* pos = lower_bound(first, last, n);
return pos == last ? *(last-1) : *pos;
}
template<class key_type, class value_type>
struct HashBucketNode
{
HashBucketNode<key_type, value_type>* _pNext;
pair<key_type, value_type> _val;
};
template<class key_type, class value_type>
class HashBucket
{
typedef struct HashBucketNode<key_type, value_type> Node;
public:
HashBucket(size_t capacity = 3) : _size(0)
{
const size_t n_buckets = next_prime(capacity);
_ht.reserve(n_buckets);
_ht.insert(_ht.end(), n_buckets, (Node*)0);
}
Node* new_node(const pair<key_type, value_type>& val)
{
Node* n = (Node*)malloc(sizeof(Node));
n->_pNext = (Node*)0;
n->_val = val;
return n;
}
Node* Insert(const pair<key_type, value_type>& val)
{
CheckCapacity(_size+1);
size_t bucketNow = HashFunc(val.first);
Node* pCur = _ht[bucketNow];
while(pCur)
{
if(pCur->_val.first == val.first && pCur->_val.second == val.second)
return pCur;
pCur = pCur->_pNext;
}
pCur = new_node(val);
pCur->_pNext = _ht[bucketNow];
_ht[bucketNow] = pCur;
_size++;
return pCur;
}
bool Erase(const key_type& k)
{
size_t bucketNow = HashFunc(k);
Node* pCur = _ht[bucketNow];
Node* pPrev = nullptr, pRet = nullptr;
while (pCur)
{
if (pCur->_val.first == k)
{
if (pCur == _ht[bucketNow])
_ht[bucketNow] = pCur->_pNext;
else
pPrev->_pNext = pCur->_pNext;
pRet = pCur->_pNext;
delete pCur;
_size--;
return pRet;
}
}
return nullptr;
}
Node* Find(const key_type& data);
void Swap(HashBucket<key_type, value_type>& ht)
{
swap(_ht, ht._ht);
swap(_size, ht._size);
}
void CheckCapacity(size_t num_elements_hint)
{
size_t bucketCount =_ht.capacity();
if (num_elements_hint > bucketCount)
{
const size_t n = next_prime(num_elements_hint);
HashBucket<key_type, value_type> newHt(n);
for (size_t bucketIdx = 0; bucketIdx < _ht.size(); ++bucketIdx)
{
Node* pCur = _ht[bucketIdx];
while (pCur)
{
_ht[bucketIdx] = pCur->_pNext;
size_t bucketNo = newHt.HashFunc(pCur->_val.first);
pCur->_pNext = newHt._ht[bucketNo];
newHt._ht[bucketNo] = pCur;
pCur = _ht[bucketIdx];
}
}
newHt._size = _size;
this->Swap(newHt);
}
}
size_t Size() const
{
return _size;
}
bool Empty() const
{
return _size == 0;
}
void show_hashtable()
{
for(size_t i=0; i< _ht.size(); i++)
{
Node* p = _ht[i];
cout << "_ht[" << i << "]:";
while(p != NULL)
{
cout << "key: " << p->_val.first << " value: " << p->_val.second << " ->";
p = p->_pNext;
}
cout << endl;
}
}
private:
size_t HashFunc(const key_type& data)
{
return data % _ht.capacity();
}
private:
vector<Node*> _ht;
size_t _size;
};
int main()
{
int ar[] = {2, 55, 108, 161, 6, 8, 6, 8, 4};
int n = sizeof(ar) / sizeof(int);
HashBucket<int, int> ht(3);
for (int i = 0; i < n; ++i) {
ht.Insert(pair<int, int>(ar[i], ar[i]));
}
ht.show_hashtable();
return 0;
}
运行结果如下:
4. 开散列与闭散列的比较
用链地址法处理哈希冲突,需要增加链接的指针,似乎增加了存储开销。事实上:由于开地址法必须保持大量的空闲空间来确保搜索效率,如二次探查法要求装载因子a<=0.7,而表项所占空间又比指针大的多,所以使用链地址法反而比开地址法节省存储空间
5. 存储其他类型
template<class T>
class DefHashF
{
public:
size_t operator()(const T& val)
{
return val;
}
};
class Str2Int
{
public:
size_t operator()(const string& s)
{
const char* str = s.c_str();
unsigned int seed = 131;
unsigned int hash = 0;
while(*str)
{
hash = hash * seed + (*str++);
}
return (hash & 0x7FFFFFFF);
}
};
参考文章