序列式容器和关联式容器
前面我们已经接触过STL中的部分容器如:string、vector、list、deque、array、forward_list等,这 些容器统称为序列式容器,因为逻辑结构为线性序列的数据结构,两个位置存储的值之间⼀般没有紧密的关联关系,比如交换⼀下,他依旧是序列式容器。顺序容器中的元素是按他们在容器中的存储位置来顺序保存和访问的。 关联式容器也是用来存储数据的,与序列式容器不同的是,关联式容器逻辑结构通常是非线性结构, 两个位置有紧密的关联关系,交换⼀下,他的存储结构就被破坏了。顺序容器中的元素是按关键字来保存和访问的。
关联式容器有map/set系列和unordered_map/unordered_set系列。
map和set底层是红黑树,红黑树是⼀颗平衡⼆叉搜索树。set是key搜索场景的结构, map是key/value搜索场景的结构。
set系列的使用
set类的介绍
• set的声明如下,T就是set底层关键字的类型
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默认要求T支持小于比较,如果不支持或者想按自己的需求走可以自行实现仿函数传给第⼆个模版参数
• set底层存储数据的内存是从空间配置器申请的,如果需要可以自己实现内存池,传给第三个参 数。
• ⼀般情况下,我们都不需要传后两个模版参数。
• set底层是用红黑树实现,增删查效率是O(logN) ,迭代器遍历是走的搜索树的中序,所以是有序 的。
set的构造和迭代器
set的支持正向和反向迭代遍历,遍历默认按升序顺序,因为底层是⼆叉搜索树,迭代器遍历走的中序;支持迭代器就意味着支持范围for,set的iterator和const_iterator都不支持迭代器修改数据,修改关键字数据,破坏了底层搜索树的结构。
set<int>::iterator it = s.begin();
auto it = s.begin();
while (it != s.end())
{
// error C3892: “it”: 不能给常量赋值
*it = 1;
cout << *it << " ";
++it;
}
cout << endl;
在使用范围for中,如果使用*it进行修改编译器会报错。
set构造函数接口
// 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());
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;
set不支持插入相同的值,如果插入相同的值在则会插入失败,返回已存在结点的迭代器
第一种情况下进行插入,如果插入成功会返回插入值的迭代器,bool类型用来判断是否插入成功。
接口使用技巧
lower_bound和upper_bound可以用来实现删除某一区间的值,下面的代码用来删除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;
在进行删除的时候我们可以通过size_t的返回值来检查是否删除成功。
int x;
cin >> x;
int num = s.erase(x);
if (num == 0)
{
cout << x << "不存在!" << endl;
}
else
{
cout << x << "删除成功!" << endl;
}
我们可以利用count接口的特性来快速查找
cin >> x;
if (s.count(x))
{
cout << x << "在!" << endl;
}
else
{
cout << x << "不存在!" << endl;
}
return 0;
multiset与set的差异
multiset和set的使用基本完全类似,主要区别点在于multiset支持值冗余,那么 insert/find/count/erase都围绕着支持值冗余有所差异
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不同的是,multiset是排序,但是不去重
int x;
cin >> x;
auto pos = s.find(x);
while (pos != s.end() && *pos == x)
{
cout << *pos << " ";
++pos;
}
cout << endl;
相比set不同的是,x可能会存在多个,find查找中序的第⼀个
cout << s.count(x) << endl;
相⽐set不同的是,count会返回x的实际个数
s.erase(x);
for (auto e : s)
{
cout << e << " ";
}
cout << endl;
相比set不同的是,erase给值时会删除所有的x
题目练习
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
set<int> s1(nums1.begin(),nums1.end());
set<int> s2(nums2.begin(),nums2.end());
vector<int> v;
auto it1=s1.begin();
auto it2=s2.begin();
while(it1!=s1.end()&&it2!=s2.end())
{
if(*it1>*it2)
{
it2++;
}
else if(*it1<*it2)
{
it1++;
}
else
{
v.push_back(*it1);
it1++;
it2++;
}
}
return v;
}
};
这道题我们使用set库函数就能够方便许多,使用set自带的去重功能,能使查找过程中方便许多。
我们使用两个set类型的变量来存储nums1和nums2的数据,将重复的部分去掉。
然后遍历it1与it2,小的迭代器先加1,因为set默认排升序,小的数据必然不是交集
最后将数据尾插进v中即可完成