【C++】树型关联式容器:set、map、multiset、multimap


本篇介绍 STL 中树型结构(底层红黑树)的关联容器

补充:

  • unordered_set、unordered_map 的底层是哈希表
  • 他们的 key 需要满足:
    • 可哈希性:键类型必须能够被哈希函数处理,以便将键映射到哈希表中的特定索引位置。通常,C++ 标准库提供的哈希函数 std::hash 可以处理内置类型(例如整数、浮点数、指针等),而对于自定义类型,需要提供自定义的哈希函数或者使用 std::hash_combine 等方式生成哈希值。
    • 相等性比较:键类型必须支持相等性比较操作符 ==,以确保在哈希表中查找键时能够正确判断键是否相等。

0. 概述

🎯关联式容器

  1. 树型结构:set、map、multiset、multimap
    底层:红黑树
  2. 哈希结构:unordered_set、unordered_map
    底层:哈希表(更优)

    注意:使用时分别要包同名头文件,<set>、<map>、 …
树形结构–>红黑树存放元素关键功能经典应用
set<value, value>排序、去重、不可修改count 接口实现 “在不在” 问题:门禁系统、车库系统、检查一篇文章中单词拼写正确…
multiset排序、不去重、不可修改
map<key, value>排序、去重、key不可修改、[]操作符“通过一个值查找另一个值”:字典、统计频次、电话查快递、姓名+验证码查考试成绩…
multimap排序、不去重 、key不可修改

  set 和 map (multiset & multimap)是典型的 关联式容器,与序列式容器(vector、list、deque…)不同的是,其里面存储的是<key, value>结构的 键值对,在数据检索时比序列式容器效率更高。

  key 为键值,value 为 key 对应的信息。

// SGI中关于键值对的定义:

template <class T1, class T2>
struct pair
{
	typedef T1 first_type;
	typedef T2 second_type;
	T1 first;			// 通常来说,first 就是 key
	T2 second;			// second 就是 value
	
	pair()
		: first(T1()) 
		, second(T2())
	{}
	
	pair(const T1& a, const T2& b)
		: first(a)
		, second(b)
	{}
};

1. set

关键词: 排序 + 去重、不可修改、<value, value>

1.0 set 的模板参数说明

set的模板参数

  • T:set 中存放元素的类型,实际在底层存储 <value, value> 的键值对。
  • Compare:set 中元素默认按照小于来比较。
  • Alloc:set 中元素空间的管理方式。通过空间配置器来申请底层空间,不需要用户传递,除非用户不想使用 STL 标准库提供的空间配置器。

1.1 set 使用特点

  1. 其元素 value(同样也是键值 key,模板类型为 T)是 唯一 的。可以使用 set 去重
    元素可以插入和删除,但 不可以修改(const)。

  2. 使用set的迭代器遍历set中的元素,可以得到 有序 序列,默认按照小于来比较。

  3. set 在底层是用二叉搜索树 红黑树 实现的。

  4. set 中只放 value,但在底层实际存放的是由 <value, value> 构成的键值对。
    ( map/multimap 中存储的是真正的键值对 <key, value>)

  5. set中查找某个元素,时间复杂度为: l o g 2 n log_2 n log2n

1.2 set 使用情况

1. insert、iterator

//[set]
// 基本测试:insert,iterator
void test_set1()
{
	set<int> s1;
	s1.insert(2);
	s1.insert(3);
	s1.insert(5);
	s1.insert(1);
	s1.insert(5);
	s1.insert(8);
	s1.insert(8);
	s1.insert(0);

	set<int>::iterator it1 = s1.begin();
	while (it1 != s1.end())
	{
		//*it1 += 1;	// 搜索树不能随意修改,编译不通过
		cout << *it1 << " ";
		++it1;
	}
	cout << endl;		// 输出 0 1 2 3 5 8

	// 范围for
	for (auto e : s1)
	{
		e += 1;			// 这里 e 是一个 int,+1 操作没有在 s1 上实现
		cout << e << " ";
	}
	cout << endl;		// 输出 1 2 3 4 6 9

	for (auto e : s1)
	{
		cout << e << " ";
	}
	cout << endl;		// 输出 0 1 2 3 5 8
}

e 的修改没有影响到 s1
注意:auto e 的改变不影响 s1,auto& e 才会影响

2. find、count

// [set]
// 基本测试:find,count
void test_set2()
{
	// 排序 + 去重
	set<int> s1;
	s1.insert(2);
	s1.insert(3);
	s1.insert(5);
	s1.insert(1);
	s1.insert(5);
	s1.insert(8);
	s1.insert(8);
	s1.insert(0);

	int x;
	while (cin >> x)
	{
		/*auto ret = s1.find(x);
		if (ret != s1.end())
		{
			cout << "在" << endl;
		}
		else
		{
			cout << "不在" << endl;
		}*/

		if (s1.count(x))	// count可以返回个数,这里可以用于查找“在不在”的问题
		{
			cout << "在" << endl;
		}
		else
		{
			cout << "不在" << endl;
		}
	}
}

注意:count 接口返回个数,可以用于查找 “在不在” 问题


2. multiset

关键词: 排序(不去重)、不可修改、<value, value>

2.1 multiset 使用特点

  1. 其元素 value(同样也是键值 key,模板类型为 T),可以重复 ;元素可以插入和删除,但 不可以修改(const)。
  2. 与 set 的区别是,multiset 中的元素可以重复,set 是中 value 是唯一的
  3. 使用迭代器对multiset中的元素进行遍历,可以得到 有序 的序列
  4. mtltiset 的插入接口中只需要插入即可
  5. multiset 中查找某个元素,时间复杂度为: l o g 2 n log_2 n log2n
  6. 使用时与 set 包含的头文件相同

2.2 multiset 使用情况

其他接口与 set 相同,此处演示与 set 不同的功能:

// 测试 multiset
void test_set3()
{
	// 排序(不去重)
	multiset<int> s1;
	s1.insert(2);
	s1.insert(3);
	s1.insert(5);
	s1.insert(1);
	s1.insert(5);
	s1.insert(8);
	s1.insert(8);
	s1.insert(5);

	multiset<int>::iterator it1 = s1.begin();
	while (it1 != s1.end())
	{
		cout << *it1 << " ";
		++it1;
	}
	cout << endl;		// 输出 0 1 2 3 5 5 8 8

	// 如果有多个相同 key,find 中序的第一个 key
	auto ret = s1.find(8);
	while (ret != s1.end() && *ret == 8)
	{
		cout << *ret << " ";
		++ret;
	}
	cout << endl;		// 输出 8 8

	cout << s1.count(1) << endl;	// 输出 1
	cout << s1.count(8) << endl;	// 输出 2
}

注意:find 返回中序排序时,重复的第一个 key


3. map

关键词: 排序 + 去重、key不可修改、<key, value>、[ ]操作符

3.0 map 的模板参数说明

map的模板参数

  • key:键值对中 key 的类型
  • T:键值对中 value 的类型
  • Compare:比较器的类型,map 中的元素是按照 key 来比较的,缺省情况下按照小于来比较,一般情况下(内置类型元素)该参数不需要传递,如果无法比较时(自定义类型),需要用户自己显式传递比较规则(一般情况下按照函数指针或者仿函数来传递)
  • Alloc:通过空间配置器来申请底层空间,不需要用户传递,除非用户不想使用标准库提供的空间配置器

3.1 map 使用特点

  1. map 中的的元素是键值对 <key, value>
  2. map 中的 key 是唯一的,并且 不能修改
  3. 默认按照小于的方式对 key 进行比较(与 value 情况无关)
  4. map 中的元素如果用迭代器去遍历,可以得到一个 有序 的序列
  5. map的底层为平衡搜索树 红黑树,查找效率比较高 O ( l o g 2 N ) O(log_2 N) O(log2N)
  6. 支持 []操作符,operator[] 中实际进行 插入+查找

3.2 map 使用情况

1. 使用场景:字典

// 字典
void test_map1()
{
	map<string, string> dict;
	dict.insert(pair<string,string>("plug","插头"));	// 正常写成这样,构造一个pair的匿名对象进行插入
	dict.insert(make_pair("sort", "排序"));				// make_pair是一个函数模板,可以省略一步<显示参数>
	dict.insert(make_pair("string", "字符串"));
	dict.insert(make_pair("count", "计数"));

	//map<string, string>::iterator dit = dict.begin();
	auto dit = dict.begin();
	while (dit != dict.end())
	{
		//cout << (*dit).first << ":" << (*dit).second << endl;
		cout << dit->first << ":" << dit->second << endl;
		++dit;
	}
	/*
	输出:
		count:计数
		plug:插头
		sort:排序
		string:字符串
	*/
}

2. 无法 insert 重复 key

// 测试 insert 重复 key
void test_map2()
{
	map<string, string> dict;
	dict.insert(make_pair("sort", "排序"));
	dict.insert(make_pair("string", "字符串"));
	dict.insert(make_pair("count", "计数"));
	dict.insert(make_pair("string", "(字符串)"));	//插入失败,因为 key-value 模型,考虑的只有 key,key 已经有了不能插入了

	auto dit = dict.begin();
	while (dit != dict.end())
	{
		cout << dit->first << ":" << dit->second << endl;
		++dit;
	}
	/*
	输出:
		count:计数
		sort:排序
		string:字符串
	*/
}

3. map 的重要功能 [ ],实现 插入、修改、插入+修改、查找

// [] 实现:
// 1. 插入
// 2. 修改
// 3. 插入+修改
// 4. 查找
// 【方法一】
typedef pair value_type
value_type& operator[](const key_type& k)
{
	return(*((this->insert(make_pair(k, mapped_type()))).first)).second;
}

// 【方法二】
V& operator[](const K& key)
{
	pair<iterator, bool> ret = insert(make_pair(key, V()));	// make_pair里面的V()是一个匿名函数
	return ret.first->second;
}

4. 使用场景:水果个数(统计出现频次)

// 测试[] 水果个数
void test_map3()
{
	string arr[] = { "梨","葡萄","梨","桔子","西瓜","菠萝","菠萝","桔子" };
	map<string, int> countMap;
	//for (auto& e : arr)				// 不使用[]的写法....
	//{
	//	auto ret = countMap.find(e);
	//	if (ret == countMap.end())
	//	{
	//		countMap.insert(make_pair(e, 1));
	//	}
	//	else
	//	{
	//		ret->second++;
	//	}
	//}

	//for (auto& kv : countMap)
	//{
	//	cout << kv.first << ": " << kv.second << endl;
	//}

	for (auto& e : arr)
	{
		countMap[e]++;	// 添加k,修改v (没有key可以直接添加key并修改value,有key便修改它的value)
	}
	for (auto& kv : countMap)
	{
		cout << kv.first << ": " << kv.second << endl;
	}
}

5. [] 功能测试

// 测试[]
void test_map4()
{
	map<string, string> dict;
	dict.insert(make_pair("string", "字符串"));	// 插入
	dict.insert(make_pair("string", "(字符串)"));	// 插入失败

	dict["left"];	// 插入 k,v 是 string 默认构造生成的缺省值""
	dict["right"] = "右边";	// 插入 + 修改
	dict["string"] = "(字符串)";	// 修改
	
	cout << dict["string"] << endl;	// 查找,输出 (字符串)
	
	for (auto& kv : dict)
	{
		cout << kv.first << ": " << kv.second << endl;
	}
	/*
	输出:
		left:
		right: 右边
		string: (字符串)
	*/
}

4. multimap

关键词: 排序(不去重)、key不可修改、<key, value>、[ ]操作符

4.1 multimap 使用特点

  1. multimap 中的 key 是 可以重复
  2. multimap 中的元素默认将 key 按照小于来比较
  3. multimap 中没有重载 operator[] 操作
    (因为 [] 是一个 key 对应一个值,而 multi 允许多个相同 key)
  4. 使用时与 map 包含的头文件相同

4.2 multimap 使用情况

// 允许键值冗余(不去重)
// multimap 没有 []!!因为原来 map 的 k 和 v 是一对一,现在是一对多了,[] 也没必要存在了
void test_map5()
{
	multimap<string, string> mdict;
	mdict.insert(make_pair("sort", "排序"));
	mdict.insert(make_pair("string", "字符串"));
	mdict.insert(make_pair("count", "计数"));
	mdict.insert(make_pair("string", "(字符串)"));	// 能进了
	mdict.insert(make_pair("string", "字符串"));	// value相同也可以插入,因为看的是key,跟value无关的蛤

	for (auto& kv : mdict)
	{
		cout << kv.first << ":" << kv.second << endl;
	}
	/*
	输出:
		count:计数
		sort:排序
		string:字符串
		string:(字符串)
		string:字符串
	*/
}

# 相关题目

  1. 复制带随机指针的链表
  2. 前K个高频单词
  3. 两个数组的交集

👉🔗指路:详见作者另一篇博客


🥰如果本文对你有些帮助,请给个赞或收藏,你的支持是对作者大大莫大的鼓励!!(✿◡‿◡) 欢迎评论留言~~


  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值