c++--------《set 和 map》

发布于:2024-12-06 ⋅ 阅读:(31) ⋅ 点赞:(0)

1 set系列的使⽤

1.1 set类的介绍

• set的声明如下,T就是set底层关键字的类型

• set默认要求T⽀持⼩于⽐较,如果不⽀持或者想按⾃⼰的需求⾛可以⾃⾏实现仿函数传给第⼆个模版参数

• set底层存储数据的内存是从空间配置器申请的,如果需要可以⾃⼰实现内存池,传给第三个参数。

• ⼀般情况下,我们都不需要传后两个模版参数。

• set底层是⽤红⿊树实现,增删查效率是 ,迭代器遍历是⾛的搜索树的中序,所以是有序的。O(logN)

• 前⾯部分我们已经学习了vector/list等容器的使⽤,STL容器接⼝设计,⾼度相似,所以这⾥我  们就不再⼀个接⼝⼀个接⼝的介绍,⽽是直接带着⼤家看⽂档,挑⽐较重要的接⼝进⾏介绍。
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;

1.2 set的构造和迭代器

在这里插入图片描述

~empty(1) 无参构造   set()
~range(2) 迭代器构造 set(InputIterator first,InputIterator second)
~copy(3)  拷贝构造   set(const set& x)
~move(4) 移动构造    set(set&& x)
~initializer list(5) set({1,2,3,4,5})  
// 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();

1.3 set重要接口

1: insert (插入元素)

// 单个数据插⼊,如果已经存在则插⼊失败,bool返回false
pair<iterator,bool> insert (const value_type& val);

//初始化列表插入,已经在容器中存在的值不会插入
void insert(initializer_list<value_type> x)

//迭代器区间插入,如果已经存在的值就不会插入
template<class InputIterator>
void insert(InputIterator first,InputIterator last);

2:find(查找元素)

//查找val,返回val所在的迭代器,没有找到返回end()

iterator find(const value_type& val);

3:count (查找元素个数)

//查找val,返回val的个数
size_type count(const value_type& val) const;

4:erase(删除元素)

// 删除⼀个迭代器位置的值
iterator erase (const_iterator position);

// 删除val,val不存在返回0,存在返回1
size_type erase (const value_type& val);

// 删除⼀段迭代器区间的值
iterator erase (const_iterator first, const_iterator last);

5:lower_bound 和 upper_bound
返回(upper_bound)的大于val的迭代器
返回(lower_bound)的大于等于val的迭代器

// 返回⼤于等val位置的迭代器
iterator lower_bound (const value_type& val) const;

// 返回⼤于val位置的迭代器
iterator upper_bound (const value_type& val) const;

2 实现样例

2.1: insert和迭代器遍历使⽤样例:

#include<iostream>
#include<set>
using namespace std;
int main()
{
// 去重+升序排序
set<int> s;
// 去重+降序排序(给⼀个⼤于的仿函数)
//set<int, greater<int>> s;
s.insert(4);
s.insert(3);
s.insert(65);
s.insert(8);
//set<int>::iterator it = s.begin();
auto it = s.begin();
while (it != s.end())
{
// 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;
}

【注意】
如果可以改变比较大小顺序 greater降序 ,默认是升序

set<string,greater<string>> strset = { "sort", "insert", "add" };
// 遍历string⽐较ascll码⼤⼩顺序遍历的
for (auto& e : strset)
{
cout << e << " ";
}
cout << endl;
}

在这里插入图片描述

2.2: find和erase使⽤样例:

#include<iostream>
#include<set>
using namespace std;
int main()
{
set<int> s = { 4,2,7,2,8,5,9 };
for (auto e : s)
{
cout << e << " ";
}
cout << endl;
// 删除最⼩值
s.erase(s.begin());
for (auto e : s)
{
cout << e << " ";
}
cout << endl;
// 直接删除x
int x;
cin >> x;
int num = s.erase(x);
if (num == 0)
{
cout << x << "不存在!" << endl;
}
for (auto e : s)
{
cout << e << " ";
}
cout << endl;
// 直接查找在利⽤迭代器删除x
cin >> x;
auto pos = s.find(x);
if (pos != s.end())
{
s.erase(pos);
}
else
{
cout << x << "不存在!" << endl;
}
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间接实现快速查找
cin >> x;
if (s.count(x))
{
cout << x << "在!" << endl;
}
else
{
cout << x << "不存在!" << endl;
}
return 0;
}

#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;
}

对于erase来说,删除后迭代器会失效

练习

1: 俩个数组的交集

利用set的性质(避免多个数据重复)可以很好解决此问题
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());
// 因为set遍历是有序的,有序值,依次⽐较
// ⼩的++,相等的就是交集
vector<int> ret;
auto it1 = s1.begin();
auto it2 = s2.begin();
while(it1 != s1.end() && it2 != s2.end())
{
if(*it1 < *it2)
{
it1++;
}
else if(*it1 > *it2)
{
it2++;
}
else
{
ret.push_back(*it1);
it1++;
it2++;
}
}
return ret;
}
};

2:环形链表

 利用接口count可以解决环形链表问题
class Solution {
public:
    ListNode *detectCycle(ListNode *head) 
    {
     set<ListNode*> s;
     ListNode* cur=head;
     while(cur)
     {
       if(s.count(cur))//s.count(cur)是判断有无cur的值
       {
         return cur;
       }
       else 
       s.insert(cur);
       cur=cur->next;
     } 
     return nullptr;
    }
};

随机链表复制

class Solution {
public:
    Node* copyRandomList(Node* head) 
    {
        Node* copyhead=nullptr;
        Node* copytail=nullptr;
        Node* cur=head;
        map<Node*,Node*> s;
      while(cur)//结点映射拷贝
      { 
        if(copytail==nullptr)
        {
        copyhead=copytail= new Node(cur->val);
        }
        else
        {
        copytail->next=new Node(cur->val);
        copytail=copytail->next;
        }
        s[cur]=copytail;//结点映射 cur->random 
        cur=cur->next;
      }
       cur=head;
       Node* copy=copyhead;
while(cur)//random复制
      {
        if(cur->random==nullptr)
        {
            copy->random=nullptr;
        }
        else
        {
            copy->random=s[cur->random];
        }
        cur=cur->next;    
        copy=copy->next;
    
      }
      return copyhead;
    }
};

3.map系列的使用

3.1 map类的介绍

map的声明如下,Key就是map底层关键字的类型,T是map底层value的类型,set默认要求Key⽀持⼩于⽐较,如果不⽀持或者需要的话可以⾃⾏实现仿函数传给第⼆个模版参数,map底层存储数据的内存是从空间配置器申请的。⼀般情况下,我们都不需要传后两个模版参数。map底层是⽤红⿊树实现,增删查改效率是 O(logN) ,迭代器遍历是⾛的中序,所以是按key有序顺序遍历的。

//Key就是map底层关键字的类型,T是map底层value的类型,
template < class Key, // map::key_type
class T, // map::mapped_type
class Compare = less<Key>, // map::key_compare
class Alloc = allocator<pair<const Key,T> > //
map::allocator_type> class map;

3.1.1 pair类型介绍

pair类型是用struct 封装的类,有俩个值key或value
typedef pair<const Key, T> value_type;
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)
{}
template<class U, class V>
pair (const pair<U,V>& pr): first(pr.first), second(pr.second)
{}
};

3.2 map的数据修改

map的构造与set的构造,和接口都差不多

增删查改

Insert
在这里插入图片描述

对于insert的返回值pair<iterator,bool>
(1) single element  给值插入

(2) with hint 指定位置插入

(3) range 迭代器区间插入

(4)initializer list 初始化插入

1:对于插入失败,则返回pair<iterator,false>,返回false
2:对于迭代器iterator,返回值iterator是返回插入时的迭代器
// 单个数据插⼊,如果已经key存在则插⼊失败,key存在相等value不相等也会插⼊失败
pair<iterator,bool> insert (const value_type& val);

// 列表插⼊,已经在容器中存在的值不会插⼊
void insert (initializer_list<value_type> il);

// 迭代器区间插⼊,已经在容器中存在的值不会插⼊
template <class InputIterator>
void insert (InputIterator first, InputIterator last);

erase
在这里插入图片描述

对于erase返回值:
1:iterator 返回的是删除的迭代器

2:size_type 是返回删除的个数

对于参数
1:const iterator position 是 指定区间删除
 
2:const key_type& k 指定值删除
 
3: 迭代器区间删除
// 删除⼀个迭代器位置的值
iterator erase (const_iterator position);

// 删除k,k存在返回0,存在返回1
size_type erase (const key_type& k);

// 删除⼀段迭代器区间的值
iterator erase (const_iterator first, const_iterator last);

find、count
在这里插入图片描述
在这里插入图片描述

iterator find (const key_type& k);
// 查找k,返回k的个数
size_type count (const key_type& k) const;

operator[ ] ()

功能增 查 改
在这里插入图片描述

map的operator【】,给key会返回value,如果key不存在的时候,
1:如果有就会修改second、2: key 不存在就插入

1、如果k不在map中,insert会插⼊k和mapped_type默认值,同时[]返回结点中存储
mapped_type值的引⽤,那么我们可以通过引⽤修改返映射值。所以[]具备了插⼊+修改功能
2、如果k在map中,insert会插⼊失败,但是insert返回pair对象的first是指向key结点的迭代器,返回值同时[]返回结点中存储mapped_type值的引⽤,所以[]具备了查找+修改的功能.

数据修改

前⾯我提到map⽀持修改mapped_type 数据,不⽀持修改key数据,修改关键字数据,破坏了底层搜索树的结构。map第⼀个⽀持修改的⽅式时通过迭代器,迭代器遍历时或者find返回key所在的iterator修改,map还有⼀个⾮常重要的修改接⼝operator[],但是operator[]不仅仅⽀持修改,还⽀持插⼊数据和查找数据,所以他是⼀个多功能复合接⼝需要注意从内部实现⻆度,map这⾥把我们传统说的value值,给的是T类型,typedef为mapped_type。⽽value_type是红⿊树结点中存储的pair键值对值。⽇常使⽤我们还是习惯将这⾥的T映射值叫做value。

【注意】

1、如果key已经在map中,插⼊失败,则返回⼀个pair<iterator,bool>对象,返回pair对象first是key所在结点的迭代器,second是false
2、如果key不在在map中,插⼊成功,则返回⼀个pair<iterator,bool>对象,返回pair对象first是新插⼊key所在结点的迭代器,second是true
3、也就是说⽆论插⼊成功还是失败,返回pair<iterator,bool>对象的first都会指向key所在的迭代器
4、那么也就意味着insert插⼊失败时充当了查找的功能,正是因为这⼀点,insert可以⽤来实现operator[]

3.3multimap和map的差异

multimap和map的使⽤基本完全类似,主要区别点在于multimap⽀持关键值key冗余,那么insert/find/count/erase都围绕着⽀持关键值key冗余有所差异,这⾥跟set和multiset完全⼀样,⽐如find时,有多个key,返回中序第⼀个。其次就是multimap不⽀持[],因为⽀持key冗余,[]就只能⽀持插⼊了,不能⽀持修改。

结束!!!

压力不是有人比你努力,而是比你牛叉几倍的人依然在努力。每个优秀的人,都有一段沉默的时光继承就讲到这里谢谢大家的观看!!!


网站公告

今日签到

点亮在社区的每一天
去签到