c++ - 第16节 - map和set

目录

1.关联式容器

2.键值对

3.树形结构的关联式容器

3.1.set

3.1.1.set的介绍

3.1.2.set的使用

3.2.map

3.2.1.map的介绍

3.2.2.map的使用

3.3.multiset

3.3.1.multiset的介绍

3.3.2.multiset的使用

3.4.multimap

3.4.1.multimap的介绍

3.4.2.multimap的使用

3.5.在OJ中的使用

4.底层结构


1.关联式容器

在初阶阶段,我们已经接触过STL中的部分容器,比如:vector、list、deque、forward_list(C++11)等,这些容器统称为序列式容器,因为其底层为线性序列的数据结构,里面存储的是元素本身。那什么是关联式容器?它与序列式容器有什么区别?
关联式容器也是用来存储数据的,与序列式容器不同的是,其 里面存储的是<key, value>结构的键值对,在数据检索时比序列式容器效率更高。


2.键值对

用来表示具有一一对应关系的一种结构,该结构中一般只包含两个成员变量key和value,key代表键值,value表示与key对应的信息。比如:现在要建立一个英汉互译的字典,那该字典中必然有英文单词与其对应的中文含义,而且,英文单词与其中文含义是一一对应的关系,即通过该应该单词,在词典中就可以找到与其对应的中文含义。
STL库中关于键值对pair的定义如下:
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)
{}
};


3.树形结构的关联式容器

根据应用场景的不桶,STL总共实现了两种不同结构的管理式容器:树型结构与哈希结构。 树型结构的关联式容器主要有四种:map、set、multimap、multiset。这四种容器的共同点是:使用平衡搜索树(即红黑树)作为其底层结果,容器中的元素是一个有序的序列。下面一依次介绍每一个容器。

3.1.set

3.1.1.set的介绍

set类的文档介绍:set - C++ Reference (cplusplus.com)

翻译:

1.set是按照一定次序存储元素的容器
2.在set中,元素的value也标识它(value就是key,类型为T),并且每个value必须是唯一。set中的元素不能在容器中修改(元素总是const),但是可以从容器中插入或删除它们。
3.在内部,set中的元素总是按照其内部比较对象(类型比较)所指示的特定严格弱排序准则进行排序。
4.set容器通过key访问单个元素的速度通常比unordered_set容器慢,但它们允许根据顺序对
子集进行直接迭代。
5.set在底层是用二叉搜索树(红黑树)实现的。

注:

1.与map/multimap不同,map/multimap中存储的是真正的键值对<key, value>,set中只放
value,但在底层实际存放的是由<value, value>构成的键值对。
2.set中插入元素时,只需要插入value即可,不需要构造键值对。
3.set中的元素不可以重复(因此可以使用set进行去重)。
4.使用set的迭代器遍历set中的元素,可以得到有序序列
5.set中的元素默认按照小于来比较
6.set中查找某个元素,时间复杂度为: log_{2}^{N}
7.set中的元素不允许修改

8.如下图所示是set类的声明,是k模型搜索树的模板,模板第一个参数是一个类,也就是key的类型,模板第二个参数是一个比较器,传的是仿函数类,默认传的是小于less仿函数类,模板第三个参数是一个空间配置器(内存池),第三个参数给了缺省值,这个缺省值其实就是官方库给的set,如果不想使用官方库的set那么也可以自己写一个set传过来

9.如果set使用默认的less仿函数,那么其插入规则是key大的排右边小的排左边,迭代器遍历访问是升序,如果set使用greater仿函数,那么其插入规则是key大的排左边小的排右边,迭代器遍历访问是降序。

3.1.2.set的使用

1.set的构造

函数声明
功能说明
set (const Compare& comp = Compare(), const Allocator& = Allocator() )
构造空的set

set (InputIterator first, InputIterator last, const Compare& comp = Compare(), const Allocator& = Allocator() )

用[first, last)区 间中的元素构造
set

set ( const set<Key,Compare,Allocator>& x)

set的拷贝构造

2.set的迭代器

函数声明
功能说明
iterator begin()
返回set中起始位置元素的迭代器
iterator end()
返回set中最后一个元素后面的迭代器
const_iterator cbegin() const
返回set中起始位置元素的const迭代器
const_iterator cend() const
返回set中最后一个元素后面的const迭代器
reverse_iterator rbegin()
返回set第一个元素的反向迭代器,即end
reverse_iterator rend()
返回set最后一个元素下一个位置的反向迭代器,即rbegin
const_reverse_iterator crbegin() const
返回set第一个元素的反向const迭代器,即cend
const_reverse_iterator crend() const
返回set最后一个元素下一个位置的反向const迭代器,即crbegin

3.set的容量

函数声明
功能说明
bool empty ( ) const
检测 set 是否为空,空返回 true ,否则返回 true
size_type size() const
返回 set 中有效元素的个数

4.set修改操作

函数声明
功能说明
pair<iterator,bool> insert ( const value_type& x )
在set中插入元素x,实际插入的是<x, x>构成的键值对,如果插入成功,返回<该元素在set中的位置,true>,如果插入失败,说明x在set中已经存在,返回<x在set中的位置,false>
void erase ( iterator position )
删除set中position位置上的元素
size_type erase ( const key_type& x )
删除set中值为x的元素,返回删除的元素的个数
void erase ( iterator first, iterator last )
删除set中[first, last)区间中的元素

void swap( set<Key,Compare,Allocator>& st );

交换两个set中的所有元素(本质是将根节点进行交换)
void clear ( )
将set中的元素清空
iterator find ( const key_type& x ) const
返回set中值为x的元素的位置
size_type count ( const key_type& x ) const
返回set中值为x的元素的个数
iterator lower_bound ( const value_type& val ) const;以val为下边界,返回大于等于val的位置(val可以是set中没有的值)
iterator upper_bound (const value_type& val) const;以val为下边界,返回大于val的位置(val可以是set中没有的值)

注:

1.set中插入函数insert的函数声明如下图一所示,第一种插入是直接插入某个值,第二种插入是在某个位置插入某个值,第三种插入是插入一段迭代器区间,其中第二三种插入方式用的很少。第一二种插入参数中的value_type类型是重定义过的,如下图二所示,key_type和value_type都是set类第一个模板参数T的重定义。

set类里面的insert函数使用方式如下图一所示,可以看出使用迭代器访问set里面的数据,访问的数据是经过排序且去重的。set里面的数据不支持修改,如下图二所示。如下图三所示,从std库中set迭代器的源码可以看出,虽然set类的迭代器中有普通iterator迭代器,也有const修饰的const_iterator迭代器,但是他们的底层用的都是const_iterator迭代器

set类的insert插入函数没有迭代器失效的问题

2.set中find函数声明如下图所示,如果找到了对应的val元素,则返回该元素所在的迭代器,如果没找到则返回的是end函数返回的尾部迭代器。

算法库algorithm中也有一个find函数,其声明如下图一所示,algorithm中find函数也能在set类中找val元素并返回val元素的迭代器,如下图二所示,那么set类中的find和算法库中的find有什么区别呢?

算法库中的find是暴力查找,遍历迭代器区间,因此其时间复杂度为O(N),set类中的find函数是根据搜索树的查找方式进行查找的,因此其时间复杂度为O(log_{2}^{N}),因此查找set类中的数据应使用set类中的find函数。

3.set中删除函数erase的函数声明如下图所示,第一种删除是在某个位置删除,第二种删除是删除某个值,第三种删除是删除某一段迭代器区间,其中第三种删除方式用的较少。

set类里面的第一种erase函数(在某个位置删除)的使用方式如下图一所示,set类里面的第二种erase函数(删除某个值)的使用方式如下图二所示。

可以认为set类里面的第二种erase函数近似等价于先find函数查找,找到后再调用第一种erase函数进行删除,二者不同的是set类里面的第二种erase函数如果要删除的值不在set中也没有问题,但是先find函数查找再调用第一种erase函数进行删除的话,如果要删除的值不在set中,然后这里调用第一种erase函数进行删除的时候没有进行find查找结果的判断,那么系统就会报错,如下图三所示。

set类里面的第二种erase函数(删除某个值)返回的是删除该值结点的个数,因为set中不允许冗余,所以返回值只可能为0或1,如果返回1则成功删除,如果返回0则删除失败,如下图所示。

删除之后迭代器指向位置的意义变了,会导致迭代器失效。

4.set中count函数声明如下图所示,其功能是返回set中值为x的元素的个数。因为set是K模型的搜索二叉树,里面的数据不允许有冗余,所以set中的count函数返回的结果只有两个,如果有该元素返回1,如果没有该元素返回0

当需要判断set中有没有值为x的元素时,使用count函数比使用find函数更加方便,如下图所示

5.set中lower_bound函数声明如下图一所示,其功能是以val为下边界,返回大于等于val的位置(val可以是set中没有的值),lower_bound函数的使用方式如下图三所示

set中upper_bound函数声明如下图二所示,其功能是以val为下边界,返回大于val的位置(val可以是set中没有的值),upper_bound函数的使用方式如下图四所示

 

3.2.map

3.2.1.map的介绍

map类的文档介绍:map - C++ Reference (cplusplus.com)

翻译:
1. map是关联容器,它按照特定的次序(按照key来比较)存储由键值key和值value组合而成的元
素。
2. 在map中,键值key通常用于排序和惟一地标识元素,而值value中存储与此键值key关联的
内容。键值key和值value的类型可能不同,并且在map的内部,key与value通过成员类型
value_type绑定在一起,为其取别名称为pair:
typedef pair<const key, T> value_type;
3. 在内部,map中的元素总是按照键值key进行比较排序的。
4. map中通过键值访问单个元素的速度通常比unordered_map容器慢,但map允许根据顺序
对元素进行直接迭代(即对map中的元素进行迭代时,可以得到一个有序的序列)。
5. map支持下标访问符,即在[]中放入key,就可以找到与key对应的value。
6. map通常被实现为二叉搜索树(更准确的说:平衡二叉搜索树(红黑树))。

7. 如下图所示是map类的声明,是KV模型搜索树的模板,模板第一个参数是一个类,也就是key的类型,模板第二个参数是一个类,也就是value的类型,模板第三个参数是一个比较器,传的是仿函数类,默认传的是小于less仿函数类,模板第四个参数是一个空间配置器(内存池),第四个参数给了缺省值,这个缺省值其实就是官方库给的map,如果不想使用官方库的map那么也可以自己写一个multiset传过来

map里面的key和value不是像前面我们KV模型搜索树那样分开存的,map中是将key和value一起存在pair里面,如下图所示。

8.如果map使用默认的less仿函数,那么其插入规则是key大的排右边小的排左边,迭代器遍历访问是升序,如果map使用greater仿函数,那么其插入规则是key大的排左边小的排右边,迭代器遍历访问是降序。

3.2.2.map的使用

1.map的构造

函数声明
功能说明
map()
构造一个空的map
2.map的迭代器
函数声明
功能说明
begin()和end()
begin:首元素的位置,end最后一个元素的下一个位置
cbegin()和cend()
与begin和end意义相同,但cbegin和cend所指向的元素不能修改
rbegin()和rend()
反向迭代器,rbegin在end位置,rend在begin位置,其++和--操作与begin和end操作移动相反
crbegin()和crend()
与rbegin和rend位置相同,操作相同,但crbegin和crend所指向的元素不能修改

注:

1.map的遍历使用以前的遍历方式是不行的,如下图一所示,因为map中是有key、value两个值的,对it解引用相当于去调用map的operator*()函数,然而c++不支持一个函数返回两个值。std里面的做法是将map中的key和value两个值打包成一个pair结构,pair结构不支持流插入和流提取,需要手动打印pair结构,如下图二所示,对迭代器解引用就是对应的pair结构,pair结构中fist成员变量存的是key值,second成员变量存的是value值。打印pair结构中fist和second成员变量可以对迭代器解引用使用*运算符再去访问,也可以用->运算符(用->运算符需要使用两个->才能通过迭代器访问到pair中的成员变量,这里编译器会进行优化,只需要写一个就可以了)

2.map的遍历也可以使用范围for,如下图所示

3.map的容量与元素访问

函数声明
功能说明
bool empty ( ) const
检测 map 中的元素是否为空,是返回 true ,否则返回 false
size_type size() const
返回 map 中有效元素的个数
mapped_type& operator[ ] (const key_type& k)
返回去key对应的value
注:

1.map中operator[ ]的函数声明如下图一所示,其中参数类型key_type和返回值类型mapped_type是类型重定义过的,如下图二所示,key_type是Key的类型重定义,其实就是key,mapped_type是T的类型重定义,其实就是value。operator[ ]函数的重载实现代码近似下图三所示。

\bullet 当operator[ ]函数的参数k在map对象中,operator[ ]函数的功能是 

(1)查找key对应的value

(2)修改key对应的value

解析:代码insert(make_pair(k, mapped_type())),其中mapped_type()相当于T(),构造一个T类型的匿名对象当作value,因为k在map对象中,所以insert函数插入失败,insert函数的返回值pair键值对中iterator就是原本map对象中k所在结点的迭代器,bool就是false,operator[ ]函数返回原本map对象中k的value引用值(返回引用值也就是说可以对对应的value值进行修改)。

\bullet 当operator[ ]函数的参数k不在map对象中,operator[ ]函数的功能是 

(1)插入key结点,结点的value是T()构造的value值

(2)修改key对应的value

解析:代码insert(make_pair(k, mapped_type())),其中mapped_type()相当于T(),构造一个T类型的匿名对象当作value,因为k不在map对象中,所以insert函数插入成功,insert函数的返回值pair键值对中iterator就是新插入的k所在结点的迭代器,bool就是true,operator[ ]函数返回新插入的k结点的value引用值(返回的value值就是mapped_type()也就是T()构造的值)(返回引用值也就是说可以对对应的value值进行修改)。

2.如下图所示,可以利用operator[ ]函数来实现插入+修改、查找+修改、插入、查找功能。

4.map中元素的修改
函数声明
功能说明
pair<iterator,bool> insert ( const value_type& x )
在map中插入键值对x,注意x是一个键值对,返回值也是键值对:iterator代表新插入元素的位置,bool代表释放插入成功
void erase ( iterator position )
删除position位置上的元素
size_type erase ( const key_type& x )
删除键值为x的元素
void erase ( iterator first, iterator last )
删除[first, last)区间中的元素
void swap ( map<Key,T,Compare,Allocator>& mp )
交换两个map中的元素
void clear ( )
将map中的元素清空
iterator find ( const key_type& x )
在map中插入key为x的元素,找到返回该元
素的位置的迭代器,否则返回end
const_iterator find ( const key_type& x ) const
在map中插入key为x的元素,找到返回该元
素的位置的const迭代器,否则返回cend
size_type count ( const key_type& x ) const
返回key为x的键值在map中的个数,注意
map中key是唯一的,因此该函数的返回值
要么为0,要么为1,因此也可以用该函数来
检测一个key是否在map中

注:

1.map中插入函数insert的函数声明如下图一所示,第一种插入是直接插入某个值(值是键值对),第二种插入是在某个位置插入某个值,第三种插入是插入一段迭代器区间,其中第二三种插入方式用的很少。第一二种插入参数中的value_type类型是重定义过的,如下图二所示,value_type是pair<const key_type,mapped_type>的重定义,其中key_type是Key的类型重定义,也就是key,mapped_type是T的类型重定义,也就是value

map类里面的insert函数使用方式如下图一所示,下图一的三种写法都可以,第一种是构造一个匿名的pair对象然后插入,第二种是先构造pair对象然后将对象插入,第三种是使用make_pair函数模板来构造pair对象然后进行插入,第三种使用make_pair函数模板的优势是不用去声明参数。make_pair函数的声明和实现如下图二所示

也可以使用我们前面学的operator[ ]函数来实现插入操作。

2.前面统计水果次数的代码使用map来实现,其代码如下图所示

上面的代码如果如果遇到countMap里面还没有的str字符串,先countMap.find(str)遍历countMap在countMap里面找到str的位置,然后再countMap.insert(make_pair(str, 1)),再遍历一遍countMap找到str插入的位置进行插入,这样连续遍历了两遍countMap,可以进行优化。

如下图所示,insert函数的第一种直接插入某个值的用法其返回值是pair<iterator,bool>,如果原本的map中没有键值对val,则insert插入成功,返回插入结点的迭代器和bool类型的true键值对,如果原本的map中有键值对val,则insert插入失败,返回map中原本val结点的迭代器和bool类型的false键值对。

我们利用insert函数的返回值,对上面统计水果次数的代码进行优化,如下图所示

我们还可以使用operator[ ]函数再进一步优化,如下图所示。

如果str没有在map中,那么operator[str]函数会先插入key为str的结点,插入的key为str的结点其value是调用int()默认构造得到的,因此其value为0,operator[str]返回value的引用,然后对value的引用++。也就是说如果str没有在map中,插入key为str的结点,其value值经过++最终为1。

如果str在map中,那么operator[str]函数插入key为str的结点失败,operator[str]返回原本map中key为str结点的value的引用,然后对value的引用++。也就是说如果str在map中,那么对原本key为str的结点的value值++。

3.其他map的函数和set的对应函数用法相同,这里不再赘述。

3.3.multiset

3.3.1.multiset的介绍

multiset类的文档介绍:multiset - C++ Reference (cplusplus.com)

翻译:

1. multiset是按照特定顺序存储元素的容器,其中元素是可以重复的。
2. 在multiset中,元素的value也会识别它(因为multiset中本身存储的就是<value, value>组成
的键值对,因此value本身就是key,key就是value,类型为T). multiset元素的值不能在容器
中进行修改(因为元素总是const的),但可以从容器中插入或删除。
3. 在内部,multiset中的元素总是按照其内部比较规则(类型比较)所指示的特定严格弱排序准则进行排序。
4. multiset容器通过key访问单个元素的速度通常比unordered_multiset容器慢,但当使用迭
代器遍历时会得到一个有序序列。
5. multiset底层结构为二叉搜索树(红黑树)。

注:

1. multiset中再底层中存储的是<value, value>的键值对
2. mtltiset的插入接口中只需要插入即可
3. 与set的区别是,multiset中的元素可以重复,set是中value是唯一的
4. 使用迭代器对multiset中的元素进行遍历,可以得到有序的序列
5. multiset中的元素不能修改
6. 在multiset中找某个元素,时间复杂度为$O(log_2 N)$
7. multiset的作用:可以对元素进行排序
8.如下图所示是multiset类的声明,是K模型搜索树冗余版本的模板,模板第一个参数是一个类,也就是key的类型,模板第二个参数是一个比较器,传的是仿函数类,默认传的是小于less仿函数类,模板第三个参数是一个空间配置器(内存池),第三个参数给了缺省值,这个缺省值其实就是官方库给的multiset,如果不想使用官方库的multiset那么也可以自己写一个multiset传过来
9.如果multiset使用默认的less仿函数,那么其插入规则是key大的排右边小的排左边相等的排右边,迭代器遍历访问是升序,如果multiset使用greater仿函数,那么其插入规则是key大的排左边小的排右边相等的排右边,迭代器遍历访问是降序。

3.3.2.multiset的使用

此处只简单演示set与multiset的不同,其他接口接口与set相同,可参考set。

(1)multiset允许里面数据相同,也就是说允许数据冗余,那么multiset支持排序不去重功能,如下图一所示

(2)count函数是返回等于某个值的结点个数,set中因为不允许冗余,count函数只可能返回1或0;multiset中因为允许冗余,count函数返回的某个值的个数不再只是1或0,如下图二所示

(3)erase函数中删除某个值的用法是删除所有等于该值的结点,返回删除的结点个数。set中因为不允许冗余,erase函数只可能返回1或0;multiset中因为允许冗余,erase函数返回的删除某个值的个数不再只是1或0,如下图三所示

(4)multiset中因为允许冗余,multiset中的find函数返回的是中序遍历第一个遍历到的要找值的节点,如下图四所示

3.4.multimap

3.4.1.multimap的介绍

multimap类的文档介绍:multimap - C++ Reference (cplusplus.com)

翻译:

1. Multimaps是关联式容器,它按照特定的顺序,存储由key和value映射成的键值对<key,
value>,其中多个键值对之间的key是可以重复的。
2. 在multimap中,通常按照key排序和惟一地标识元素,而映射的value存储与key关联的内
容。key和value的类型可能不同,通过multimap内部的成员类型value_type组合在一起,
value_type是组合key和value的键值对:
typedef pair<const Key, T> value_type;
3. 在内部,multimap中的元素总是通过其内部比较对象,按照指定的特定严格弱排序标准对
key进行排序的。
4. multimap通过key访问单个元素的速度通常比unordered_multimap容器慢,但是使用迭代
器直接遍历multimap中的元素可以得到关于key有序的序列。
5. multimap在底层用二叉搜索树(红黑树)来实现。
注:
1.multimap和map的唯一不同就是:map中的key是唯一的,而multimap中key是可以重复的,如下图所示。

2.如下图所示是multimap类的声明,是KV模型搜索树冗余版本的模板,模板第一个参数是一个类,也就是key的类型,模板第二个参数是一个类,也就是value的类型,模板第三个参数是一个比较器,传的是仿函数类,默认传的是小于less仿函数类,模板第四个参数是一个空间配置器(内存池),第四个参数给了缺省值,这个缺省值其实就是官方库给的map,如果不想使用官方库的map那么也可以自己写一个multiset传过来

3.如果multimap使用默认的less仿函数,那么其插入规则是key大的排右边小的排左边相等的排右边,迭代器遍历访问是升序,如果multimap使用greater仿函数,那么其插入规则是key大的排左边小的排右边相等的排右边,迭代器遍历访问是降序。

3.4.2.multimap的使用

此处只简单演示set与multiset的不同,其他接口接口与set相同,可参考set。
(1)map中的key是唯一的,而multimap中key是可以重复的(前面讲过不再赘述)
(2)map中重载了operator[]操作,multimap中没有重载operator[]操作。multimap中数据允许冗余,key和value可能存在一对多的情况,所以multimap中不支持operator[]操作
(3)map中insert的函数声明如下图一所示,直接插入某个值(值是键值对)的用法返回的是pair<iterator,bool>键值对,因为map中insert存在插入失败的情况,插入的key原本map中已经有了,那么返回的iterator就是原本map中key结点的迭代器,返回的bool是false。
multimap中insert的函数声明如下图二所示,直接插入某个值(值是键值对)的用法返回的是iterator迭代器,因为map中insert不存在插入失败的情况,返回的iterator就是新插入结点的迭代器

3.5.OJ中的使用

练习题一:

题目描述:

题目链接:692. 前K个高频单词 - 力扣(LeetCode)

代码1:

代码2:

代码3:

注:

1.这里既可以创建以pair<pair<string,int>>为数据的vector,也可以创建以map<string,int>::iterator为数据的vector来进行排序。创建以pair<pair<string,int>>为数据的vector将在将map中键值对拷贝过来的时候因为键值对pair大小大于迭代器,效率较低,所以我们使用以map<string,int>::iterator为数据的vector。

2.在map中将数据依次插入的时候已经按照key也就是字符串的ASCII码进行了排序,然后将map中的结点的迭代器拷贝到vector中,使用sort函数配合IterCompare仿函数进行排序,将排序后的vector前k个迭代器指向结点的key值依次插入到数据为string类型的vector中,返回数据为string类型的vector。

这里有一个问题是,题目中要求如果不同的单词有相同出现频率, 按字典顺序(ASCII码顺序)排序,在map中已经按照字符串的ASCII码进行了排序,但是使用的sort是快排,快排是不稳定的,导致最终相同出现频率的单词没有按照ASCII码顺序排序。

sort介绍如下图所示,里面提到相等的元素不能保证其相对顺序,也就是说sort是不稳定的,有一个稳定的排序stable_sort。我们使用stable_sort即可解决该问题,如上面的代码1所示。

针对上面的问题,我们也可以通过对仿函数改进来解决,如果value次数不同就按照value进行比较,如果value次数相同则按照key字符串的ASCII码进行比较,如上面的代码2所示。

3.map本身是可以排序的,我们使用map<int,string>对key值次数进行排序,但是map本身还会去重,相同次数的字符串只会保留一个,我们应该使用multimap。multimap和map默认是排的升序,我们这里需要排降序,解决办法是给multimap传仿函数greater,进行降序排序,如上面代码3所示,给multimap传仿函数greater进行降序排序是稳定的,原因如下:

原因:如果multiset、multimap使用默认的less仿函数,那么其插入规则是key大的排右边小的排左边相等的排右边,遍历结果是升序,如果multiset、multimap使用greater仿函数,那么其插入规则是key大的排左边小的排右边相等的排右边,遍历结果是降序。两种仿函数都是相等的排在右边,相等的排在右边就能够保证相等的两个值先插入的先访问后插入的后访问,可以保证排序的稳定性。

练习题二:

题目描述:

题目链接:349. 两个数组的交集 - 力扣(LeetCode)

代码1:

代码2(优化版):

注:

1.代码1中两个数据集分别插入两个set进行排序+去重。然后使用算法:遍历其中一个set的每一个值,和另一个set每一个值进行比对,找到所有交集数据,这一部分算法其时间复杂度为N\times log_{2}^{N},效率较低。

2.找两个集合的交集,先将两个集合插入到两个set中进行排序+去重。然后使用算法:两个迭代器对两个set同时进行遍历。

(1)相比较,迭代器指向值小的,该小的迭代器++

(2)相比较,迭代器指向值相等的就是交集,两个迭代器同时++

其中一个走完了,剩下一个set中的数据中不会再有交集,结束。

上面代码2用的就是该思路,该思路时间复杂度为O(N)

3.找两个集合的差集,先将两个集合插入到两个set中进行排序+去重,然后使用两个迭代器对两个set同时进行遍历。

(1)相比较,迭代器指向值小的就是差集,该小的迭代器++

(2)相比较,迭代器指向值相等,两个迭代器同时++

其中一个走完了,剩下一个set中的数据都是差集。

该思路时间复杂度为O(N)


4.底层结构

前面对map、multimap、set、multiset进行了简单的介绍,在其文档介绍中发现,这几个容器有个共同点是: 其底层都是按照二叉搜索树来实现的,但是二叉搜索树有其自身的缺陷,假如往树中插入的元素有序或者接近有序,二叉搜索树就会退化成单支树,时间复杂度会退化成O(N),因此map、set等关联式容器的底层结构是对二叉树进行了平衡处理,即采用平衡树来实现,平衡树有AVL树和红黑树,后面我们会详细介绍AVL树和红黑树。
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

随风张幔

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值