[C++] STL - introduction and use of unordered_map and unordered_set

Introduction and use of unordered_set and unordered_map

insert image description here

1. Unordered series of associative containers

In C++98, STL provides a series of associative containers whose bottom layer is a red-black tree structure, and the query efficiency can reach O(logN), that is, the height of the red-black tree needs to be compared in the worst case. When the tree When there are many nodes in , the query efficiency is not ideal. The best query is to find elements with a small number of comparisons. Therefore, in C++11, STL provides four unordered series of associative containers. The association between these four containers and the red-black tree structure Containers are basically used in the same way, but their underlying structures are different. Let's start to explain in turn.


Two, unordered_set

1. Introduction of unordered_set

  • 1. unordered_set is an associative container that does not store key values ​​in a specific order , which allows fast indexing to corresponding elements through key values.
  • 2. In unordered_set, the value of an element is also the key that uniquely identifies it .
  • 3. Internally, the elements in unordered_set are not sorted in any particular order. In order to find the specified key within a constant range, unordered_set puts the key values ​​of the same hash value in the same bucket.
  • 4. The unordered_set container is faster than set to access a single element by key, but it is usually less efficient in range iteration over a subset of elements .
  • 5. Its iterators are at least forward iterators . (unidirectional iterator)

2. The construction method of unordered_set

  • 1. Construct an empty container
unordered_set<int> s1;
  • 2. Copy construct a container
unordered_set<int> s2(s1);
  • 3. Use an iterator to construct a range
string str("string");
unordered_set<string> s3(str.begin(), str.end());

3. Function interface description of unordered_set

member function Function Description
begin Gets a forward iterator to the first element in the container
end Gets a forward iterator to the next position of the last element in the container
insert Insert the specified element
erase delete the specified element
find Find the specified element
size Get the number of elements in the container
clear empty container
swap Exchange data in two containers
count Get the number of elements of the specified element value in the container

Example:

void test_unordered_set2()
{
     
     
	unordered_set<int> us;
	//插入元素(去重)
	us.insert(1);
	us.insert(12);
	us.insert(6);
	us.insert(23);
	us.insert(3);
	us.insert(6);
	us.insert(0);
	//遍历容器方式一(范围for)
	for (auto e : us)
	{
     
     
		cout << e << " "; // 1 12 6 23 3 0
	}
	cout << endl; 
	//删除元素方式一
	us.erase(3);
	//删除元素方式二
	unordered_set<int>::iterator pos = us.find(1); //查找值为1的元素
	if (pos != us.end())
	{
     
     
		us.erase(pos);
	}
	//遍历容器方式二(迭代器遍历)
	unordered_set<int>::iterator it = us.begin();
	while (it != us.end())
	{
     
     
		cout << *it << " "; // 12 6 23 0
		it++;
	}
	cout << endl; 
	//容器中值为2的元素个数
	cout << us.count(2) << endl; //0
	//容器大小
	cout << us.size() << endl; //4
	//清空容器
	us.clear();
	//容器判空
	cout << us.empty() << endl; //1
	//交换两个容器的数据
	unordered_set<int> tmp{
     
     1, 2, 3, 4,};
	us.swap(tmp);
	for (auto e : us)
	{
     
     
		cout << e << " ";//1 2 3 4
	}
	cout << endl; 
}

4. Introduction and use of unordered_multiset

The underlying layers of unordered_multiset and unordered_set are both implemented with hash tables. The member functions provided are not significantly different from unordered_set. The only difference is that unordered_multiset allows key-value redundancy, that is, the key value can be the same, but unordered_set does not. The comparison is as follows:

image-20230412004509679

Since the unordered_multiset container allows key-value redundancy, the meanings of the member functions find and count in this container are also different from those in the unordered_set container:

member function count Function Description
unordered_set object Returns 1 if the element with the value key exists, and returns 0 if it does not exist
unordered_multiset object Returns the number of elements whose key value is key
member function ind Function Description
unordered_set object Returns the iterator position of the element whose value is key
unordered_multiset object Returns an iterator over the first element whose value is key in the underlying hash table

三、unordered_map

1. Introduction of unordered_map

  • 1. unordered_map is an associative container that stores <key, value> key-value pairs, which allows fast indexing to the corresponding value through keys .
  • 2. In unordered_map, the key value is usually used to uniquely identify the element, and the map value is an object whose content is associated with this key. Keys and map values ​​may be of different types .
  • 3. Internally, unordered_map does not sort <key, value> in any specific order. In order to find the value corresponding to the key within a constant range, unordered_map puts key-value pairs with the same hash value in the same bucket.
  • 4. The unordered_map container accesses a single element by key faster than map, but it is usually less efficient in range iteration over a subset of elements .
  • 5. unordered_map implements the direct access operator (operator[]), which allows direct access to the value using the key as a parameter .
  • 6. Its iterators are at least forward (one-way) iterators .

2. The construction method of unordered_map

  • 1. Construct an empty container:
unordered_map<string, int> mp1;
  • 2. Copy construct a container:
unordered_map<string, int> mp2(mp1);
  • 3. Construct a container using an iterator range:
unordered_map<string, int> mp2(mp1.begin(), mp1.end());

3. Function interface description of unordered_map

  • 1. The capacity of unordered_map
function declaration Features
bool empty() const Check if empty
size_t size() const Get the number of valid elements
  • 2. Iterator of unordered_map
function declaration Features
begin Return the iterator position of the first element of unordered_map
end Returns an iterator to the next position of the last element of unordered_map
cbegin Returns a const iterator to the first element of the unordered_map
a few Returns a const iterator to the position next to the last element of the unordered_map
  • 3. Element access of unordered_map
function declaration Features
operator[ ] Returns the value corresponding to the key, there is no default value

**Note:** For the overload of [ ], this function actually calls the insertion operation of the hash bucket, and uses the parameters key and V() to construct a default value and insert it into the underlying hash bucket. Whether the insertion is successful or not, There are the following instructions:

  1. If the key is not in the hash bucket, the insertion is successful and returns V() .
  2. If the key is already in the hash bucket, the insertion fails, and the value corresponding to the key is returned .

In fact, it is no different from the rules for overloading the [ ] operator of map.

  • 4. Unordered_map query
function declaration Features
iterator find(const K& key) Returns the position of the key in the hash bucket
size_t count(const K& key) Returns the number of key-value pairs whose key code is key in the hash bucket

**Note: **The key in unordered_map cannot be repeated, so the maximum return value of the count function is 1.

  • 5. The modification operation of unordered_map
function declaration Features
insert Insert key-value pairs into the container
erase Delete the key-value pair in the container
void clear Empty the number of valid elements in the container
void swap(unordered map&) Swaps elements in two containers
  • 6. Bucket operation of unordered_map
function declaration Features
size_t bucket_count() const Returns the total number of buckets in the hash bucket
size_t bucket_size(size_t n) const Returns the total number of valid elements in bucket n
size_t bucket(const K& key) Returns the bucket number where the element key is located

Example:

void test_unordered_map3()
{
     
     
	unordered_map<string, string> mp;
	/*insert插入*/
	//1:借助pair构造函数
	pair<string, string> kv("string", "字符串");
	mp.insert(kv);
	//2:借助pair构造匿名对象插入
	mp.insert(pair<string, string>("blue", "蓝色"));
	//3:调用make_pair函数模板插入
	mp.insert(make_pair("sky", "天空"));
	//4:使用[]运算符重载函数进行插入
	mp["快乐"] = "happy";
	//5:使用{}
	mp.insert({
     
      "左边", "left"});
	/*遍历*/
	//1:迭代器遍历
	unordered_map<string, string>::iterator it = mp.begin();
	while (it != mp.end())
	{
     
     
		cout << it->first << ":" << it->second << " ";
		it++;
	}
	cout << endl; // string:字符串 blue:蓝色 sky:天空 happy:快乐 left:左边
	//2:范围for
	for (auto e : mp)
	{
     
     
		cout << e.first << ":" << e.second << " ";
	}	
	cout << endl; // string:字符串 blue:蓝色 sky:天空 happy:快乐 left:左边
	/*删除*/
	//1:根据key删除
	mp.erase("string");
	//2:根据迭代器位置删除
	unordered_map<string, string>::iterator pos = mp.find("sky");
	if (pos != mp.end())
	{
     
     
		mp.erase(pos);
	}
	for (auto e : mp)
	{
     
     
		cout << e.first << ":" << e.second << " ";
	}
	cout << endl; // blue:蓝色 happy:快乐 左边:left
	/*修改*/
	//1:通过迭代器位置修改
	pos = mp.find("快乐");
	if (pos != mp.end())
	{
     
     
		pos->second = "ikun";
	}
	//2:通过[]修改
	mp["左边"] = "小黑子";
	for (auto e : mp)
	{
     
     
		cout << e.first << ":" << e.second << " ";
	}
	cout << endl; // blue:蓝色 快乐:ikun 左边:小黑子
	/*交换*/
	unordered_map<string, string> tmp{
     
      {
     
      "2023", "年"}, {
     
     "4", "月"}, {
     
     "3", "日"}};
	mp.swap(tmp);
	for (auto e : mp)
	{
     
     
		cout << e.first << e.second << " ";
	}
	cout << endl; //2023年4月3日
}

4. Introduction and use of unordered_multimap

The underlying layers of unordered_multimap and unordered_map are both implemented with hash tables. The member functions provided are not significantly different from those of unordered_map. The only difference is that unordered_multimap allows key-value redundancy, that is, the key value can be the same, but unordered_map does not. The comparison is as follows:

image-20230412010827097

unordered_multimap allows key-value redundancy, which leads to differences between its internal find and count functions and unordered_map, as follows:

member function count Function Description
unordered_map object Returns 1 if the element with the value key exists, and returns 0 if it does not exist
unordered_multimap object Returns the number of elements whose key value is key
member function find Function Description
unordered_map object Returns the iterator position of the element whose value is key
unordered_multimap object Returns an iterator over the first element whose value is key in the underlying hash table

4. The difference between map/set and unordered_map/unordered_set

The following will be compared from the following angles:

unordered_map / unordered_set map / set
underlying data structure Hashtable/Hashtable red black tree
Is it in order out of order orderly
search efficiency O(1) O(logN)
iterator type one-way iterator bidirectional iterator
head File #include<unordered_map>
#include<unordered_set>
#include < map >
#include < set >

5. Performance comparison of set/unordered_set

Because the difference between map and unordered_map containers is similar to the difference between set and unordered_set containers, so let's take set and unordered_set tests as examples to test the efficiency of insertion, deletion, and search

void test_speed()
{
     
     
	const size_t N = 1000000;

	unordered_set<int> us;
	set<int> s;

	vector<int> v;
	v.reserve(N);
	srand((unsigned int)time(0));
	for (size_t i = 0; i < N; ++i)
	{
     
     
		//v.push_back(rand());
		//v.push_back(rand()+i);
		v.push_back(i);
	}

	size_t begin1 = clock();
	for (auto e : v)
	{
     
     
		s.insert(e);
	}
	size_t end1 = clock();
	cout << "set insert:" << end1 - begin1 << endl;

	size_t begin2 = clock();
	for (auto e : v)
	{
     
     
		us.insert(e);
	}
	size_t end2 = clock();
	cout << "unordered_set insert:" << end2 - begin2 << endl;


	size_t begin3 = clock();
	for (auto e : v)
	{
     
     
		s.find(e);
	}
	size_t end3 = clock();
	cout << "set find:" << end3 - begin3 << endl;

	size_t begin4 = clock();
	for (auto e : v)
	{
     
     
		us.find(e);
	}
	size_t end4 = clock();
	cout << "unordered_set find:" << end4 - begin4 << endl;

	cout << s.size() << endl;
	cout << us.size() << endl;

	size_t begin5 = clock();
	for (auto e : v)
	{
     
     
		s.erase(e);
	}
	size_t end5 = clock();
	cout << "set erase:" << end5 - begin5 << endl;

	size_t begin6 = clock();
	for (auto e : v)
	{
     
     
		us.erase(e);
	}
	size_t end6 = clock();
	cout << "unordered_set erase:" << end6 - begin6 << endl;

}

image-20230407233602006

Summary : When the amount of test data is small, there is not much difference between the two. When the amount of data is large, it is better to use the unordered_ series.

Guess you like

Origin blog.csdn.net/m0_64224788/article/details/130118220