[C++] Introduction and use of unordered_set and unordered_map

⭐Blog homepage: ️CS semi homepage
⭐Welcome to follow: like, favorite and leave a message
⭐ Column series: Advanced C++
⭐Code repository: Advanced C++
Home It is not easy for people to update. Your likes and attention are very important to me. Friends, please like and follow me. Your support is the biggest motivation for my creation. Friends are welcome to send private messages to ask questions. Family members, don’t forgetLike and collect + follow! ! !


1. Unordered series of associative containers

In C++98, STL providesa series of associative containers with a red-black tree structure at the bottom layer, which is efficient in querying Achievable l o g 2 N log_2 N log2N, that is, in the worst case, the height of the red-black tree needs to be compared. When there are many nodes in the tree, the query efficiency is not ideal. The best query is that can find the element with a small number of comparisons, so in C++11, STL provides 4 more The unordered series of associative containers, these four containers are basically similar to the associative containers of red-black tree structure, except that their underlying structures are different.

二、unordered_map and unordered_multimap

1. Introduction to unordered_map

  1. unordered_map is an associative container that stores <key, value> key-value pairs, which allows fast indexing to the corresponding keys. value.
  2. In unordered_map, the key value is usually used to uniquely identify the element, while the map value is an object whose content is associated with this key. Keys and mapped values ​​may be of different types.
  3. Internally,unordered_map does not sort <kye, value> in any specific order, in order to find the key within a constant range Corresponding value, unordered_map puts key-value pairs with the same hash value in the same bucket.
  4. unordered_map containerAccessing a single element by key is faster than map, but it usually traverses the elements Inefficient range iteration of subsets.
  5. unordered_maps implements the direct access operator (operator[]), which allows direct access to value using key as parameter.
  6. Its iterator is at leasta forward iterator.

2. Use of unordered_map

(1) Definition

It is defined as follows:

void test_unordered_map1()
{
    
    
	// 构造一个空的key为int,value为double的unordered_map
	unordered_map<int, double> um1;

	// 给um1赋上值
	um1.insert(make_pair(1, 1.1));
	um1.insert(make_pair(2, 2.2));
	um1.insert(make_pair(3, 3.3));
	um1.insert(make_pair(4, 4.4));

	// 拷贝构造
	unordered_map<int, double> um2(um1);

	// 迭代器区间拷贝um2的一段
	unordered_map<int, double> um3(um2.begin(), um2.end());

	// for循环打印一下um3,um3没问题则um1和um2都没问题
	for (auto& e : um3)
	{
    
    
		cout << e.first<< "=>" << e.second << " ";
	}
	cout << endl;
}

(2) Interface usage

member function Function
insert Insert key-value pair
erase Delete the key-value pair with the value of the specified key
size Get the number of elements in the container
find Find key-value pairs with specified key value
empty Determine whether the container is empty
clear Clear current container
swap Exchange data in two containers
count Get the number of elements with the specified key value in the container
[] The [] function of operator overloading is very powerful, including insertion, modification, search, etc.
begin() Get the forward iterator of the first element in the container
end() Gets the forward iterator of the next element of the last element in the container

Key points[]:
1. If there is already a key-value pair with the key value key in the current container, thenReturns a reference to value for this key.
2. If there is no key-value pair with the key value key in the current container, firstInsert key-value pair <key, value()>, and then returns a reference to the value in the key-value pair.

Let’s look directly at the code, regarding all the above code operations:

void test_unordered_map2()
{
    
    
	// 构造一个空的key为int,value为string的unordered_map
	unordered_map<int, string> um1;
	
	// 插入方法一:构造匿名对象插入
	um1.insert(pair<int, string>(1, "111"));
	um1.insert(pair<int, string>(2, "222"));
	um1.insert(pair<int, string>(3, "333"));

	// 插入方法二:调用make_pair插入
	um1.insert(make_pair(4, "444"));
	um1.insert(make_pair(5, "555"));
	um1.insert(make_pair(6, "666"));

	// 插入方法三:用operator[]
	um1[7] = "777";
	um1[8] = "888";
	um1[9] = "999";
	um1[10] = "000";

	// 遍历方式一:利用迭代器进行遍历打印
	//unordered_map<int, string>::iterator it = um1.begin();
	auto it = um1.begin();
	while (it != um1.end())
	{
    
    
		cout << (*it).first << "=>" << (*it).second << " ";
		++it;
	}
	cout << endl; // 1=>111 2=>222 3=>333 4=>444 5=>555 6=>666 7=>777 8=>888 9=>999 10=>000

	// 遍历方法二:利用for循环进行遍历打印
	for (auto& e : um1)
	{
    
    
		cout << e.first<< "=>" << e.second << " ";
	}
	cout << endl; // 1=>111 2=>222 3=>333 4=>444 5=>555 6=>666 7=>777 8=>888 9=>999 10=>000

	// 删除操作1:根据键值对key删除
	um1.erase(5);
	// 删除操作2:根据迭代器进行删除
	unordered_map<int, string>::iterator rit = um1.find(7); // 顺带使用键值对key就可以用find函数了
	if (rit != um1.end())
	{
    
    
		um1.erase(rit);
	}
	// 遍历打印一下,用for循环方便快捷一点
	for (auto& e : um1)
	{
    
    
		cout << e.first << "=>" << e.second << " ";
	}
	cout << endl; // 1=>111 2=>222 3=>333 4=>444 6=>666 8=>888 9=>999 10=>000

	// 修改键值对:通过find获得迭代器进行修改
	auto pos = um1.find(1);
	if (pos != um1.end())
	{
    
    
		pos->second = "11/11";
	}

	// 修改键值对:通过operator[]运算符重载进行修改
	um1[2] = "22/22";

	// 打印一下
	for (auto& e : um1)
	{
    
    
		cout << e.first << "=>" << e.second << " ";
	}
	cout << endl; // 1=>11/11 2=>22/22 3=>333 4=>444 6=>666 8=>888 9=>999 10=>000

	// 判空
	cout << um1.empty() << endl; // 0 -- 不空

	// 计算容器的大小
	cout << um1.size() << endl; // 8个

	// 计算容器中键值对的大小
	cout << um1.count(3) << endl; // 1

	// 交换两容器中的数据
	unordered_map<int, string> tmp{
    
    {
    
    11, "123"}, {
    
     22, "345" }};
	um1.swap(tmp);
	for (auto& e : tmp)
	{
    
    
		cout << "tmp=>" << " ";
		cout << e.first << "=>" << e.second << " ";
	}
	cout << endl; // tmp=> 1=>11/11 2=>22/22 3=>333 4=>444 6=>666 8=>888 9=>999 10=>000

	for (auto& e : um1)
	{
    
    
		cout << "um1=>" << " ";
		cout << e.first << "=>" << e.second << " ";
	}
	cout << endl; // um1=> 11=>123 22=>345

	// 清空
	um1.clear();
	for (auto& e : um1)
	{
    
    
		cout << e.first << "=>" << e.second << " ";
	}
	cout << endl;
}

3、unordered_multimap

This container is basically the same as unordered_map. The difference between the two is that multimap allows the redundancy of key-value pairs, that is, it allows key and value to have different values.

void test_unordered_map3()
{
    
    
	unordered_multimap<int, string> ummp1;
	ummp1.insert(make_pair(2023, "yes"));
	ummp1.insert(make_pair(2023, "no"));
	ummp1.insert(make_pair(2023, "before"));
	ummp1.insert(make_pair(2023, "now"));
	for (auto& e : ummp1)
	{
    
    
		cout << e.first << "=>" << e.second << " ";
	}
	cout << endl;
}

There are three more differences:

1. The find function of unordered_map and unordered_multimap:

find function Function
unordered_map Returns an iterator of key-value pairs whose key value is key
unordered_multimap Returns an iterator over the first key-value pair found in the underlying hash table with key value key

2. Count function function

count function Function
unordered_map If the key-value pair whose key value is key exists, 1 is returned, and if it does not exist, 0 is returned (the find member function can be replaced)
unordered_multimap Returns the number of key-value pairs whose key value is key (find member function cannot be replaced)

3. operator[] function function

We do not have this operator[] overload in unordered_multimap, because this container can be redundant, so we are not sure which one we are looking for, which will cause a lot of errors, so our unordered_multimap does not have operator[] of!

二、unordered_set and unordered_multiset

1. Introduction to unordered_set

1. unordered_set is an associative container that stores key values ​​in no specific order, which allows quick indexing to the 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 specific order. In order to find the specified key within a constant range, unordered_set places key values ​​​​with the same hash value in the same in the barrel.
4. The unordered_set container accesses a single element through key faster than set, but it is usually less efficient in range iteration over a subset of elements.
5. Its iterator is at least a forward iterator.

2. Use of unordered_set

(1) Definition

void test_unordered_set1()
{
    
    
	// 构造一个空壳的us1的unordered_set的容器
	unordered_set<int> us1;

	// 插入几个值
	us1.insert(1);
	us1.insert(2);
	us1.insert(3);
	us1.insert(4);

	// 拷贝构造
	unordered_set<int> us2(us1);

	// 迭代器区间构造
	unordered_set<int> us3(us2.begin(), us2.end());

	// for循环打印一下
	for (auto& e : us3)
	{
    
    
		cout << e << " ";
	}
	cout << endl;
}

(2) Interface usage

member function Function
insert Insert specified element
erase Delete specified element
size Get the number of elements in the container
find Find the specified element
empty Determine whether the container is empty
clear Clear current container
swap Exchange data in two containers
count Get the number of specified elements in the container
[] The [] function of operator overloading is very powerful, including insertion, modification, search, etc.
begin() Get the forward iterator of the first element in the container
end() Gets the forward iterator of the next element of the last element in the container
void test_unordered_set2()
{
    
    
	// 先构造一个空的容器
	unordered_set<int> us1;

	// 插入元素(只有这一种插入法)
	us1.insert(1);
	us1.insert(2);
	us1.insert(3);
	us1.insert(1);
	us1.insert(4);
	us1.insert(5);

	// 遍历容器第一种方法:迭代器遍历
	unordered_set<int>::iterator it = us1.begin();
	while (it != us1.end())
	{
    
    
		cout << *it << " ";
		++it;
	}
	cout << endl; // 1 2 3 4 5

	// 遍历容器第二种方法:for循环
	for (auto& e : us1)
	{
    
    
		cout << e << " ";
	}
	cout << endl; // 1 2 3 4 5

	// 删除元素的方式一:直接找到值进行删除
	us1.erase(1);

	// 删除元素的方法二:利用迭代器进行删除
	unordered_set<int>::iterator pos = us1.find(2);
	if (pos != us1.end())
	{
    
    
		us1.erase(pos);
	}

	// 打印一下
	for (auto& e : us1)
	{
    
    
		cout << e << " ";
	}
	cout << endl; // 3 4 5

	// 判断容器是否为空
	cout << us1.empty() << endl; // 0

	// 获取值为3的个数
	cout << us1.count(3) << endl; // 1

	// 查看当前容器的容量
	cout << us1.size() << endl; // 3

	// 交换数据
	unordered_set<int> tmp{
    
    99, 88, 77, 66};
	us1.swap(tmp);

	// 打印一下
	for (auto& e : us1)
	{
    
    
		cout << e << " ";
	}
	cout << endl; // 99 88 77 66 

	// 打印一下
	for (auto& e : tmp)
	{
    
    
		cout << e << " ";
	}
	cout << endl; // 3 4 5

	// 清空
	us1.clear();

	// 打印一下
	for (auto& e : us1)
	{
    
    
		cout << e << " ";
	}
	cout << endl;  //   
}

3、unordered_multiset

The roughly implemented function is the same as unordered_map, but the only difference is that this multi-functional set allows values ​​to be repeated!

void test_unordered_set3()
{
    
    
	unordered_multiset<int> ums1;
	ums1.insert(1);
	ums1.insert(2);
	ums1.insert(4);
	ums1.insert(3);
	ums1.insert(1);
	ums1.insert(5);
	ums1.insert(2);
	ums1.insert(7);

	for (auto& e : ums1)
	{
    
    
		cout << e << " ";
	}
	cout << endl;  // 1 1 2 2 3 4 5 7
}

Compared with the ordinary set, the count function of this multi-functional set returns the number of items. The count function of the ordinary set returns 1 if it exists and 0 if it does not exist.

Compared with the ordinary set, the find function of this multi-functional set returns the iterator of the first element found with the key value val in the underlying hash table, while the ordinary set returns a simple key.

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

Insert image description here

Let’s do some performance testing:

#include <iostream>
#include <set>
#include <unordered_set>
#include <time.h>
using namespace std;

int main()
{
    
    
	int N = 1000;
	vector<int> v;
	v.reserve(N);
	srand((unsigned int)time(NULL));
	//随机生成N个数字
	for (int i = 0; i < N; i++)
	{
    
    
		v.push_back(rand());
	}

	//将这N个数插入set容器
	set<int> s;
	clock_t begin1 = clock();
	for (auto e : v)
	{
    
    
		s.insert(e);
	}
	clock_t end1 = clock();

	//将这N个数插入unordered_set容器
	unordered_set<int> us;
	clock_t begin2 = clock();
	for (auto e : v)
	{
    
    
		us.insert(e);
	}
	clock_t end2 = clock();

	//分别输出插入set容器和unordered_set容器所用的时间
	cout << "set insert: " << end1 - begin1 << endl;
	cout << "unordered_set insert: " << end2 - begin2 << endl;

	//在set容器中查找这N个数
	clock_t begin3 = clock();
	for (auto e : v)
	{
    
    
		s.find(e);
	}
	clock_t end3 = clock();

	//在unordered_set容器中查找这N个数
	clock_t begin4 = clock();
	for (auto e : v)
	{
    
    
		us.find(e);
	}
	clock_t end4 = clock();

	//分别输出在set容器和unordered_set容器中查找这N个数所用的时间
	cout << "set find: " << end3 - begin3 << endl;
	cout << "unordered_set find: " << end4 - begin4 << endl;

	//将这N个数从set容器中删除
	clock_t begin5 = clock();
	for (auto e : v)
	{
    
    
		s.erase(e);
	}
	clock_t end5 = clock();

	//将这N个数从unordered_set容器中删除
	clock_t begin6 = clock();
	for (auto e : v)
	{
    
    
		us.erase(e);
	}
	clock_t end6 = clock();

	//分别输出将这N个数从set容器和unordered_set容器中删除所用的时间
	cout << "set erase: " << end5 - begin5 << endl;
	cout << "unordered_set erase: " << end6 - begin6 << endl;
	return 0;
}

Insert image description here

Guess you like

Origin blog.csdn.net/m0_70088010/article/details/133468059