Hello 大家好。今天,我们要学习的是STL的一种容器,set(集合)。在学习set之前,我们先来了解一下STL中容器的两种类型
1. 序列式容器和关联式容器
前面我们已经接触过STL中的部分容器如:string、vector、list、deque、array、forward_list等,这
些容器统称为序列式容器,因为逻辑结构为线性序列的数据结构,两个位置存储的值之间一般没有紧
密的关联关系,比如交换一下,他依旧是序列式容器。序列式容器中的元素是按他们在容器中的存储位
置来顺序保存和访问的。
关联式容器也是用来存储数据的,与序列式容器不同的是,关联式容器逻辑结构通常是非线性结构,
两个位置有紧密的关联关系,交换一下,他的存储结构就被破坏了。顺序容器中的元素是按关键字来
保存和访问的。关联式容器有map/set系列和unordered_map/unordered_set系列。
本节讲解的set底层是红黑树,红黑树是一颗平衡二叉搜索树(二叉搜索树的进阶,左右子树高度之差的绝对值不超过1)。set是key搜索场景(就是只查找key是否存在)的结构,
2 set和multiset参考文档
https://legacy.cplusplus.com/reference/set/
set和multiset区别在于是否支持冗余,set不支持冗余,multiset支持
set和multiset头文件都是
在实际使用中,我们主要使用set,因此在本篇文章中我们也主要介绍set
3.set类的介绍
• set的声明如下,T就是set底层关键字的类型
• set默认要求T支持小于比较,如果不支持或者想按自己的需求走可以自行实现仿函数传给第二个模
版参数
• set底层存储数据的内存是从空间配置器申请的,如果需要可以自己实现内存池,传给第三个参
数。
• 一般情况下,我们都不需要传后两个模版参数。
• set底层是用红黑树实现,增删查效率是O(logN) ,迭代器遍历是走的搜索树的中序,所以是有序
的。
template < class T, // set::key_type/value_type
class Compare = less<T>, // set::key_compare/value_compare
class Alloc = allocator<T> // set::allocator_type
> class set;
set和multiset默认的排序都是升序,set相当于是排序+去重,multiset是排序
4.set的构造和迭代器
set的构造我们关注以下几个接口即可。
set的支持正向和反向迭代遍历,遍历默认按升序顺序,因为底层是二叉搜索树,迭代器遍历走的中
序;支持迭代器就意味着支持范围for,set的iterator和const_iterator都不支持迭代器修改数据,因为修改
关键字数据,会破坏底层搜索树的结构。
// empty (1) 无参默认构造
explicit set (const key_compare& comp = key_compare(),
const allocator_type& alloc = allocator_type());
// range (2) 迭代器区间构造
template <class InputIterator>
set (InputIterator first, InputIterator last,
const key_compare& comp = key_compare(),
const allocator_type& = allocator_type());
// copy (3) 拷贝构造
set (const set& x);
// initializer list (5) initializer 列表构造
set (initializer_list<value_type> il,
const key_compare& comp = key_compare(),
const allocator_type& alloc = allocator_type());
// 迭代器是一个双向迭代器
iterator -> a bidirectional iterator to const value_type
// 正向迭代器
iterator begin();
iterator end();
// 反向迭代器
reverse_iterator rbegin();
reverse_iterator rend();
#include<iostream>
#include<set>
using namespace std;
int main()
{
// empty (1) 无参默认构造
set<int> first;
// range (2) 迭代器区间构造
//默认排序为升序
int myints[] = { 10,20,90,40,60,50,15 };
set<int> second(myints, myints + 7);
set<int> third(second.begin(), second.end());
// copy (3) 拷贝构造
set<int> fouth(second);
// initializer list (5) initializer 列表构造
//降序排列
set<int,greater<int>> five{ 1,2,7,9,4,5 };
//标准写法
//set<int>::iterator it = second.begin();
//建议使用auto使用auto自动识别变量类型
//正向迭代器
auto it = second.begin();
//反向迭代器
auto rit = second.rbegin();
while (it != second.end())
{
cout << *it << " ";
it++;
}
cout << endl;
while (rit != second.rend())
{
cout << *rit << " ";
rit++;
}
return 0;
}
5.set的增删查
set的增删查关注以下几个接口即可:
// 单个数据插入,如果已经存在则插入失败
pair<iterator,bool> insert (const value_type& val);
// 列表插入,已经在容器中存在的值不会插入
void insert (initializer_list<value_type> il);
// 迭代器区间插入,已经在容器中存在的值不会插入
template <class InputIterator>
void insert (InputIterator first, InputIterator last);
// 查找val,返回val所在的迭代器,没有找到返回end()
iterator find (const value_type& val);
// 查找val,返回Val的个数
size_type count (const value_type& val) const;
// 删除一个迭代器位置的值
iterator erase (const_iterator position);
// 删除val,val不存在返回0,存在返回1
size_type erase (const value_type& val);
// 删除一段迭代器区间的值
iterator erase (const_iterator first, const_iterator last);
// 返回大于等val位置的迭代器
iterator lower_bound (const value_type& val) const;
// 返回大于val位置的迭代器
iterator upper_bound (const value_type& val) const;
5.1 insert插入
// 单个数据插入,如果已经存在则插入失败
pair<iterator,bool> insert (const value_type& val);
// 列表插入,已经在容器中存在的值不会插入
void insert (initializer_list<value_type> il);
// 迭代器区间插入,已经在容器中存在的值不会插入
template <class InputIterator>
void insert (InputIterator first, InputIterator last);
当insert单个元素时,返回值是pair<iterator,bool>,pair是STL的一个struct模板
pair有两个值,first和second,T1是first的类型,T2是是second的类型
pair在作为insert插入单个元素的返回值时,first的类型是iterator,second的类型是bool
pair的first指向新插入的元素或与要插入元素相等的set中已经存在的元素
insert插入成功时,second是true,插入失败时,second是false
#include<iostream>
#include<set>
using namespace std;
int main()
{
// 去重+升序排序
set<int> s;
// 去重+降序排序(给一个大于的仿函数)
//set<int, greater<int>> s;
// 单个数据插入,如果已经存在则插入失败
s.insert(5);
s.insert(2);
s.insert(7);
s.insert(5);
auto it = s.begin();
while (it != s.end())
{
//set不允许修改元素
// error C3892: “it”: 不能给常量赋值
// *it = 1;
cout << *it << " ";
++it;
}
cout << endl;
// 插入一段initializer_list列表值,已经存在的值插入失败
s.insert({ 2,8,3,9 });
for (auto e : s)
{
cout << e << " ";
}
cout << endl;
set<string> strset = { "sort", "insert", "add" };
// 遍历string比较ascll码大小顺序遍历的
for (auto& e : strset)
{
cout << e << " ";
}
cout << endl;
// 迭代器区间插入,已经在容器中存在的值不会插入
set<int> s1;
s1.insert(s.begin(), s.end());
for (auto& e : s1)
{
cout << e << " ";
}
return 0;
}
5.2 删除和查找
// 删除一个迭代器位置的值
iterator erase (const_iterator position);
// 删除val,val不存在返回0,存在返回1
size_type erase (const value_type& val);
// 删除一段迭代器区间的值
iterator erase (const_iterator first, const_iterator last);
// 查找val,返回val所在的迭代器,没有找到返回end()
iterator find (const value_type& val);
// 查找val,返回Val的个数
size_type count (const value_type& val) const;
#include<iostream>
#include<set>
using namespace std;
int main()
{
set<int> s = { 4,2,7,2,8,5,9 };
cout << "s:";
for (auto e : s)
{
cout << e << " ";
}
cout << endl;
// 删除一个迭代器位置的值
s.erase(s.begin());
cout << "s:删除一个迭代器位置的值" << endl;
cout << "s:";
for (auto e : s)
{
cout << e << " ";
}
cout << endl;
set<int> s1 = { 1,2,3,4,5,6,7,8 };
cout << "s1:";
for (auto& e : s1)
{
cout << e << " ";
}
cout << endl;
// 删除一段迭代器区间的值
s1.erase(++s1.begin(), --s1.end());
cout << "s1:删除一段迭代器区间的值" << endl;
cout << "s1:";
for (auto& e : s1)
{
cout << e << " ";
}
cout << endl;
// 直接删除x
int x;
cout << "s:输入要删除的元素" << endl;
cin >> x;
// 删除val,val不存在返回0,存在返回1
int num = s.erase(x);
if (num == 0)
{
cout << x << "在s中不存在!" << endl;
}
cout << "s:";
for (auto e : s)
{
cout << e << " ";
}
cout << endl;
// 直接查找在利用迭代器删除x
cout << "输入s中要删除的元素" << endl;
cin >> x;
// 查找val,返回val所在的迭代器,没有找到返回end()
auto pos = s.find(x);
if (pos != s.end())
{
s.erase(pos);
}
else
{
cout << x << "在s中不存在!" << endl;
}
cout << "s:";
for (auto e : s)
{
cout << e << " ";
}
cout << endl;
// 算法库的查找 O(N)
auto pos1 = find(s.begin(), s.end(), x);
// set自身实现的查找 O(logN)
auto pos2 = s.find(x);
// 利用count间接实现快速查找
cout << "s:输入要查找的元素" << endl;
cin >> x;
if (s.count(x))
{
cout << x << "在s中!" << endl;
}
else
{
cout << x << "不存在s中!" << endl;
}
return 0;
}
lower_bound()和upper_bound
lower_bound(): >= 和upper_bound: >
#include<iostream>
#include<set>
using namespace std;
int main()
{
std::set<int> myset;
for (int i = 1; i < 10; i++)
myset.insert(i * 10); // 10 20 30 40 50 60 70 80 90
for (auto e : myset)
{
cout << e << " ";
}
cout << endl;
// 实现查找到的[itlow,itup)包含[30, 60]区间
// 返回 >= 30
auto itlow = myset.lower_bound(30);
// 返回 > 60
auto itup = myset.upper_bound(60);
// 删除这段区间的值
myset.erase(itlow, itup);
for (auto e : myset)
{
cout << e << " ";
}
cout << endl;
return 0;
}
6.multiset和set的差异
multiset和set的使用基本完全类似,主要区别点在于multiset支持值冗余,那么
insert/find/count/erase都围绕着支持值冗余有所差异,具体参看下面的样例代码理解。
#include<iostream>
#include<set>
using namespace std;
int main()
{
// 相比set不同的是,multiset是排序,但是不去重
multiset<int> s = { 4,2,7,2,4,8,4,5,4,9 };
auto it = s.begin();
while (it != s.end())
{
cout << *it << " ";
++it;
}
cout << endl;
// 相比set不同的是,x可能会存在多个,find查找中序的第一个
int x;
cin >> x;
auto pos = s.find(x);
while (pos != s.end() && *pos == x)
{
cout << *pos << " ";
++pos;
}
cout << endl;
// 相比set不同的是,count会返回x的实际个数
cout << s.count(x) << endl;
// 相比set不同的是,erase给值时会删除所有的x
s.erase(x);
for (auto e : s)
{
cout << e << " ";
}
cout << endl;
return 0;
}
到此,set的插入、删除、查找就讲完了,怎么样,是不是感觉大脑里面多了很多新知识。
如果觉得博主讲的还可以的话,就请大家多多支持博主,收藏加关注,追更不迷路
如果觉得博主哪里讲的不到位或是有疏漏,还请大家多多指出,博主一定会加以改正
博语小屋将持续为您推出文章