C++ 关联式容器之unordered_map

STL 关联式容器

目录

STL 关联式容器

unordered_map

常用接口介绍

1.常见构造

3.容量操作

4. unordered_map的修改操作

5. 元素访问与查询

6. unordered_map的桶操作

常见接口实现


简介

说到 关联式容器,  需要区分是序列式容器, 例如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;
}

结果如下:

发布了223 篇原创文章 · 获赞 639 · 访问量 12万+

猜你喜欢

转载自blog.csdn.net/qq_41071068/article/details/104361724