set和map

set和map

关联式容器

在初阶阶段,我们已经接触过STL中的部分容器,比如:vector、list、deque、forward_list(C++11)等,这些容器统称为序列式容器,因为其底层为线性序列的数据结构,里面存储的是元素本身。那什么是关联式容器?它与序列式容器有什么区别?

关联式容器也是用来存储数据的,与序列式容器不同的是,其里面存储的是<key, value>结构的键值对,在数据检索时比序列式容器效率更高。

键值对

用来表示具有一一对应关系的一种结构,该结构中一般只包含两个成员变量key和value,key代表键值,value表示与key对应的信息。

cpp 复制代码
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)
{}
};

set

①set是一个树形结构的容器,它会按一定次序存储数据(默认是升序),并且set会对数据进行去重操作

②前面有讲过二叉搜索树,set也是一个树形结构的容器。 set中只有一个value参数(与map/multimap不同,map/multimap中存储的是真正的键值对<key, value>,set中只放

value,但在底层实际存放的是由<value, value>构成的键值对。)

③set在底层是用二叉搜索树(红黑树)实现的。

④查找效率比较高,时间复杂度为O(logN^2)

⑤set中的元素不允许修改

unordered_set

unordered_set是特殊的set,与set不同的点在于,unordered_set不会对数据进行排序。

set容器通过key访问单个元素的速度通常比unordered_set容器慢,但它们允许根据顺序对

子集进行直接迭代。

具体看操作(解析部分操作)

insert

set进行insert操作,传入一个键值对,由于set存放的是<value,value>,所以只需要传一个value就足够了。 (value就是key)

返回值: 是一个pair类型,里面有两个参数。 第一个表示key所在位置的迭代器,第二个bool值表示是否插入成功。 也就是说,如果我们插入的这个值在set中已经存在,那么就会插入失败,返回的就是已存在key的迭代器,以及false值。 如果插入的这个值在set中不存在,那么就会找到恰当的位置插入进去,返回新插入节点的迭代器,以及true值。

当然,也可以以set一部分的迭代器区间来插入

find

set进行find也是传入一个键值对,传入value值就可以(value就是key)。返回对应key的迭代器,如果key不存在就返回set::end

count

set使用count时,依旧传入键值对,也就是value(key),接着count会去set找,传入的值是否存在,如果存在那么会返回1(因为set中数据存在也只会存在一个),不存在就返回0。

使用一下set吧

cpp 复制代码
#include<set>  // 使用set需要包头文件
#include<iostream>
void TestSet1()
{
	// 用数组array中的元素构造set
	int array[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0, 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };  // 0到9之间的数字都有出现
	set<int> s(array, array + sizeof(array) / sizeof(array[0]));
	cout << s.size() << endl;
	// 正向打印set中的元素,从打印结果中可以看出:set可去重
	for (auto& e : s)
		cout << e << " ";
	cout << endl;
	// 使用迭代器逆向打印set中的元素
	for (auto it = s.rbegin(); it != s.rend(); ++it)
		cout << *it << " ";
	cout << endl;
	// set中值为3的元素出现了几次
	cout << s.count(3) << endl;
}

结果:(set具有去重的功能)

map

①map是关联容器,它按照特定的次序(按照key来比较)存储由键值key和值value组合而成的元素。(相较于set,map相当于kv模型,既有key又有value)。

②在map中,键值key通常用于排序和惟一地标识元素,而值value中存储与此键值key关联的内容。 键值key和值value的类型可能不同,并且在map的内部,key与value通过成员类型value_type绑定在一起,为其取别名称为pair

③在内部,map中的元素总是按照键值key进行比较排序的(同样默认是排升序)

map支持下标访问符,即在[]中放入key,就可以找到与key对应的value
但是set、multiset、multimap都是不支持[]访问的!!!

⑤map通常被实现为二叉搜索树(更准确的说:平衡二叉搜索树(红黑树))

⑥查找效率比较高,时间复杂度为O(logN^2)

⑦map中的key是唯一的,并且不能修改

key: 键值对中key的类型

T: 键值对中value的类型

Compare: 比较器的类型,map中的元素是按照key来比较的,缺省情况下按照小于来比

较,一般情况下(内置类型元素)该参数不需要传递,如果无法比较时(自定义类型),需要用户

自己显式传递比较规则(一般情况下按照函数指针或者仿函数来传递)

Alloc:通过空间配置器来申请底层空间,不需要用户传递,除非用户不想使用标准库提供的

空间配置器

注意:在使用map时,需要包含头文件。

map还有初始化列表的构造,具体的形式是这样的:

unordered_map

与unordered_set类似,就是在map的基础上多出一条规则,不对数据进行排序!

insert

在开始之前,我们先来看看map的几个参数

map的键值对为<key,value>,所以我们在传入参数时,是与set会有些许不同的。 我们传入参数可以用以下两种方法:

cpp 复制代码
// 第一种
    m.insert(pair<string, string>("peach", "桃子"));
// 第二种
    m.insert(make_pair("banana", "香蕉"));

第一种是直接构造,第二种是用到了一个 make_pair的函数

make_pair

使用make_pair,直接将map的key和value传进去就可以了,这个函数会自动帮我们创建并返回我们需要的pair类型。

operator[]

首先operator[]map独有的一个操作。set、multiset、multimap都是不具备的!

operator的底层原理是利用的insert操作,insert与前面set的操作类似,只不过insert里面传入的参数是<key,value>,多了一个参数而已

insert返回值,是一个pair<iterator,bool>,我们通过调用first,就可以拿到对应key的迭代器,再通过迭代器,就可以访问到这个key所对应的value(通过迭代器.second获得)。

我们模拟一下底层就是这样的:

我们结合上图看,operator[]的返回值是一个key对应的value值。 operator因为底层是调用的insert,所以不管这个值key存不存在,insert返回过来的pair<iterator,bool>,iterator都是存在的。 所以我们再通过这个iterator就可以找到key对应的value值了。

具体看一看operator[]可以用来做些什么吧!

cpp 复制代码
void test_map2()
{
	map<string, string> dict;
	dict.insert({ "string", "字符串" });

	// 插入(一般不会这么用)
	dict["right"];  //不存在,就插入

	// 插入+修改
	dict["left"] = "左边";  // 不存在,就进行插入,插入之后还要进行修改value值

	// "查找"
	cout << dict["string"] << endl; //因为返回的是value值,如果存在就打印得出来结果

	// 修改
	dict["right"] = "右边";  // 已经存在,这里的操作就是直接进行修改
}

我们再提及一个点,我们要注意map的operator[]和vector中operator[]作用是完全不同的。

vector中是以下标去访问数据,而map中实际是去插入,或者进行修改以及查找,并不是以下标去对应访问的!

其他的操作与set类似,这里就不再过多叙述!

那么接下来,我们还是来看看map操作的具体使用

map的具体使用

cpp 复制代码
#include <string>
#include <map>
void TestMap1()
{
	map<string, string> m;
	// 向map中插入元素的方式:
	// 1.将键值对<"peach","桃子">插入map中,用pair直接来构造键值对
	m.insert(pair<string, string>("peach", "桃子"));
	// 2.将键值对<"peach","桃子">插入map中,用make_pair函数来构造键值对
	m.insert(make_pair("banana", "香蕉"));
	cout << m.size() << endl;


	// 用迭代器去遍历map中的元素,可以得到一个按照key排序的序列
	for (auto& e : m)
		cout << e.first << "--->" << e.second << endl;
	cout << endl;
	// map中的键值对key一定是唯一的,如果key存在将插入失败
	auto ret = m.insert(make_pair("peach", "桃色"));
	if (ret.second)
		cout << "<peach, 桃色>不在map中, 已经插入" << endl;
	else
		cout << "键值为peach的元素已经存在:" << ret.first->first << "--->" << ret.first->second << " 插入失败" << endl;


	// 删除key为"apple"的元素
	m.erase("apple");
	if (1 == m.count("apple"))  //count的使用
		cout << "apple还在" << endl;
	else
		cout << "apple被吃了" << endl;
}
cpp 复制代码
void test_map3()
{
	string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜",
		"苹果", "香蕉", "苹果", "香蕉","苹果","草莓", "苹果","草莓" };
	map<string, int> countMap;  // map用来计数
	for (auto& e : arr)
	{
		auto it = countMap.find(e);  // find的使用  没找到就会返回map::end
		if (it != countMap.end())   //找到就进入循环,让对应key的value值++
		{
			it->second++;
		}
		else   // 如果这个key本身还不存在,那我们就进行插入
		{
			//countMap.insert({ e, 1 });
			countMap.insert(make_pair(e,1));
		}
	}

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

multiset和multimap

multiset中的元素不能修改。 multimap中的key也同样不能修改,但value可以修改。

set和map都会进行去重的操作,而multiset和multimap就是在set和map的基础上不对数据进行去重的操作。 其他的都是相同的,multimap和map还有一个不同的点在于multimap不能使用operator[]。
multiset是按照特定顺序存储元素的容器,其中元素是可以重复的。
multimap和map的唯一不同就是:map中的key是唯一的,而multimap中key是可以重复的。

同样用实例看看区别:

cpp 复制代码
void test_map3()
{
	string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜",
		"苹果", "香蕉", "苹果", "香蕉","苹果","草莓", "苹果","草莓" };
	map<string, int> countMap;
	for (auto& e : arr)
	{
		auto it = countMap.find(e);
		if (it != countMap.end())   //没找到就会返回npos
		{
			it->second++;
		}
		else
		{
			//countMap.insert({ e, 1 });
			countMap.insert(make_pair(e,1));
		}
	}

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

可以看到结果是没有进行去重操作的: 对应的水果数量都不为1

相关推荐
Ljubim.te17 分钟前
软件设计师——数据结构
数据结构·笔记
Eric.Lee202124 分钟前
数据集-目标检测系列- 螃蟹 检测数据集 crab >> DataBall
python·深度学习·算法·目标检测·计算机视觉·数据集·螃蟹检测
黑不溜秋的26 分钟前
C++ 语言特性29 - 协程介绍
开发语言·c++
一丝晨光30 分钟前
C++、Ruby和JavaScript
java·开发语言·javascript·c++·python·c·ruby
林辞忧34 分钟前
算法修炼之路之滑动窗口
算法
￴ㅤ￴￴ㅤ9527超级帅44 分钟前
LeetCode hot100---二叉树专题(C++语言)
c++·算法·leetcode
liuyang-neu1 小时前
力扣 简单 110.平衡二叉树
java·算法·leetcode·深度优先
penguin_bark1 小时前
LCR 068. 搜索插入位置
算法·leetcode·职场和发展
_GR1 小时前
每日OJ题_牛客_牛牛冲钻五_模拟_C++_Java
java·数据结构·c++·算法·动态规划
ROBIN__dyc1 小时前
表达式
算法