目录
一、前言
【unordered_map】
是STL
中的容器之一,不同于普通容器,它的查找速度极快,常用来存储各种经常被检索的数据,因为容器的底层是【哈希表】。除此之外,还可以借助其特殊的性质,解决部分难题。
二、预备知识
在正式学习 unordered_map 之前,首先要有一些预备知识,否则后面可能看不懂相关操作
💢关联式容器💢
在以往的 【
STL
容器】学习中,我们接触到的都是 序列式容器,比如string
、vector
、list
、deque
等,序列式容器的特点就是 底层为线性序列的数据结构,就比如list
,其中的节点是 线性存储 的,一个节点存储一个元素,其中存储的元素都可序,但未必有序
- 【关联式容器】 则比较特殊,其中存储的是
<key, value>
的 键值对,这就意味着可以按照 键值大小key
以某种特定的规则放置于适当的位置,关联式容器 没有首尾的概念,因此没有头插尾插等相关操作,本文中学习的unordered_map
就属于 关联式容器
注意: stack
、queue
等适配器也属于序列式容器,因为他们的底层是 deque
等容器
💢键值对💢
【键值对】是 一种用来表示具有一一对应关系的结构,该结构中一般只包含两个成员变量:
key
和value
,前者表示 键值,后者表示 实值 关联式容器的实现离不开【键值对】
因此在标准库中,专门提供了这种结构 pair
定义如下 :
//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) {}
#ifdef __STL_MEMBER_TEMPLATES
template <class U1, class U2>
pair(const pair<U1, U2>& p) : first(p.first), second(p.second) {}
#endif
};
pair
中的first
表示 键值,second
则表示 实值,在给 【关联式容器】 中插入数据时,可以构建pair
对象
比如下面就构建了一个 键值
key
为string
,实值value
为int
的匿名 键值对pair
对象
pair<string, int>("hehe", 123);
- 可以将此匿名对象传入 关联式容器 中,当然这样写未免过于麻烦了,于是库中设计了一个函数模板
make_pair
,可以根据传入的参数,去调用pair
构建对象并返回
make_pair("hehe", 123); //构建出的匿名对象与上面的一致
make_pair
的定义如下所示:
template <class T1,class T2>
pair<T1,T2> make_pair (T1 x, T2 y)
{
return ( pair<T1,T2>(x,y) );
}
- 该函数实际会被编译器优化为 内联函数,因此不会造成过多消耗,可以放心使用
💢哈希结构的关联式容器💢
所以在
C++
标准中,共提供了四种 哈希结构的关联式容器
unordered_set
unordered_multiset
unordered_map
unordered_multimap
关于 树形结构的关联式容器 将在 二叉搜索树 中学习
树型结构与哈希结构的关联式容器功能都是一模一样的,不过 哈希结构查找比树型结构快得多 -> O(1)
注:
STL
中选择的树型结构为 红黑树RB-Tree
- 树型结构中的元素 中序遍历 后有序,而哈希结构中的元素无序
三、unordered_map 详解
在C++98中,STL提供了底层为 红黑树结构 的一系列关联容器,在查询时效率可以达到 O(logN)。但是在较差的情况下 ,需要比较红黑树的高度次,当树中节点非常多的时候,查询效率也会不理想达到 O(N)
- 最好的查询是,进行很少的比较次数就能够将元素找到,因此在C++11中,STL又提供了 4 个 unordered系列的关联式容器。
- 接下来我们将会对,unordered_map 进行详细的介绍,其余的容器将会在后续的文章中讲述。
🔥unordered_map 的介绍
unordered_map 是存储 <key, value> 键值对 的关联式容器,其允许通过keys快速的索引到与其对应的value。
- 在 unordered_map 中,键值通常用于惟一地标识元素,而映射值是一个对象,其内容与此键关联。键和映射值的类型可能不同。
- 在内部 unordered_map 没有对 <kye, value> 按照任何特定的顺序排序, 为了能在常数范围内找到key所对应的value,unordered_map将相同哈希值的键值对放在相同的桶中。
- unordered_map容器通过key访问单个元素要比map快,但它通常在遍历元素子集的范围迭代方面效率较低。
- unordered_map实现了直接访问操作符(operator[ ]),它允许使用key作为参数直接访问value。
- 它的迭代器是单向迭代器。
🔥unordered_map 的构造
构造一个 unordered_ map 容器对象,根据使用的构造函数版本初始化其内容,我们主要掌握3种方式即可:
(1)构造一个某个类型的容器
unordered_map<string, int> um1; // 构造一个key为string类型,value为int类型的空容器
(2)拷贝构造某个类型的容器
unordered_map<string, int> um1({ {"apple", 1}, {"lemon", 2}});
unordered_map<string, int> um2(um1); // 拷贝构造同类型容器um1的复制品
(3)使用迭代器区间进行初始化构造
- 构造一个 unordered_map 对象,其中包含 【first ,last )中的每一个元素副本。
unordered_map<string, int> um1({ {"apple", 1}, {"lemon", 2}});
unordered_map<string, int> um3(um1.begin(), um1.end()); // 使用迭代器拷贝构造um1容器某段区间的复制品
简单的使用一下:
#include <iostream>
#include <vector>
#include <unordered_map>
using namespace std;
int main()
{
vector<pair<string, int>> arr = { make_pair("G", 71), make_pair("A", 65), make_pair("F", 70) };
unordered_map<string, int> m1; //创建一个空的 map
unordered_map<string, int> m2(arr.begin(), arr.end()); //创建包含数据的 map
cout << "m1: " << endl;
for (auto e : m1)
cout << e.first << " | " << e.second << endl;
cout << "========================" << endl;
cout << "m2: " << endl;
for (auto e : m2)
cout << e.first << " | " << e.second << endl;
return 0;
}
🔥unordered_map 的使用
unordered_map 的成员函数主要分为:迭代器,容量操作,修改操作。
- 需要注意的是,对于unordered_ map 而言,它存储的数据是无序的,并组它是一个单向迭代器。
- 我这里举几个用的例子,帮助大家理解
🥝 insert
在unordered_ map 中插入新元素。
- 每个元素只有在它不等同于容器中已经存在的任何其他元素时才会被插入,也就是说unordered_ map 中的每个元素是唯一的。
(1)直接使用 pair 直接来构建键值对
void test_unordered()
{
// 构造对象
unordered_map<string, double> um({ {"apple", 1.0}, {"lemon", 2.0} });
// 构造匿名对象插入新元素
um.insert(pair<string, double>("milk", 2.0));
um.insert(pair<string, double>("eggs", 6.5));
um.insert(pair<string, double>("sugar", 0.8));
// 遍历
for (auto e : um)
{
cout << e.first << ": " << e.second << endl;
}
}
(2)使用 make_pair 函数来构造键值对
void test_unordered()
{
// 构造对象
unordered_map<string, double> um({ {"apple", 1.0}, {"lemon", 2.0} });
// 调用make_pair函数模板插入新元素
um.insert(make_pair("milk", 2.0));
um.insert(make_pair("eggs", 6.5));
um.insert(make_pair("sugar", 0.8));
// 遍历
for (auto e : um)
{
cout << e.first << ": " << e.second << endl;
}
}
🍐 operator [ ]
返回 key 对应的 value :
- 如果 k 与容器中元素的键匹配,则函数返回对其映射值得引用。
- 如果 k 与容器中任何元素得键不匹配,该函数将插入一个具有该键得新元素,并返回对其映射值得引用。
(1)利用 [ ] 运算符重载函数进行插入
void test_unordered()
{
// 构造对象
unordered_map<string, double> um;
// 利用[]运算符重载函数进行插入
um["apple"] = 1.5;
um["lemon"] = 2.0;
um["sugar"] = 0.8;
// 遍历
for (auto e : um)
{
cout << e.first << ": " << e.second << endl;
}
}
(2)利用 [ ] 运算符重载函数进行修改
void test_unordered()
{
// 构造对象
unordered_map<string, double> um;
// 利用[]运算符重载函数进行插入
um["apple"] = 1.5;
um["lemon"] = 2.0;
um["sugar"] = 0.8;
// 利用[]运算符重载函数修改value
um["apple"] = 8.88;
// 遍历
for (auto e : um)
{
cout << e.first << ": " << e.second << endl;
}
}
总结:
- 若当前容器中已有键值为 key 的键值对,则返回该键值对的 value 的引用
- 若当前容器中没有键值为 key 的键值对,则先插入键值对 <key,value()>,然后再返回该键值对中的 value 的引用。
🍋 find
在容器中搜索键值等于 k 的元素,如果找到则返回该元素的迭代器,否则返回unordered_map::end (容器末端之前的元素)的迭代器。
void test_unordered()
{
// 构造对象
unordered_map<string, double> um;
// 利用[]运算符重载函数进行插入
um["mom"] = 5.4;
um["dad"] = 6.1;
um["bro"] = 5.9;
// 查找"dad"
auto pos = um.find("dad");
if (pos != um.end())
{
cout << pos->first << " is " << pos->second;
}
else
{
cout << "not found" << endl;
}
}
🍍 erase
从 unordered_map 容器中移除单个元素或一组元素
(1)从 unordered_map 容器中移除单个元素
void test_unordered()
{
// 构造对象
unordered_map<string, string> um;
// 填充容器
um["U.S."] = "Washington";
um["U.K."] = "London";
um["France"] = "Paris";
um["Russia"] = "Moscow";
um["China"] = "Beijing";
um["Germany"] = "Berlin";
um["Japan"] = "Tokyo";
// 直接删除"Japan"
um.erase("Japan");
// 遍历
for (auto e : um)
{
cout << e.first << ": " << e.second << endl;
}
}
(2)从容器中删除单个元素(搭配 find 使用)
void test_unordered()
{
// 构造对象
unordered_map<string, string> um;
// 填充容器
um["U.S."] = "Washington";
um["U.K."] = "London";
um["France"] = "Paris";
um["Russia"] = "Moscow";
um["China"] = "Beijing";
um["Germany"] = "Berlin";
um["Japan"] = "Tokyo";
// 查找"Russia"的位置
auto pos = um.find("Russia");
if (pos != um.end())
{
um.erase(pos);
cout << "delete success" << endl;
}
else
{
cout << "not found" << endl;
}
}
(3)从 map 容器中移除一组元素(【first,last))
void test_unordered()
{
// 构造对象
unordered_map<string, string> um;
// 填充容器
um["U.S."] = "Washington";
um["U.K."] = "London";
um["France"] = "Paris";
um["Russia"] = "Moscow";
um["China"] = "Beijing";
um["Germany"] = "Berlin";
um["Japan"] = "Tokyo";
// 查找"France"的位置
auto pos = um.find("France");
// 删除从"France"开始后面所有的元素
um.erase(pos, um.end());
// 遍历
for (auto e : um)
{
cout << e.first << ": " << e.second << endl;
}
}
🍌 size
返回 unordered_map 中的有效元素个数
void test_unordered()
{
// 构造对象
unordered_map<string, double> um = {
{"milk", 2.30},
{"potatoes", 1.90},
{"eggs", 0.40}
};
cout << "size: " << um.size() << endl;
// 插入重复元素
um["milk"] = 5.80;
cout << "size: " << um.size() << endl;
}
🍈 empty
检测 unordered_map 中的元素是否为空,是返回 true,否则返回 false
void test_unordered()
{
unordered_map<int, int> um1; // 构造空容器
unordered_map<int, int> um2 = { {1,10},{2,20},{3,30} }; // 构造非空容器
// um1是空容器,所以结果为真
cout << "um1 " << (um1.empty() ? "is empty" : "is not empty") << endl;
// um2不是空容器,所以结果为假
cout << "um2 " << (um2.empty() ? "is empty" : "is not empty") << endl;
}
🍉 swap
交换 unordered_map 容器中的元素
void test_unordered()
{
// 初始化um1和um2容器
unordered_map<string, string>
um1 = { {"Star Wars","G"},{"Alien","R"} },
um2 = { {"Inception","C"},{"Donnie Darko","R"} };
// 交换两个容器的内容
um1.swap(um2);
// 遍历um1
cout << "um1: ";
for (auto& x : um1)
{
cout << x.first << "-" << x.second << ", ";
}
cout << endl;
// 遍历um2
cout << "um2: ";
for (auto& x : um2)
{
cout << x.first << "-" << x.second << ", ";
}
}
🍓 count
再容器中搜索键为 k 的元素,并返回找到的元素数
- 因为 unordered_map 容器不允许重复键,这意味着如果容器中存在具有该键的元素,则函数实际返回 1,否则返回 0.
void test_unordered()
{
// 初始化容器
unordered_map<string, double> um = {
{"Burger",2.99},
{"Fries",1.99},
{"Soda",1.50}
};
// 在um中查找下列的数据
for (auto& x : { "Burger","Pizza","Salad","Soda" })
{
if (um.count(x) > 0)
std::cout << "um has " << x << std::endl;
else
std::cout << "um has no " << x << std::endl;
}
}
🔥map 和 unordered_map 的区别
map 和
unordered_map
是 C++ 标准模板库(STL)中的两种关联容器,它们都有存储唯一元素的特性,但它们在底层实现、元素存储顺序、查找和插入的性能上存在显著的区别。
- map:使用自平衡二叉搜索树实现,元素是有序的,适合需要有序存储的场景,操作的时间复杂度是 O(logn)。
unordered_map
:使用哈希表实现,元素是无序的,适合只关心元素存在性而不关心顺序的场景,操作的时间复杂度在理想情况下是 O(1)。
四、常考面试题
题目:复杂链表的复制
链接:LCR 154. 复杂链表的复制 - 力扣(LeetCode)
- 题目分析:复杂链表的深度拷贝,将题目给定的链表进行复制,这个链表比较特殊,不仅指向下一个节点,还随机指向空或其他节点
- 之前的解法是在两个节点新增节点,然后更改链接关系,比较麻烦,现在可以借助 unordered_map 建立映射关系,直接照着原链表更改链接关系即可
//剑指 Offer 35. 复杂链表的复制
//https://leetcode.cn/problems/fu-za-lian-biao-de-fu-zhi-lcof/
class Solution {
public:
Node* copyRandomList(Node* head) {
map<Node*, Node*> copyNodeMap; //存放原来的链表节点,及新的链表节点
Node* cur = head;
Node* copyHead = nullptr;
Node* copyTail = nullptr;
//先拷贝出链表
while(cur)
{
Node* copy = new Node(cur->val);
copyNodeMap[cur] = copy;
if(copyHead == nullptr)
{
copyHead = copyTail = copy;
}
else
{
copyTail->next = copy;
copyTail = copyTail->next;
}
cur = cur->next;
}
//初步拷贝已完成,进行随机指针的拷贝
cur = head;
while(cur)
{
//非常重要的一步
copyNodeMap[cur]->random = copyNodeMap[cur->random];
cur = cur->next;
}
return copyHead;
}
};
五、共勉
以下就是我对 【C++】unordered_map 容器 的理解,如果有不懂和发现问题的小伙伴,请在评论区说出来哦,同时我还会继续更新【C++】,请持续关注我哦!!!