C++STL~~unordered_set&unordered_map

unordered系列关联式容器

在C++98中,STL提供了底层为红黑树结构的一系列关联式容器,在查询时效率可达到O(logN),即最差情况下需要比较红黑树的高度次,当树中的节点非常多时,查询效率也不理想。最好的查询是,进行很少的比较次数就能够将元素找到,因此在C++11中,STL又提供了4个unordered系列的关联式容器,这四个容器与红黑树结构的关联式容器使用方式基本类似,只是其底层结构不同,本文中只对unordered_map和unordered_set进行介绍

一、unordered_set&unordered_map的概念

unordered_set

  1. unordered_set的声明如下,Key就是unordered_set底层关键字的类型
  2. unordered_set默认要求Key支持转换为整形,如果不支持或者想按自己的需求走可以自行实现支持将Key转成整形的仿函数传给第二个模板参数
  3. unordered_set默认要求Key支持比较相等,如果不支持或者想按自己的需求走可以自行实现支持将Key比较相等的仿函数传给第三个模板参数
  4. unordered_set底层存储数据的内存是从空间配置器申请的,如果需要可以自己实现内存池,传给第四个参数。
  5. ⼀般情况下,我们都不需要传后三个模板参数
  6. unordered_set底层是⽤哈希桶实现,增删查平均效率是,迭代器遍历不再有序,为了跟set区分,所以取名unordered_set。O(1)
  7. 前面部分已经学习了set容器的使用,set和unordered_set的功能高度相似,只是底层结构不同,有⼀些性能和使用的差异

unordered_map

  1. unordered_map是存储<key, value>键值对的关联式容器,其允许通过keys快速的索引到与其对应的value。
  2. 在unordered_map中,键值通常用于惟一地标识元素,而映射值是一个对象,其内容与此键关联。键和映射值的类型可能不同。
  3. 在内部,unordered_map没有对<kye, value>按照任何特定的顺序排序, 为了能在常数范围内找到key所对应的value,unordered_map将相同哈希值的键值对放在相同的桶中。
  4. unordered_map容器通过key访问单个元素要比map快,但它通常在遍历元素子集的范围迭代方面效率较低。
  5. unordered_maps实现了直接访问操作符(operator[]),它允许使用key作为参数直接访问value。
  6. 它的迭代器至少是前向迭代器。

二、unordered_set&unordered_map的使用

unordered_set的接口说明

  1. 在使用unordered_set之前,需要包含<unordered_set>头文件

  2. 定义空的unordered_set:
    可以简单地定义一个指定类型的unordered_set。例如,定义一个存储int类型元素的unordered_set:

  3. 使用insert方法插入单个元素:
    insert方法返回一个pair类型的值,其中first是指向插入元素的迭代器(如果元素已经存在,则指向已存在的元素),second是一个bool值,表示插入是否成功。

  4. 使用find方法查找元素:
    find方法返回一个迭代器,如果找到元素,则指向该元素;如果未找到,则返回end()迭代器。

  5. 使用erase方法删除单个元素:
    可以通过指定元素的值或者迭代器来删除元素。如果通过值删除,会删除第一个匹配的元素。

  6. 可以使用迭代器来遍历unordered_set中的元素。由于unordered_set中的元素是无序的,所以遍历的顺序是不确定的。

#include <iostream>
#include <unordered_set>

int main() 
{
	// unordered_set的使用
	// 定义并初始化一个unordered_set
	std::unordered_set<int> mySet = { 1, 2, 3 };

	// 插入元素
	mySet.insert(4);
	mySet.insert(5);

	// 查找元素
	auto itSet = mySet.find(3);
	if (itSet != mySet.end()) {
		std::cout << "在unordered_set中找到元素3" << std::endl;
	}
	else {
		std::cout << "在unordered_set中未找到元素3" << std::endl;
	}

	// 删除元素
	mySet.erase(2);

	// 遍历unordered_set
	std::cout << "unordered_set中的元素:";
	for (auto element : mySet) {
		std::cout << element << " ";
	}
	std::cout << std::endl;
}

unordered_map的接口说明

  1. 在使用unordered_map之前,需要包含<unordered_map>头文件

  2. 定义空的unordered_map:
    可以简单地定义一个指定类型的unordered_map。例如,定义一个存储int类型元素的unordered_map:

  3. 使用insert方法插入单个元素:
    insert方法返回一个pair类型的值。其中pair的first是一个迭代器,指向插入后的键 - 值对(如果键已经存在,则指向已存在的键 - 值对);second是一个bool值,表示插入是否成功。
    使用[]运算符插入或更新键 - 值对如果键不存在,[]运算符会插入一个新的键 - 值对,并返回对应的值的引用;如果键已经存在,则返回该键对应的值的引用,并且可以通过这个引用修改值

  4. 使用find方法查找元素:
    find方法返回一个迭代器,如果找到元素,则指向该元素;如果未找到,则返回end()迭代器。

  5. 使用erase方法删除单个元素:
    可以通过指定元素的值或者迭代器来删除元素。如果通过值删除,会删除第一个匹配的元素。

  6. 可以使用迭代器来遍历unordered_map中的元素。由于unordered_map中的元素是无序的,所以遍历的顺序是不确定的。

#include <iostream>
#include <unordered_set>
#include <unordered_map>

int main() {
    // unordered_map的使用
    // 定义并初始化一个unordered_map
    std::unordered_map<std::string, int> myMap = {{"apple", 1}, {"banana", 2}};

    // 插入键 - 值对
    myMap.insert({"cherry", 3});
    myMap["date"] = 4;

    // 查找键
    auto itMap = myMap.find("banana");
    if (itMap!= myMap.end()) {
        std::cout << "在unordered_map中找到键banana,对应的值为:" << itMap->second << std::endl;
    } else {
        std::cout << "在unordered_map中未找到键banana" << std::endl;
    }

    // 删除键 - 值对
    myMap.erase("apple");

    // 遍历unordered_map
    std::cout << "unordered_map中的键 - 值对:" << std::endl;
    for (auto& [key, value] : myMap) {
        std::cout << "键:" << key << ",值:" << value << std::endl;
    }

    return 0;
}

三、unordered_set&unordered_map的练习

一、在长度2n的数组中找出重复N次的元素
给你一个整数数组 nums ,该数组具有以下属性:
nums.length == 2 * n.
nums 包含 n + 1 个 不同的 元素
nums 中恰有一个元素重复 n 次
找出并返回重复了 n 次的那个元素。
示例 1:
输入:nums = [1,2,3,3]
输出:3
示例 2:
输入:nums = [2,1,2,5,3,2]
输出:2

class Solution {
public:
	int repeatedNTimes(vector<int>& A) {
		size_t N = A.size() / 2;
		// 用unordered_map统计每个元素出现的次数
		unordered_map<int, int> m;
		for (auto e : A)
			m[e]++;

		// 找出出现次数为N的元素
		for (auto& e : m)
		{
			if (e.second == N)
				return e.first;
		}
	}
};

二、两个数组的交集
给定两个数组 nums1 和 nums2 ,返回 它们的 交集。输出结果中的每个元素一定是 唯一 的。我们可以 不考虑输出结果的顺序 。
示例 1:
输入:nums1 = [1,2,2,1], nums2 = [2,2]
输出:[2]
示例 2:
输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出:[9,4]
解释:[4,9] 也是可通过的

class Solution {
public:
	vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {

		// 用unordered_set对nums1中的元素去重
		unordered_set<int> s1;
		for (auto e : nums1)
			s1.insert(e);
		// 用unordered_set对nums2中的元素去重
		unordered_set<int> s2;
		for (auto e : nums2)
			s2.insert(e);
		// 遍历s1,如果s1中某个元素在s2中出现过,即为交集
		vector<int> vRet;
		for (auto e : s1)
		{
			if (s2.find(e) != s2.end())
				vRet.push_back(e);
		}
		return vRet;
	}
};

四、unordered_set&set的差异

  1. 查看文档发现unordered_set的支持增删查且跟set的使用一模一样
  2. unordered_set和set的第一个差异是对key的要求不同,set要求Key支持小于比较,而unordered_set要求Key支持转成整形且支持等于比较,要理解unordered_set的这个两点要求得后续我们结合哈希表底层实现才能真正理解,也就是说这本质是哈希表的要求。
  3. unordered_set和set的第二个差异是迭代器的差异,set的iterator是双向迭代器,unordered_set是单向迭代器,其次set底层是红黑树,红黑树是二叉搜索树,走中序遍历是有序的,所以set迭代器遍历是有序+去重。而unordered_set底层是哈希表,迭代器遍历是无序+去重。
  4. unordered_set和set的第三个差异是性能的差异,整体而已大多数场景下,unordered_set的增删查改更快一些,因为红黑树增删查改效率是O(logN) ,而哈希表增删查平均效率是 O(1)
  5. unordered_multiset跟multiset功能完全类似,支持Key冗余。 unordered_multiset跟multiset的差异也是三个方面的差异,key的要求的差异,iterator及遍历顺序的差异,性能的差异。
pair<iterator,bool> insert ( const value_type& val );
size_type erase ( const key_type& k );
iterator find ( const key_type& k );

五、unordered_map&map的差异

  1. 查看文档发现unordered_map的支持增删查且跟map的使用一模一样
  2. unordered_map和map的第一个差异是对key的要求不同,map要求Key支持小于比较,而unordered_map要求Key支持转成整形且支持等于比较,要理解unordered_map的这个两点要求得后续我们结合哈希表底层实现才能真正理解,也就是说这本质是哈希表的要求。
  3. unordered_map和map的第二个差异是迭代器的差异,map的iterator是双向迭代器,unordered_map是单向迭代器,其次map底层是红黑树,红黑树是二叉搜索树,走中序遍历是有序的,所以map迭代器遍历是Key有序+去重。而unordered_map底层是哈希表,迭代器遍历是Key无序+去重。
  4. unordered_map和map的第三个差异是性能的差异,整体而言大多数场景下,unordered_map的增删查改更快⼀些,因为红黑树增删查改效率是O(logN),而哈希表增删查平均效率是 O(1)
  5. unordered_multimap跟multimap功能完全类似,支持Key冗余。 unordered_multimap跟multimap的差异也是三个方面的差异,key的要求的差异,iterator及遍历顺序的差异,性能的差异。
pair<iterator,bool> insert ( const value_type& val );
size_type erase ( const key_type& k );
iterator find ( const key_type& k );
mapped_type& operator[] ( const key_type& k );

六、总结

unordered_set
性能特点

  • 平均时间复杂度:插入、删除和查找操作的平均时间复杂度接近常数时间O(1)。这是因为通过哈希函数能够快速定位元素所在的桶,在桶内进行操作的时间复杂度通常较低。
    最坏情况时间复杂度:在最坏情况下,例如哈希函数设计不合理,导致大量元素哈希到同一个桶中,时间复杂度会退化为线性时间O(n)。不过在实际应用中,这种情况可以通过合理设计哈希函数和调整哈希表的参数来尽量避免。

与其他容器的比较

  • 与set的比较:set是基于红黑树实现的有序容器,元素插入和查找的时间复杂度为O(logn)。unordered_set在平均情况下插入和查找更快,但元素是无序的;而set可以按照特定的顺序(默认是从小到大)遍历元素。

unordered_map
性能特点

  • 平均时间复杂度:和unordered_set类似,插入、删除和查找操作的平均时间复杂度接近常数时间O(1)。其性能优势主要在于通过键的哈希值快速定位键 - 值对。
    最坏情况时间复杂度:同样在最坏情况下会退化为线性时间O(n),原因也是哈希冲突导致元素在桶内的线性查找时间增加。

与其他容器的比较

  • 与map的比较:map是基于红黑树的有序容器,存储键 - 值对,键的插入和查找时间复杂度为O(logn)。unordered_map在平均情况下插入和查找更快,但键 - 值对是无序的;map则可以按照键的顺序遍历键 - 值对。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值