持续学习中…
暂定结构:基础知识总结-问题分析-参考文献
如有错误,欢迎各位批评指正
代码块在文章中看着有点冗长,但我用的语雀,代码块可以收缩,软件里面看着是比较舒服的
STL,全名叫标准模板库
广义上分为三部分,容器,算法和迭代器,容器和算法通过迭代器进行无缝连接
详细分为六部分,容器-Container,算法-Algorithm,迭代器-Iterator,仿函数-Function object,适配器-Adaptor,空间配置器-Allocator
- 容器:是一种存储数据的模板类,如list,deque,vector,set,map
- 算法:对容器中的元素进行操作的模板函数,如find(),sort(),copy(),for_each()
- 迭代器:对容器中元素进行访问的类对象。它是一种将operator*,operator++,operator–进行重载的模板类
- 仿函数:行为类似函数,可作为算法的某种策略,从实现角度来看,仿函数就是重载了operator()的类的对象
- 适配器:修饰现有类的接口,形成一种新的接口,主要包括3中适配器Container Adaptor、Iterator Adaptor、Function Adaptor
- 空间配置器:负责空间的配置和管理,比如对象的创建和销毁,内存的获取和释放
容器
顺序容器:vector,deque,list
元素顺序增加,插入位置与元素值无关
关联式容器:set/multiset,map/multimap
元素时排序的,根据元素值决定插入的位置
容器适配器:stack,queue,priority_queue
封装了一些基本的容器,具备一些新的功能
- 是什么
- 元素在内存中怎么放
- 迭代器
- 动态空间分配机制
- 适用场景
- 特点,优缺点
vector
动态数组,元素在内存中连续存放,随机访问元素和在尾部增删元素都是常数时间
vector的迭代器
因为是线性空间存储,所以可以用指针来作为vector的迭代器
vector支持随机存取,指针也拥有这个能力,也就是可以直接+n来到达想要访问元素的位置,而其他的类似list,只能逐个迭代(就是不能直接+n)
vector的动态空间分配
vector创建时会在内存中分配一块线性连续空间,以两个迭代器指示空间中已使用的范围,另设一迭代器指示整块空间的末尾(可以用capacity()来查看空间大小)。当空间满了后,空间配置器会分配一块更大的空间,然后将原有的元素复制到新空间中,最后释放旧空间,这样所需要的时间旧很多了,同时建立在原空间上的迭代器也会失效。这警示我们用vector时,要预留足够的空间(reserve())
#include<vector>
vector<T> v; //采用模板实现类实现,默认构造函数
vector(v.begin(), v.end());//将v[begin(), end())区间中的元素拷贝给本身。
vector(n, elem);//构造函数将n个elem拷贝给本身。
vector(const vector &vec);//拷贝构造函数。
assign(beg, end);//将[beg, end)区间中的数据拷贝赋值给本身。
assign(n, elem);//将n个elem拷贝赋值给本身。
vector& operator=(const vector &vec);//重载等号操作符
swap(vec);// 将vec与本身的元素互换。
size();//返回容器中元素的个数
empty();//判断容器是否为空
resize(int num);//重新指定容器的长度为num,若容器变长,则以默认值填充新位置。如果容器变短,则末尾超出容器长度的元素被删除。
resize(int num, elem);//重新指定容器的长度为num,若容器变长,则以elem值填充新位置。如果容器变短,则末尾超出容器长>度的元素被删除。
capacity();//容器的容量
reserve(int len);//容器预留len个元素长度,预留位置不初始化,元素不可访问。
at(int idx); //返回索引idx所指的数据,如果idx越界,抛出out_of_range异常。
operator[];//返回索引idx所指的数据,越界时,运行直接报错
front();//返回容器中第一个数据元素
back();//返回容器中最后一个数据元素
insert(const_iterator pos, int count,ele);//迭代器指向位置pos插入count个元素ele.
push_back(ele); //尾部插入元素ele
pop_back();//删除最后一个元素
erase(const_iterator start, const_iterator end);//删除迭代器从start到end之间的元素
erase(const_iterator pos);//删除迭代器指向的元素
clear();//删除容器中所有元素
deque
双向队列,元素在内存中连续存放,随机访问元素和两端增删元素都是常数时间
deque的动态空间管理
deque中的元素存储在一段一段定量的连续空间块中,首端空间块向一个方向扩展,尾端空间块向另一个方向扩展,当空间快满时会链接一个新的空间快
deque是由这些空间块组成,每个块可以看作一个定量分配的数组,所以在读取数据时计算出偏移量及可在常数时间内读取
deque和vector的区别:
- deque允许在常量时间内对头部元素进行删除和插入
- deque没有容量的概念,deque是以分段的连续空间组合而成,随时可以添加一段空间链接起来,而vector旧空间不足会重新分配一块更大的空间,然后复制元素,再释放旧空间
deque和vector的相似点
- 在尾部插入元素具有良好的性能(常数时间),但在中间插入元素相对较慢
- 元素在内存中都是连续存放的
使用deque的场景
- 需要在两端插入和删除元素
- 不需要引用中间元素时候-因为deque的不同空间块之间是以指针数组的形式连接,所以性能上比不上vector
- 需要释放不再使用的元素-当空间块为空时会进行释放
#include<deque>
deque<T> deqT;//默认构造形式
deque(beg, end);//构造函数将[beg, end)区间中的元素拷贝给本身。
deque(n, elem);//构造函数将n个elem拷贝给本身。
deque(const deque &deq);//拷贝构造函数。
assign(beg, end);//将[beg, end)区间中的数据拷贝赋值给本身。原数据消失
assign(n, elem);//将n个elem拷贝赋值给本身。
deque& operator=(const deque &deq); //重载等号操作符
swap(deq);// 将deq与本身的元素互换
copy(deq.begin(),deq.end(),ostream_iterator<int>(cout," "));//将deq中的元素复制到标准输出流cout,并以空格间隔
deq[index]
deq.at(index)
front();//返回第一个数据。
back();//返回最后一个数据
deque.size();//返回容器中元素的个数
deque.empty();//判断容器是否为空
deque.resize(num);//重新指定容器的长度为num,若容器变长,则以默认值填充新位置。如果容器变短,则末尾超出容器长度的元素被删除。
deque.resize(num, elem); //重新指定容器的长度为num,若容器变长,则以elem值填充新位置,如果容器变短,则末尾超出容器长度的元素被删除。
push_back(elem);//在容器尾部添加一个数据
push_front(elem);//在容器头部插入一个数据
pop_back();//删除容器最后一个数据
pop_front();//删除容器第一个数据
clear();//移除容器的所有数据
erase(beg,end);//删除[beg,end)区间的数据,返回下一个数据的位置。
erase(it);//删除it迭代器处的数据,返回下一个数据的位置。
insert(it,elem);//在it迭代器处插入一个elem元素的拷贝,返回新数据的位置。
insert(it,n,elem);//在it迭代器处插入n个elem数据,无返回值。
insert(it,beg,end);//在it迭代器处插入[beg,end)区间的数据,无返回值。
list
双向链表,元素在内存中是非连续的,元素的逻辑顺序由指针实现
list是由一系列结点构成,每个结点包括存储数据的数据域,存储下一个结点的指针域
list的特点
- 随用随加,不用即释放,不会造成内存上的浪费
- 在任意位置增删元素都是常数时间
- 结点的空间消耗大,读取数据的时间效率低
list的迭代器
list的迭代器是Bidirectional Iterators,具有前移,后移,取值的功能
list的迭代器在增删元素时不会像vector一样丢失,删除元素时只是当前位置迭代器丢失,其他位置迭代器不受影响
#include<list>
list<T> lstT;//list采用采用模板类实现,对象的默认构造形式:
list(beg,end);//构造函数将[beg, end)区间中的元素拷贝给本身。
list(n,elem);//构造函数将n个elem拷贝给本身。
list(const list &lst);//拷贝构造函数。
push_back(elem);//在容器尾部加入一个元素
pop_back();//删除容器中最后一个元素
push_front(elem);//在容器开头插入一个元素
pop_front();//从容器开头移除第一个元素
insert(pos,elem);//在pos位置插elem元素的拷贝,返回新数据的位置。
insert(pos,n,elem);//在pos位置插入n个elem数据,无返回值。
insert(pos,beg,end);//在pos位置插入[beg,end)区间的数据,无返回值。
clear();//移除容器的所有数据
erase(beg,end);//删除[beg,end)区间的数据,返回下一个数据的位置。
erase(pos);//删除pos位置的数据,返回下一个数据的位置。
remove(elem);//删除容器中所有与elem值匹配的元素。
size();//返回容器中元素的个数
empty();//判断容器是否为空
resize(num);//重新指定容器的长度为num,
resize(num, elem);//重新指定容器的长度为num,
assign(beg, end);//将[beg, end)区间中的数据拷贝赋值给本身。
assign(n, elem);//将n个elem拷贝赋值给本身。
list& operator=(const list &lst);//重载等号操作符
swap(lst);//将lst与本身的元素互换。
front();//返回第一个结点元素值。
back();//返回最后一个结点元素值。
如果想获取结点应该用begin()
reverse();//反转链表,比如lst包含1,3,5元素,运行此方法后,lst就包含5,3,1元素。
sort(); //list排序
set/multiset
set时一个内部自动排序且不含重复元素的容器,multiset可以有重复元素,set和multiset的底层实现时红黑树,所以容器中的元素时一个个树结点,通过父指针和子结点指针连接在一起
红黑树
红黑树是一种二叉搜索树,左子树结点值小于根节点,右子树值大于根节点,每个树节点包括数据值,左右子结点指针,父节点指针和一个表示颜色的变量,红黑树通过一些规则保持一种平衡
- 每个节点要么是红色,要么是黑色。
- 根节点是黑色的。
- 每个叶子节点(NIL节点,空节点)都是黑色的。
- 如果一个节点是红色的,则它的两个子节点都是黑色的。
- 对于每个节点,从该节点到其所有后代叶子节点的简单路径上,均包含相同数量的黑色节点。
迭代器
set的元素既是键值又是实值,只能通过迭代器访问,因为set需保持有序,不可以通过迭代器更改元素的值,增删元素也不会对其他迭代器造成影响。如果想要改变元素值,只能先删除,然后再添加
set容器的使用场景
- 有序序列
- 快速查找元素
- 需要删除重复元素-插入重复元素会被忽略
#include<set>
set<T,[MyCompare]> st;//set默认构造函数:默认从小到大,Mycompare类里面写重载(),能更高的支持面向对象编程
mulitset<T> mst; //multiset默认构造函数:
set(const set &st);//拷贝构造函数
set& operator=(const set &st);//重载等号操作符
swap(st);//交换两个集合容器
size();//返回容器中元素的数目
empty();//判断容器是否为空
insert(elem);//在容器中插入元素。返回两个值第一个是迭代器第二个是是否插入成功bool值,可以用pair接受
clear();//清除所有元素
erase(pos);//删除pos迭代器所指的元素,返回下一个元素的迭代器。
erase(beg, end);//删除区间[beg,end)的所有元素 ,返回下一个元素的迭代器。
erase(elem);//删除容器中值为elem的元素。
find(key);//查找键key是否存在,若存在,返回该键的元素的迭代器;若不存在,返回set.end();
count(key);//查找键key的元素个数
lower_bound(keyElem);//返回第一个key>=keyElem元素的迭代器。
upper_bound(keyElem);//返回第一个key>keyElem元素的迭代器。
equal_range(keyElem);//返回容器中key与keyElem相等的上下限的两个迭代器。
map/multimap
map与set类似,也是是一个根据键值自动排序且不含重复键值的容器,底层实现也是红黑树,但map的元素值是pair,pair的first是键值,second是实值。类似于set的find函数查找的也是键值
迭代器
map 也是通过迭代器对元素进行访问,也不可以通过迭代器修改键值,但是可以修改实值,增删操作也不会对其他迭代器造成影响
map 的使用场景
- 统计元素出现次数
- 充当字典,快速查找元素
- 消去重复元素
- 需要数对作为元素值
#include<map>
map<T1, T2,[MyCompare]> mapTT;//map默认构造函数:
map(const map &mp);//拷贝构造函数
map& operator=(const map &mp);//重载等号操作符
swap(mp);//交换两个集合容器
size();//返回容器中元素的数目
empty();//判断容器是否为空
mapStu.insert(pair<int, string>(3, "小张"));//通过pair的方式插入对象,返回pair<iterator,bool>
mapStu[3] = "小刘"; //通过数组的方式插入值
clear();//删除所有元素
erase(pos);//删除pos迭代器所指的元素,返回下一个元素的迭代器。
erase(beg,end);//删除区间[beg,end)的所有元素 ,返回下一个元素的迭代器。
erase(keyElem);//删除容器中key为keyElem的对组。
find(key);//查找键key是否存在,若存在,返回该键的元素的迭代器;/若不存在,返回map.end();
count(keyElem);//返回容器中key为keyElem的对组个数。对map来说,要么是0,要么是1。对multimap来说,值可能大于1。
lower_bound(keyElem);//返回第一个key>=keyElem元素的迭代器。
upper_bound(keyElem);//返回第一个key>keyElem元素的迭代器。
equal_range(keyElem);//返回容器中key与keyElem相等的上下限的两个迭代器。
stack
stack是一种先进后出的STL容器,不提供迭代器,只能再在顶获取元素,增删操作也是在栈顶完成
底层实现
stack的默认底层实现是deque,也可以用list和vector实现
#include<stack>
stack<T> stk; //stack采用模板类实现, stack对象的默认构造形式
stack(const stack &stk); //拷贝构造函数
push(elem); //向栈顶添加元素
pop(); //从栈顶移除第一个元素
top(); //返回栈顶元素
empty(); //判断堆栈是否为空
size(); //返回栈的大小
queue
queue是一种先进先出的STL容器,不提供迭代器
底层实现
queue的默认底层实现是deque,也可以用list实现
优先级队列priority_queue,queue的一种,包括在queue.h里面
优先级队列会根据给定的优先级函数对插入元素进行排序,在访问时也是先访问优先级最高的元素
优先级队列的底层实现是堆(完全二叉树),存储元素的容器默认是vector,也可以是deque
#include<queue>
priority_queue<int, vector<int>, less<int>> priQueMaxFirst;
//第一个是元素的数据类型,第二个是存储数据的容器,第三个是优先级函数
//less是大项堆,大数据在前,小于前面的元素时下沉.greater时小项堆
//也可以用自定义的优先级函数
priority_queue<int, vector<int>, cmp> priQueMaxFirst;
struct cmp //class也可以
{
bool operator()( Data &a, Data &b){
return a.getId() < b.getId();
}
};//这是个大项堆,可以看作a是新增元素,然后逐个跟最前边元素比较
q.top();
//优先级队列获取优先级最高的元素操作时top不是front,也没有back,其他跟queue相同
#include<queue>
queue<T> que; //queue采用模板类实现,queue对象的默认构造形式
queue(const queue &que); //拷贝构造函数
push(elem); //往队尾添加元素
pop(); //从队头移除第一个元素
back(); //返回最后一个元素
front(); //返回第一个元素
empty(); //判断堆栈是否为空
size(); //返回栈的大小
迭代器
本质是容器类中的模板类,类中对一些操作符进行重载,其对象可以对容器中的元素进行访问等一系列操作
迭代器的类型
- 前向迭代器(Forward Iterator):支持解引用*,递增++,赋值=操作
- 双向迭代器(Bidirectional Iterator):支持前向迭代器的操作和递减–操作
- 随机访问迭代器(Random Access Iterator):支持双向迭代器的操作和+,-,+=,-=,>,<操作
各容器的迭代器类型
容器名 迭代器类型
array 随机访问
vector 随机访问
deque 随机访问
stack、queue、priority_queue 不支持
list 双向
set/multiset 双向
map/multimap 双向
迭代器的定义和获取
容器类名::iterator it;
vector<int>::iterator it;
auto it;
//auto自动识别类型
容器对象.begin();
容器对象.end();
容器对象.rbegin();
容器对象.rend();
迭代器的辅助函数
advance(it,n);//移动it
distance(it1,it2);
iter_swap(it1,it2);
算法
各种常⽤的算法,如sort、find、copy、for_each。从实现的⻆度来看,STL算法是⼀种function tempalte.
常见算法及用法
//算法主要是由头文件<algorithm><functional> <numeric>组成。
//<algorithm>是所有STL头文件中最大的一个,其中常用的功能涉及到比较,交换,查找,遍历,复制,修改,反转,排序,合并等…
//<numeric>体积很小,只包括在几个序列容器上进行的简单运算的模板函数.
//<functional> 定义了一些模板类,用以声明函数对象。
//sort
sort(vec.begin(),vec.end()); //默认为从小到大
class MyCompare
{
public:
bool operator()(int num1, int num2)
{
return num1 > num2;
}
};
sort(v.begin(), v.end(),MyCompare());//利用仿函数,排序为从大到小
//find
class GreaterThenFive
{
public:
bool operator()(int num)
{
return num > 5;
}
};
vector<int>::iterator it = find(v.begin(), v.end(), GreaterThenFive());
//value也可以,如果没有找到返回v.end()
//for_each 遍历 返回函数对象
void print01(int val){
cout << val << " ";
}
struct print001{
void operator()(int val){
cout << val << " ";
}
};
void test01(){
vector<int> v;
for (int i = 0; i < 10;i++){
v.push_back(i);
}
//遍历算法
for_each(v.begin(), v.end(), print01);
cout << endl;
for_each(v.begin(), v.end(), print001());
cout << endl;
}
//for_each返回值
struct print02{
print02(){
mCount = 0;
}
void operator()(int val){
cout << val << " ";
mCount++;
}
int mCount;
};
void test02(){
vector<int> v;
for (int i = 0; i < 10; i++){
v.push_back(i);
}
print02 p = for_each(v.begin(), v.end(), print02());
cout << endl;
cout << p.mCount << endl;
}
//copy
int src[] = {1, 2, 3, 4, 5};
int dst[5];
copy(std::begin(src), std::end(src), std::begin(dst));
仿函数
仿函数是一个类对象,类中重载了函数调用运算符,使得它能像函数一样调用
作用
- 可以作为一些算法的参数,如sort,for_each,用来定制算法的行为
- 可以用来保持内部状态,如优先队列,map,set
适配器
stl适配器有三种,容器适配器,迭代器适配器,函数适配器
容器适配器
有stack,queue,priority_queue,基本一些基本容器,如deque,vector实现,行使一些不同的功能(先进先出,新进后出)
迭代器适配器
包括反向迭代器(reverse_iterator),插入迭代器(insert_iterator),流迭代器(istream_iterator,ostream_iterator),都是在基本迭代器的基础上具有特定功能的迭代器
反向迭代器
常用来对容器进行反向遍历,++变–,–变++
#include <iterator>
vector<int> myvector{ 1,2,3,4,5,6,7,8 };
reverse_iterator<std::vector<int>::iterator> my_reiter(myvector.rbegin());//指向 8
cout << *my_reiter << endl;// 8
cout << *(my_reiter + 3) << endl;// 5
cout << *(++my_reiter) << endl;// 7
插入迭代器
向指定容器插入元素
- back_insert_iterator 在指定容器的尾部插入新元素,但前提必须是提供有 push_back() 成员方法的容器(包括 vector、deque 和 list)
- front_insert_iterator 在指定容器的头部插入新元素,但前提必须是提供有 push_front() 成员方法的容器(包括 list、deque 和 forward_list)
- insert_iterator 在容器的指定位置之前插入新元素,前提是该容器必须提供有 insert() 成员方法
#include <iterator>
list<int> foo(2, 5);
list<int>::iterator it = ++foo.begin();
insert_iterator<list<int>> insert_it = inserter(foo, it);
*insert_it = 1;
*insert_it = 2;
*insert_it = 3;
*insert_it = 4;
//5 1 2 3 4 5
list<int> foo(2, 5);
back_insert_iterator<list<int>> back_it = back_inserter(foo);
*back_it = 1;
*back_it = 2;
*back_it = 3;
*back_it = 4;
//5 5 1 2 3 4
list<int> foo(2, 5);
front_insert_iterator<list<int>> front_it = front_inserter(foo);
*front_it = 1;
*front_it = 2;
*front_it = 3;
*front_it = 4;
//4 3 2 1 5 5
流迭代器
- istream_iterator 从输入流中读取数据
- ostream_iterator 将元素输出到输出流中
#include <iterator>
vector<int> numbers = {1, 2, 3, 4, 5};
ostream_iterator<int> out_it(cout, " "); // 向标准输出写入整数,使用空格分隔
copy(numbers.begin(), numbers.end(), out_it); // 将vector中的数据写入输出流
double value1, value2;
std::cout << "Please insert values: "; //11 11 2
std::istream_iterator<double> eos; //输入流的结束位置
std::istream_iterator<double> iit (std::cin); //读取输入流的每个数据,绑定std::cin
if (iit!=eos) value1=*iit;
++iit;
if (iit!=eos) value2=*iit;
std::cout << value1 << "*" << value2 << "=" << (value1*value2) << '\n';
//11 * 11 = 121
函数适配器
函数适配器可以修改现有函数的参数,返回值和调用方式从而生成另一种函数
问题
容器和算法通过迭代器进行无缝连接体现在哪?
- 迭代器可以作为算法的参数和返回值,算法通过迭代器访问容器中的元素
- 迭代器有一些统一的接口(移动到上一/下一元素,获取当前元素的值),只要容器支持迭代器,都可以与算法进行连接,算法通过统一的接口对容器中的元素进行操作。常见的算法有sort(),find()等
迭代器是一种将operator*,operator++,operator–进行重载的类对象是什么意思?
迭代器不只存在于容器中,自定义的类中,也可以定义一个迭代器类,类中将++,–,*等类似于指针的操作符方法重载
#include <iostream>
// 自定义容器类
class MyContainer {
private:
int data[5] = {1, 2, 3, 4, 5};
public:
// 自定义迭代器类
class Iterator {
private:
int* ptr;
public:
Iterator(int* p) : ptr(p) {}
// 重载*操作符,获取指向的元素值
//返回的时数据的引用,表示用户可以通过*it修改值
//const表示这个重载函数内部不会对对象的成员变量进行修改
int& operator*() const {
return *ptr;
}
// 重载++操作符,将指针移动到下一个位置
Iterator& operator++() { //返回引用是为了支持连续的递增操作,++++
++ptr;
return *this; //this指向调用该成员函数的对象的指针
}
// 重载!=操作符,判断两个迭代器是否相等
bool operator!=(const Iterator& other) const {
return ptr != other.ptr;
}
};
// 获取容器起始位置的迭代器
Iterator begin() {
return Iterator(data);
}
// 获取容器结束位置的迭代器
Iterator end() {
return Iterator(data + 5);
}
};
int main() {
MyContainer container;
// 使用自定义容器的迭代器遍历容器中的元素并输出
for (auto it = container.begin(); it != container.end(); ++it) {
std::cout << *it << " ";
}
std::cout << std::endl;
return 0;
}
顺序容器和关联容器的区别?
- 关联容器中的元素是按关键字来保存和访问的;而顺序容器中的元素是按它们在容器中的位置来顺序保存和访问的;
- 关联容器不支持顺序容器的位置相关的操作,因为关联容器中元素是根据关键字存储的,这些操作对关联容器没有意义。而且,关联容器也不支持构造函数或插入操作这些接受一个元素值和一个数量值的操作。
- 关联容器支持高效的关键字查找和访问。两个主要的关联容器(associative container)类型是 map 和 set。map 中的元素是一些关键字 — 值(key–value)对:关键字起到索引的作用,值则表示与索引相关联的数据。而在 set 中每个元素只包含一个关键字:set 支持高效的关键字查询操作 —— 检查一个给定关键字是否在 set 中
map,hashtable,deque,list,unordered_map的实现原理
- map:map是一个内部自动排序而不含重复元素的容器,map的底层实现是红黑树,结点根据父节点指针和左右子结点指针连接在一起,结点的元素值有两个,键值和实值,map根据键值进行排序。map只能通过迭代器来访问元素,为了保持map的顺序,通过迭代器不可以修改键值,但可以修改实值
- hashtable:哈希表利用函数映射的原理将元素值和存储位置关联起来,能够快速定位到想要查找的记录
- deque:双端队列在内存中是一个个定量的空间块连接在一起,每个空间块中是一个数组,deque在内存中向两边扩展,如果两边的空间快满了,则分配一个新的空间块连接在需要添加元素的一边,如果空间块为空则直接释放
- list:双端队列,list中的元素在内存不是连续存储,而是通过指针进行连接,每个结点包括存储元素值的数据域和存储下一个结点指针的指针域
- unordered_map:unordered_map内部元素无序且不含重复元素,底层实现是哈希表,通过哈希函数将元素值和存储位置关联起来,可以实现快速查找
STL容器增删和查找操作的时间复杂度为多少
- vector
增删:O(n)
查找:O(1) - list
增删:O(1)
查找:O(n) - deque
增删:O(n)
查找:O(1) - map/set/multiset/multimap
增删:O(logn)
查找:O(logn) - unordered_map/unordered_set
增删:O(1)
查找:O(1)
map和unordered_map的优缺点适用场景
- map
- 优点:内部元素有序,根据键值对元素排序
- 缺点:空间代价较高,红黑树实现,每个结点都要额外保存父节点指针,左右子结点指针和结点颜色信息
- 适用场景:需要元素有序存储时适用map
- unordered_map
- 优点:利用哈希表实现,插入和查找元素等操作可以在常数时间内实现
- 缺点:在哈希冲突较多时,性能可能会下降,最坏情况下时间复杂度为O(n)
- 适用场景:需要频繁插入,查找的场景,例如统计元素的出现次数
迭代器什么时候会失效?
迭代器失效时指迭代器完全无效或者指向的元素不同
- 对于序列容器vector和deque,插入和删除操作,会使插入/删除点之后的元素向后移动,插入/删除点之后所有迭代器失效(deque的前端插入和删除除外)。如果插入操作使vector重新分配了空间,则全部迭代器失效,解决方法it = vec.erase(it),erase会返回下一个有效的迭代器
- 对于双向链表list,删除时会使删除点的迭代器失效
- 对于关联容器map和set,删除时会使删除点的迭代器失效
迭代器的作用,有指针为什么还要用迭代器
迭代器的作用
- 充当算法和容器间的胶合剂
- 指向容器中的元素,通过迭代器可以访问其指向的元素
- 通过非const迭代器可以修改指向的元素
有指针为什么还要用迭代器?(迭代器和指针的区别)
迭代器与指针不同,迭代器是模板类,封装了指针,重载了指针的++,–,->等操作符,可以在不暴露容器内部结构的情况下对元素进行遍历,是一种"高级"指针,可以根据不同的数据类型实现不同的++,–等操作
说说STL迭代器是如何删除元素的
主要考察的是迭代器失效问题
回答与迭代器失效相同,只关注删除