深入STL之set_map初识

们可以看到set和map其实就是两个类模板,它们的底层就是不支持冗余的搜索二叉树,set就是key结构,map就是key/value结构。下面我们将通过学习它们的接口对set和map进入更深层次的理解。


一、序列式容器和关联式容器

在学习set和map相关的接口之前,先给大家介绍一下两个概念,序列式容器和关联式容器。那么二者究竟是怎么回事呢?又有什么区别呢?

  • 序列式容器:序列式容器指的是在逻辑结构上是线性的数据结构,它们中的两个数据没有紧密的关系,就比如交换一下两个元素的位置,这个容器依然是序列式容器。这样的容器有:vector,list,deque等。
  • 关联式容器:关联式容器指的是在逻辑结构上非线性的数据结构,它中存储的数据有着紧密的联系,如果交换一下它的存储结构就被破坏了,访问关联式容器的方法是通过关键字key来进行访问的,这样的容器有map/set系列,unordered_map/unorder_set系列,这篇文章我们来谈map/set。它们的底层是红黑树,一棵平衡二叉树。

二、pair类的介绍

2.1、pair的初识

要想学习map和set还是首先要了解一下pair类的,pair包在utility这个头文件下,它是一个类模板,我们可以把pair理解成键值对,它里面可以存放两个不同类型的参数,其中的第一个成员first对应我们存入的第一个参数,通常是key,second对应第二个参数通常是value。

我们可以发现pair是struct定义的类,而我们知道struct定义的类里面的成员默认是公有成员,也就是说如果我们需要在外界访问类的成员可以直接将类定义成struct类,这样避免我们还要搞友元函数这种额外的操作了。

其实map底层对key/value两个值的存储就是使用pair进行存储的,原因其实也是很简单就是,C++不支持多个返回值,如果分开存储的话对迭代器进行*操纵是没办法进行返回的,所以存成立pair类型。后面会进行详细的讲解的。

2.2、make_pair

make_pair是一个函数模板,传给它一个key和value它会返回一个对应的pair对象,由于C++11已经出现了多参数的构造函数也支持隐式类型转化,make_pair的使用频率已经很少了。

三、set和相关接口的介绍

set - C++ Reference

3.1、set的insert接口

  • 第一个函数是返回一个pair类型的对象,参数只有一个key值。
  • 第二个函数是返回一个迭代器,参数是给一个迭代器位置和一个要插入的key值。
  • 第三个函数返回空,是一个函数模板,需要给定一个迭代器区间进行插入。

下面将分别进行模拟:

void test_set_insert()
{
	set<int> s;
	// 返回值为一个pair类型
	// 如果插入失败的话pair中的second就是false
	s.insert(5);
	s.insert(2);
	s.insert(7);
	s.insert(6);
	s.insert(5);
	pair<set<int>::iterator, bool> p = s.insert(6);
	cout << p.second << endl;
	auto it = s.begin();
	while (it != s.end())
	{
		cout << *it << ' ';
		it++;
	}
	cout << endl;
}

void test_set_insert01()
{
	// 可以通过指定一个迭代器位置
	// 进行插入
	set<int> s;
	s.insert(2);
	s.insert(7);
	s.insert(6);
	s.insert(5);
	s.insert(s.begin(), 5);
	s.insert(++s.begin(), 3);
	
	auto it = s.begin();
	while (it != s.end())
	{
		cout << *it << ' ';
		it++;
	}
	cout << endl;
}

void test_set_insert02()
{
	array<int, 4> arr = { 10, 8, 9, 1 };
	set<int> s;
	s.insert(2);
	s.insert(7);
	s.insert(6);
	s.insert(5);
	s.insert(s.begin(), 5);
	s.insert(++s.begin(), 3);
	// 指定一段迭代器区间进行插入
	s.insert(arr.begin(), arr.end());
	// 隐式类型转换
	s.insert({ 1,2,3,4,5 });

	auto it = s.begin();
	while (it != s.end())
	{
		cout << *it << ' ';
		it++;
	}
	cout << endl;
}

3.2、set的find接口

void test_set_find()
{
	set<int> s;
	s.insert(2);
	s.insert(7);
	s.insert(6);
	s.insert(5);
	s.insert(s.begin(), 5);
	s.insert(++s.begin(), 3);

	int x = 0;
	while (cin >> x)
	{
		if (s.find(x) != s.end())
		{
			cout << x << ':' << "存在" << endl;
		}
		else {
			cout << x << ':' << "不存在" << endl;
		}
	}
}

我们需要进行考虑的是明明算法库中提供了一个find函数,为什么set容器还是实现一个自己的find成员函数,直接使用算法库中的函数不行吗?

下面我们来对比一下:

接着来看一下set的find的效率和算法库中find的效率谁高。我们知道算法库中的find的底层实现使用的是线性查找即暴力查找,时间复杂度为O(n),而set中成员函数find的查找是基于底层红黑树进行实现的,所以它的时间复杂度可以达到O(logn)。

3.3、set的erase接口

第一个是指定一个迭代器位置进行删除。

第二个是指定要删除的元素进行删除,返回值是size_t类型,这是为了要兼容multiset。

第三个是指定一段迭代器区间进行删除。

void test_set_erase()
{
	set<int> s;
	s.insert(2);
	s.insert(7);
	s.insert(6);
	s.insert(5);
	s.insert(s.begin(), 5);
	s.insert(++s.begin(), 3);

	s.erase(s.begin());
	s.erase(5);
	s.erase(s.begin(), --s.end());
	auto it = s.begin();
	while (it != s.end())
	{
		cout << *it << ' ';
		it++;
	}
	cout << endl;
}

3.4、set的count接口

count函数会统计容器中的所有的val元素,并且最终会返回val元素的个数,在set容器中,count返回值只有0和1两种情况如果返回值是0的话就证明没有这个元素,如果为1的话就证明该元素存在。也就是说在set容器中是可以使用count函数进行查询操作的。

void test_set_count()
{
	set<int> s;
	s.insert(2);
	s.insert(7);
	s.insert(6);
	s.insert(5);
	s.insert(s.begin(), 5);
	s.insert(++s.begin(), 3);

	int x = 0;
	while (cin >> x)
	{
		if (s.count(x))
		{
			cout << x << ':' << "存在" << endl;
		}
		else {
			cout << x << ':' << "不存在" << endl;
		}
	}
}

3.5、lower_bound和upper_bound

lower_bound函数返回的是目标元素的第一个元素,upper_bound返回的是目标元素的下一个元素。即二者加在一起使用的话返回的是左闭右开区间。C++中所有返回的区间都是左闭右开区间。

void test_multiset_lu_bound()
{
	multiset<int> s;
	s.insert({ 4, 2, 7, 2, 4, 8, 4, 5, 4, 9 });
	auto it1 = s.lower_bound(2);
	auto it2 = s.upper_bound(8);
	cout << *it1 << " and " << *it2 << endl;

	auto it = it1;
	while (it != it2)
	{
		cout << *it << ' ';
		it++;
	}
	cout << endl;
}

四、multiset和set的差异

multiset和set相比就是一个是支持冗余数据一个不支持冗余数据,想multiset容器中数据同样会进行排序但是不会进行去重操作了,find函数返回的是中序遍历的第一个元素的迭代器,erase是将所有的和目标元素值相同的元素删除然后会返回删除元素的个数,count是返回所有的和目标元素相同的元素的个数。

五、map和相关接口的介绍

map - C++ Reference

map和set相比之下insert,find,erase等接口是非常相似的这里不做过多的介绍,下面重点来介绍一下[]

5.1、map的[]

map容器是支持[]进行下标访问的。那么[]的底层实现机制是什么呢?我们来探究一下:

我们可以在官方文档中看到[]重载返回值是value,参数是key,也就是说我们传入一个key作为参数,他会返回给我们对应的value值。

我们看他的底层实现实际上使用的是insert这个接口,他会返回一个pair对象,pair对象的first是一个迭代器,对迭代器进行解引用再访问他的second成员即是value。

我们需要注意的就是如果map容器中没有目标元素,调用[]会先进行插入。

void test_map_index()
{
	string arr[] = { "苹果", "西瓜", "苹果", "苹果", "苹果","西瓜", "西瓜", "香蕉", "橘子", "樱桃" };
	map<string, int> countMap;
	for (const auto& e : arr)
	{
		countMap[e]++;
	}
	for (auto& e : countMap)
	{
		cout << e.first << ':' << e.second << endl;
	}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值