STL 关联式容器
目录
简介
说到 关联式容器, 需要区分是序列式容器, 例如vector, list, deque等都是序列式容器, 序列式其特点都体现在 "序列"上 .
序列式容器中存储元素是线性排布的, 在进行查找搜索时效率较低. 序列式容器并不会对插入元素进行任何方式的重新排序,
插入在哪个位置就在哪个位置 .
关联式容器, 特点也体现在 "关联"上, 关联式容器通过键值对(包括key和value), 将存储的每一个数据value与一个键值key一
一对应起来, 只需要通过键值key就可以读写元素, 在查找搜索时效率高.
关联式容器分为两种, 有序和无序(unordered)的, C++98中, STL只有底层用红黑树实现的有序的关联式容器包括map, set,
multimap, multiset. 这四个容器会将插入的元素按特定顺序存储(并不是像序列式容器一样存在哪个位置就在哪个位置, 而是
存元素的位置可能会还会进行调整), 以保证红黑树的平衡, 进而实现了log₂N的查询效率, 即最坏情况也只需要查询红黑树的
树高次. 但log₂N的时间复杂度毕竟不是常数, 当数据量特别大时, 也会比较耗时.
所以, 在C++11中, STL中又新增了4个 unordered系列的(无序的)关联式容器, 包括 unordered_map, unordered_set,
unordered_multimap, unordered_multiset. 它们的底层是用哈希表(散列表)来实现的, 即 将键值key值映射到表中一个位置来
访问value. 所以不会按照某种顺序来存储元素, 即是无序的, 在不发生哈希冲突(碰撞)时查询效率可达到O(1) .
关于哈希详细写在另一篇博客,戳链接( ̄︶ ̄)↗ https://blog.csdn.net/qq_41071068/article/details/104475643
unordered_map
在线说明文档,戳链接( ̄︶ ̄)↗ http://www.cplusplus.com/reference/unordered_map/unordered_map/
简介
1. 头文件 #include<unordered_map>
2. unordered_map存储的是<key, value>键值对, 可以直接通过key快速访问到其对应的value, 与map保持一致
3. unordered_map中key用来唯一的标识元素, 所以, key是唯一的, 即不同元素的value可以相同或不同, 但key必定是不同的
这一点与map保持一致.
4. 在unordered_map 内并没有对<kye, value>按照任何特定的顺序排序, 为了能在时间复杂度是常数范围内找到key所对应
的value,unordered_map将相同哈希值的键值对放在相同的桶中 . 与map不同
5. unordered_map容器通过key访问单个元素要比map快 .但unordered_map在元素子集的范围迭代上效率不如map. 这都是
有他们各自的底层结构决定的.
6. template<K, T, Hash, Pred, Alloc>
class unordered_map;template < class K, //键值key的类型 class T, //数据Value的类型 class Hash = hash<Key>, /*用于将K类型的Key转换成一个size_t类 型的唯一值并返回, 用作哈希函数的参数 可以是仿函数或函数指针*/ class Pred = equal_to<Key>, /*返回pair<K, T>(key, Value)元素的first 也就是key, 可以是仿函数或函数指针*/ class Alloc = allocator< pair<const Key,T> > //空间配置器 > class unordered_map;
键值对
SGI-STL中关于键值对的定义:
template <class T1, class T2>
struct pair {
typedef T1 first_type;
typedef T2 second_type;
T1 first;
T2 second;
pair() :
first(T1()),
second(T2())
{}
pair(const T1& a, const T2& b)
: first(a), second(b)
{}
};
pair用来将key和value合并为一个键值对, 其中first就是key, second就是value .
常用接口介绍
1.常见构造
构造方式 | 功能 |
---|---|
unordered_map( ); | 构造空的unordered_map |
template <class InputIterator> unordered_map ( InputIterator first, InputIterator last) |
用 [ first,last ) 区间中的元素构造unordered_map |
unordered_map ( const unordered_map& ump ) | 拷贝构造 |
unordered_map ( initializer_list<value_type> il) | 初始化列表构造 |
2. unordered_map iterator(迭代器)的使用
函数声明 | 功能 |
---|---|
begin( ) | 返回第一个元素位置的iterator |
end( ) | 返回最后一个元素后一个位置的iterator |
cbegin( ) | 返回第一个元素位置的const_iterator |
cend( ) | 返回最后一个元素后一个位置的const_iterator |
具体用法 :
#include<iostream>
#include<unordered_map>
using namespace std;
void test1() {
pair<int, int> p1(1, 2);
pair<int, int> p2(3, 4);
pair<int, int> p3(5, 6);
pair<int, int> p4(7, 8);
unordered_map<int, int> mp1;
unordered_map<int, int> mp2 = { p1, p2, p3, p4 };
unordered_map<int, int> mp3(mp2);
unordered_map<int, int> mp4(mp2.begin(), mp2.end());
unordered_map<int, int>::iterator i;
cout << "mp2:\n";
for (i = mp2.begin(); i != mp2.end(); ++i) {
cout << (*i).first << ' ' << (*i).second << endl;
}
cout << "mp3:\n";
for (i = mp3.begin(); i != mp3.end(); ++i) {
cout << (*i).first << ' ' << (*i).second << endl;
}
cout << "mp4:\n";
for (i = mp4.begin(); i != mp4.end(); ++i) {
cout << (*i).first << ' ' << (*i).second << endl;
}
}
int main() {
test1();
return 0;
}
3.容量操作
函数申明 | 功能 |
---|---|
size_t size( ) | 返回元素的个数 |
bool empty( ) | 检测是否为空 |
4. unordered_map的修改操作
函数申明 | 功能 |
---|---|
pair<iterator,bool> insert ( const value_type& val ); template <class InputIterator> void insert ( InputIterator first, InputIterator last ); void insert ( initializer_list<value_type> il ); |
插入元素val 插入[first, last)范围内的元素 插入参数列表中得值 |
iterator erase ( const_iterator position ); size_type erase ( const key_type& k ); iterator erase ( const_iterator first, const_iterator last ); |
删除postion位置处的元素 删除键值为k的元素 删除[first, last)范围内的元素 |
void clear( ); | 清空容器中的所有元素 |
void swap(unordered_map& ump) | 交换两个容器中的元素 |
关于迭代器失效的问题:
当insert后, 如发生扩容, 则之前所有元素的迭代器都会失效. 如不发生扩容, 则不会失效.
当erase后, 迭代器一定会失效
5. 元素访问与查询
函数申明 | 功能 |
---|---|
T& operator[](const K& key); T& operator[](K&& key); |
返回与key对应的value的引用, 如果没有则插入(key, T()), 并返回value的引用 |
T& at(const K& key); | 返回与key对应的value的引用,如果没有则抛出异常 |
size_t count(const K& key); | 返回哈希桶中关键码为key的键值对的个数,unordered_map中要么为1要么为0 |
iterator find(const K& key) | 返回key在哈希桶中的位置 |
值得注意的是 [] 重载, 当存在于key所对应的value时, 返回value的引用, 当不存在时, 则插入(key, T())到容器中, T()是value的默认
构造. 还有就是at()与[ ]重载不同的是, 当key没有对应的value时, at()会抛出异常
6. unordered_map的桶操作
函数申明 | 功能 |
---|---|
size_t bucket_count()const | 返回哈希桶中桶的总个数 |
size_t bucket_size(size_t n)const | 返回n号桶中有效元素的总个数 |
size_t bucket(const K& key) | 返回元素key所在的桶号 |
常见接口实现
unordered_map只是对哈希桶的一个封装, 所以首先得实现一个哈希桶, 如下:
HashBucket.h
#pragma once
#include<vector>
using namespace std;
template <class V>
class HashBucketNode {
public:
V m_val;
HashBucketNode * m_next;
HashBucketNode(const V& val = V()) :
m_val(val),
m_next(nullptr)
{}
template<class K, class V, class KeyOfVal, class HF>
friend class Iterator;
template<class K, class V, class KeyOfVal, class HF>
friend class HashBucket;
};
class dealInt {
public:
int operator()(const int& key) {
return key;
}
};
template<class K, class V, class KeyOfVal, class HF = dealInt>
class Iterator {
public:
HashBucket<K, V, KeyOfVal, HF>* m_hbpos;//直到桶在哪儿, 方便找上一个或下一个桶
HashBucketNode<V>* m_node;//具体节点
Iterator(HashBucket<K, V, KeyOfVal, HF>* hbpos = nullptr, HashBucketNode<V>* node = nullptr) :
m_hbpos(hbpos),
m_node(node)
{}
Iterator(const Iterator& it) :
m_hbpos(it.m_hbpos),
m_node(it.m_node)
{}
V& operator*() {
return m_node->m_val;
}
V* operator->() {
return &(m_node->m_val);
}
Iterator operator++() {
HashBucketNode<V>* t = m_node;
m_node = m_node->m_next;
if (!m_node) {//为空
size_t tmp = m_hbpos->HashFunc(KeyOfVal()(t->m_val)) + 1;//下一个桶的下标
for (; tmp < m_hbpos->capacity(); ++tmp) {
if (m_hbpos->m_table[tmp]) {
m_node = m_hbpos->m_table[tmp];
break;
}
}
}
return *this;
}
Iterator operator++(int) {
Iterator tmp = *this;
this->operator++();
return tmp;
}
bool operator==(const Iterator& data) const {
return m_node == data.m_node;
}
bool operator!=(const Iterator& data) const {
return m_node != data.m_node;
}
};
template<class K, class V, class KeyOfVal, class HF = dealInt>
class HashBucket {
vector<HashBucketNode<V>*> m_table;
size_t m_size;
static size_t s_m_primeTable[30];
size_t m_primePos;
template<class K, class V, class KeyOfVal, class HF>
friend class Iterator;
typedef Iterator<K, V, KeyOfVal, HF> Iterator;
size_t HashFunc(const K& key) {
return HF()(key) % capacity();
}
void checkCapacity() {
int oldcapacity = capacity();
if (oldcapacity == m_size) {//此时哈希冲突发生概率100%, 此时扩容
vector<HashBucketNode<V>*> tmp(s_m_primeTable[++m_primePos]);
m_table.swap(tmp);
m_size = 0;
for (auto e : tmp) {
for (; e; e = e->m_next) {
insert(e->m_val);
}
}
}
}
public:
HashBucket(const size_t capacity = s_m_primeTable[0]) :
m_table(capacity, nullptr),
m_size(0),
m_primePos(0)
{}
~HashBucket() {
clear();
}
size_t capacity() {
return m_table.size();
//return s_m_primeTable[m_primePos];
}
Iterator begin() {
for (size_t i = 0; i < capacity(); ++i) {
if (m_table[i]) {
return Iterator(this, m_table[i]);
}
}
return Iterator(this, nullptr);
}
Iterator end() {
return Iterator(this, nullptr);
}
pair<Iterator, bool> insert(const V& val) {
checkCapacity();//首先检查是否需要扩容
int k = HashFunc(KeyOfVal()(val));
HashBucketNode<V>* cur = m_table[k];
while (cur) {
if (KeyOfVal()(cur->m_val) == KeyOfVal()(val)) {
return pair<Iterator, bool>(Iterator(this, cur), false);
}
cur = cur->m_next;
}
cur = new HashBucketNode<V>(val);
cur->m_next = m_table[k];
m_table[k] = cur;
++m_size;
return pair<Iterator, bool>(Iterator(this, m_table[k]), true);//因为是头插, 所以返回头
}
Iterator find(const K& Keyval) {
int k = HashFunc(Keyval);
HashBucketNode<V>* cur;
for (cur = m_table[k]; cur; cur = cur->m_next) {
if (KeyOfVal()(cur->m_val) == Keyval) {
break;
}
}
return Iterator(this, cur);
}
pair<Iterator, bool> erase(const K& Keyval) {
Iterator f = find(Keyval);
int n = HashFunc(Keyval);
if (f.m_node) {
Iterator tmp = f;
if (m_table[n] == f.m_node) {
m_table[n] = m_table[n]->m_next;
}
else {
for (HashBucketNode<V>* cur = m_table[n]; cur->m_next; cur = cur->m_next) {
if (cur->m_next == f.m_node) {
cur->m_next = cur->m_next->m_next;
break;
}
}
}
++tmp;
delete f.m_node;
--m_size;
return pair<Iterator, bool>(tmp, true);
}
return pair<Iterator, bool>(Iterator(this, nullptr), false);
}
size_t size() {
return m_size;
}
bool empty() {
return 0 == m_size;
}
void clear() {
HashBucketNode<V>* tmp;
m_size = 0;
for (auto head : m_table) {
while (head) {
tmp = head;
head = head->m_next;
delete tmp;
}
}
for (auto& e : m_table) {
e = nullptr;
}
}
size_t bucket_count()const {//返回哈希桶中桶的个数
size_t count = 0;
for (auto& i : m_table) {
if (i != nullptr) {
++count;
}
}
return count;
}
size_t bucket_size(size_t n)const {//返回n号桶中的元素个数
size_t count = 0;
for (HashBucketNode<V>* i = m_table[n]; i; i = i->m_next) {
++count;
}
return count;
}
size_t bucket(const K& key) {//返回key对应几号桶
return HashFunc(key);
}
};
template<class K, class V, class KeyofValue, class HF>
size_t HashBucket<K, V, KeyofValue, HF>::s_m_primeTable[30] = {
11, 23, 47, 89, 179,
353, 709, 1409, 2819, 5639,
11273, 22531, 45061, 90121, 180233,
360457, 720899, 1441807, 2883593, 5767169,
11534351, 23068673, 46137359, 92274737, 184549429,
369098771, 738197549, 1476395029, 2952790016u, 4294967291u
};
再对HashBucket进行封装.
unordered_map.h
#include"HashBucket.h"
using namespace std;
namespace gh {
template <class K, class V, class HF = dealInt>
class unordered_map {
typedef pair<K, V> ValueType;
class KeyOfVal {
public:
const K& operator()(const ValueType& V) {
return V.first;
}
};
typedef HashBucket<K, ValueType, KeyOfVal, HF> HB;
HB m_data;
public:
typedef Iterator<K, ValueType, KeyOfVal, HF> iterator;
unordered_map() :m_data(HB()) {}
iterator begin() {
return m_data.begin();
}
iterator end() {
return m_data.end();
}
size_t size() {
return m_data.size();
}
bool empty() {
return m_data.empty();
}
void clear() {
m_data.clear();
}
pair<iterator, bool> insert(const ValueType& val) {
return m_data.insert(val);
}
pair<iterator, bool> erase(const K& Keyval) {
return m_data.erase(Keyval);
}
iterator find(const K& Keyval) {
return m_data.find(Keyval);
}
V& operator[](const K& key) {
/*pair<iterator, bool> tmp = insert(ValueType(key, V()));
iterator tmp2 = tmp.first;
return (*tmp2).second;*/
return (*insert(ValueType(key, V())).first).second;
}
const V& operator[](const K& key) const {
return (*insert(ValueType(key, V())).first).second;
}
size_t bucket_count()const {//返回哈希桶中桶的个数
return m_data.bucket_count();
}
size_t bucket_size(size_t n)const {//返回n号桶中的元素个数
return m_data.bucket_size(n);
}
size_t bucket(const K& key) {//返回key对应几号桶
return m_data.bucket(key);
}
};
};
测试入口, main.cpp
#include<iostream>
#include"unordered_map.h"
#include<unordered_map>
#include"HashBucket.h"
using namespace std;
class KeyofValueint {
public:
int operator()(int key) {
return key;
}
};
void test1() {
cout << "test1:\n";
HashBucket<int, int, KeyofValueint> hb;
hb.insert(6);
Iterator<int, int, KeyofValueint> q = hb.insert(9).first;
hb.insert(17);
hb.insert(16);
hb.insert(19);
hb.insert(27);
hb.insert(61);
hb.insert(98);
hb.insert(26);
hb.insert(63);
hb.insert(39);
hb.insert(28);
pair<Iterator<int, int, KeyofValueint>, bool> p = hb.insert(100);
pair<Iterator<int, int, KeyofValueint>, bool> p2 = hb.insert(100);
cout << *p.first << endl;
p = hb.erase(9);
p2 = hb.erase(10101);
cout << *p.first << endl << endl;
for (auto & e : hb) {
cout << e << endl;
}
}
void test2() {
cout << "\ntest2:\n";
gh::unordered_map<int, int> ump;
ump.insert(pair<int, int>(6, 10));
auto q = ump.insert(pair<int, int>(9, 30)).first;
ump.insert(pair<int, int>(17, 48));
ump.insert(pair<int, int>(16, 32));
ump.insert(pair<int, int>(19, 56));
ump.insert(pair<int, int>(27, 89));
ump.insert(pair<int, int>(61, 565));
ump.insert(pair<int, int>(98, 34));
ump.insert(pair<int, int>(26, 78));
ump.insert(pair<int, int>(63, 67));
ump.insert(pair<int, int>(39, 67));
ump.insert(pair<int, int>(28, 67));
pair<gh::unordered_map<int, int>::iterator, bool> p = ump.insert(pair<int, int>(100, 89));
pair<gh::unordered_map<int, int>::iterator, bool> p2 = ump.insert(pair<int, int>(100, 23));
cout << (*(p.first)).first << " " << (*(p.first)).second << endl;
p = ump.erase(9);
p2 = ump.erase(10101);
cout << (*(p.first)).first << " " << (*(p.first)).second << endl;
for (auto & e : ump) {
cout << e.first << " " << e.second << endl;
}
}
void test3() {
gh::unordered_map<int, int> ump;
ump.insert(pair<int, int>(6, 10));
cout << "\ntest3:\n";
cout << ump[6] << endl;
cout << ump[10] << endl;
}
void test4() {
cout << "\ntest4:\n";
unordered_map<int, int> ump;
ump.insert(pair<int, int>(6, 10));
ump.insert(pair<int, int>(17, 12));
ump.insert(pair<int, int>(28, 13));
auto q = ump.insert(pair<int, int>(9, 30)).first;
ump.insert(pair<int, int>(17, 48));
ump.insert(pair<int, int>(16, 32));
ump.insert(pair<int, int>(19, 56));
ump.insert(pair<int, int>(27, 89));
ump.insert(pair<int, int>(61, 565));
pair<unordered_map<int, int>::iterator, bool> p = ump.insert(pair<int, int>(100, 89));
pair<unordered_map<int, int>::iterator, bool> p2 = ump.insert(pair<int, int>(100, 23));
cout << (*(p.first)).first << " " << (*(p.first)).second << endl;
size_t a = ump.erase(6);
size_t b = ump.erase(17);
size_t c = ump.erase(10101);
cout << (*(p.first)).first << " " << (*(p.first)).second << endl;
for (auto & e : ump) {
cout << e.first << " " << e.second << endl;
}
}
void test5() {
cout << "\ntest5:\n";
gh::unordered_map<int, int> ump;
ump.insert(pair<int, int>(6, 10));
ump.insert(pair<int, int>(9, 30));
ump.insert(pair<int, int>(17, 48));
ump.insert(pair<int, int>(16, 32));
ump.insert(pair<int, int>(19, 56));
ump.insert(pair<int, int>(27, 89));
ump.insert(pair<int, int>(61, 565));
ump.insert(pair<int, int>(98, 34));
ump.insert(pair<int, int>(26, 78));
ump.insert(pair<int, int>(63, 67));
ump.insert(pair<int, int>(39, 67));
ump.insert(pair<int, int>(28, 67));
ump.insert(pair<int, int>(100, 89));
ump.insert(pair<int, int>(100, 23));
cout << ump.bucket_count() << endl;
cout << ump.bucket_size(3) << endl;
cout << ump.bucket(98) << endl;
}
int main() {
test1();
test2();
test3();
test4();
test5();
system("pause");
return 0;
}
结果如下: