文章目录
一、STL分类
c++ STL又叫 standard template libaray 标准模板库。
下面列出来的是我们常用到的C++STL容器
容器中,对象的构造析构,内存的开启释放是通过容器的空间配置器allocator实现的
allocate 负责内存开辟
dealocate 负责内存释放
construct 对象构造
destory 对象析构
二、vector
1、底层数据结构
动态开辟的数组,每次以原来空间大小的2倍进行扩容vector vec;
2、相关接口
增加:
vec.push_back(20);在容器末尾添加元素,O(1) 导致容器扩容
vec.insert(it,20);在迭代器指向的位置添加一个元素20 O(n) 导致容器扩容
删除:
vec.pop_back();末尾删除元素O(1)
vec.erase(it);删除迭代器指向的元素 O(n)
注意:对容器进行连续插入或者删除操作(insert/erase),一定要更新迭代器,否则第一次insert或者erase完成,迭代器就失效了
例子一:实现把vec容器中所有的偶数全部删除
int main()
{
vector<int> vec;
auto it2 = vec.begin();
while (it2 != vec.end())
{
if (*it2 % 2 == 0)
{
it2 = vec.erase(it2);
}
else
{
++it2;
}
}
return 0;
}
如果只是vec.erase(it2);执行完过后迭代器就失效了,it2不能再加加了。所以要对迭代器进行更新一次,删除以后,后面的元素挪到了当前迭代器的位置,所以不用it2++
另外,当前元素继续判断,如果当前元素不是偶数,才要进行迭代器++
运行结果如下:
例子二:给vector容器中所有的奇数前面都添加一个小于奇数1的偶数(连续插入)
int main()
{
vector<int> vec;
auto it1 = vec.begin();
for (it1 = vec.begin(); it1 != vec.end(); ++it1)
{
if (*it1 % 2 != 0)
{
it1=vec.insert(it1, *it1 - 1);//要对迭代器进行更新
it1++;
}
}
return 0;
}
要对迭代器进行更新.找到了奇数要进行两次++操作,如下图所示,找到了47在其之前插入46,++it1将46插入,it1还应该++对迭代器进行更新寻找下一个奇数。
查询:
operator[];因为底层是一个数组,数组最大的特点就是通过数组下标随机访问vec[5] O(1)
iterator迭代器进行遍历
find ,for_each
foreach => 通过iterator来实现
3、常用方法
size():返回容器底部有效的数据元素
empty():判断容器是否为空
reserve(20):给vector预留空间 只给容器底层开辟指定大小的内存空间,并不会添加新的元素
resize(20):容器扩容 不仅给容器底层开辟指定大小的内存空间,还会添加新的元素
swap:两个容器进行元素交换
在这儿,我们要特别区分一下reserve和resize。
reserve主要是给vector容器预留空间,如下代码验证:
int main()
{
vector<int> vec;//默认开辟的vector,底层空间为0
vec.reserve(20);
cout << vec.empty() << endl;//输出布尔值
cout << vec.size() << endl;
for (int i = 0; i < 20; i++)
{
vec.push_back(rand() % 100 + 1);
}
cout << vec.empty() << endl;
cout << vec.size() << endl;
return 0;
}
运行结果如下:
所以我们可以看出reserve给vector容器预留空间20但是里面并没有数据所以empty为true,添加的过程并不会导致容量的增加。所以最后还是20的容量。
resize就是容器扩容,如下代码验证
int main()
{
vector<int> vec;//默认开辟的vector,底层空间为0
vec.resize(20);
cout << vec.empty() << endl;//输出布尔值
cout << vec.size() << endl;
for (int i = 0; i < 20; i++)
{
vec.push_back(rand() % 100 + 1);
}
cout << vec.empty() << endl;
cout << vec.size() << endl;
return 0;
}
运行结果如下:
不仅仅开辟了20个空间还在里面存放了20个为0的整形元素,再添加20个元素的时候开始扩容
所以empty为false.添加的过程又增加了20个元素。所以最后输出40.
4、通过迭代器对vector容器遍历
方式一:
auto it1 = vec.begin();
for (; it1 != vec.end(); ++it1)
{
cout << *it1 << " ";
}
方式二:
int size = vec.size();
for (int i = 0; i < size; i++)
{
cout << vec[i] << " ";
}
三、queue和list
1、双端队列容器queue
1、底层数据结构
动态开辟的二维数组,一位数组从2开始,以2倍的方式进行扩容,每次扩容后,原来第二维的数组,从新的第一维数组的下标oldsize/2开始存放,上下都预留相同的空行,方便支持deque的首尾元素添加deque deq;
如下所示,当一个队列满了,则向下扩充一个队列
当两个队列都满了,再扩充
2、相关接口
增加:
deq.push_back(20);从末尾添加元素O(1)
deq.push_front(20);从首部添加元素O(1) //vec.insert(vec.begin(),20) O(n)
deq.insert(it,20);it指向的位置添加元素O(n)
删除:
deq.pop_back();从末尾删除元素 O(1)
deq.pop_front();从首部删除元素O(1)
deq.erase(it);从it指向的位置删除元素O(n)
查询搜索:
iterator(连续的insert和erase一定要考虑迭代器失效的问题)
2、链表容器list
1、底层数据结构
双向的循环链表 pre data next,如图所示:
2、相关接口
增加:
mylist.push_back(20);从末尾添加元素O(1)
mylist.push_front(20);从首部添加元素O(1) //vec.insert(vec.begin(),20) O(n)
mylist.insert(it,20);it指向的位置添加元素O(1) 链表中进行insert的时候,先要进行一个query查询操作
对于链表来说,查询操作效率就比较慢了
删除:
mylist.pop_back();从末尾删除元素 O(1)
mylist.pop_front();从首部删除元素O(1)
mylist.erase(it);从it指向的位置删除元素O(n)
查询搜索:
iterator(连续的insert和erase一定要考虑迭代器失效的问题)
deque和list 比vector容器多出来的增加删除函数接口:
push_front 和pop_fornt
四、vector、queue、list的区别
1、vector和deque之间的区别
vector特点:动态开辟的数组,内存是连续的,2倍的方式进行扩容 vector vec;
deque特点:动态开辟的二维数组空间,deque底层内存不是连续的第二维是固定长度的数组空间,扩容的时候(把第一维的数组进行二倍扩容)
1、底层数据结构的区别
2、前中后插入元素的时间复杂度
中间和末尾的时间复杂度相同O(1),前面插入deque是O(1)因为是双端队列在前或后都可以扩充,但是vectorO(n)
3、对于内存的使用效率
vector需要的内存空间必须是连续的,deque可以分块进行数据存储不需要内存空间必须是连续的
4、在中间进行insert或者erase,效率vector好一点,deque差一点。
虽然时间复杂度都是O(n),但是要考虑挪动的方便性由于deque的第二维内存空间不是连续的,所以在deque中间进行元素的insert或者erase,造成元素移动的时候比vector慢。
如下图所示:
在deque中,每一个第二维是连续的(重新new出来的),是一种分段连续。
删除了一个元素,后面元素的移动首先要找到一维去,存了起始位置的地址,然后再根据偏移量找到元素移动位置。
但是vector的删除就很简便了,如下图所示:
因为他的存储空间是连续的,所以依次挨个移动即可。
2、vector和list之间的区别
这两个的区别就类似于数组和链表的区别,这样一联想,就很容易得出答案了
1、 底层数据结构
一个是动态数组,一个是双向循环链表
2. 增删改查时间复杂度
数组 增加删除O(n) 查询O(n) 随机访问O(1)
链表考虑搜索的时间 其余删除查询的时间复杂度都是O(1)