STL的vector介绍
vector:向量容器
底层数据结构:是动态开辟的数组,每次以原来空间大小的2倍进行扩容的
添加操作
vector<int> vec; 定义一个int类型的vector
vec.push_back(20); 在末尾添加元素 时间复杂度O(1) 有可能导致容器扩容
vec.insert(it, 20); it迭代器指向的位置添加一个元素20 O(n) 有可能导致容器扩容
插入1个元素,元素后边的所有元素都要向后挪动1个位置
扩容示意图如下:
以原来空间大小的2倍进行扩容,开辟空间,还要把原来的对象在新的内存空间上拷贝构造新的对象,再把原来空间上的对象析构掉,然后free原来的空间。
容器中进行对象的构造和析构,内存的开辟和释放,都是通过容器的空间配置器allocator(负责内存开辟)和deallocator(负责内存释放),construct(对象构造),destroy(对象析构)
删除操作
删除:
vec.pop_back(); 从末尾删除元素 时间复杂度是O(1)
vec.erase(it); 删除it迭代器指向的元素
删除后不允许中间空着,从删除点向后的元素都得向前挪动位置 时间复杂度O(n)
查询操作
查询:
operator[] 通过下标的随机访问vec[5] 时间复杂度O(1)
iterator迭代器进行遍历
find
for_each
foreach =>是通过iterator来实现的
注意:对容器进行连续插入或者删除操作(insert/erase),一定要更新迭代器,否则第一次insert或者erase完成,迭代器就失效了
常用方法介绍
size() 返回容器底层有效元素的个数
empty() 判断容器是否为空
reserve(20)
给vector预留空间的 只给容器底层开辟指定大小的内存空间,并不会添加新的元素
resize(20):
容器扩容用的 不仅给容器底层开辟指定大小的内存空间,还会添加新的元素
swap : 两个容器进行元素交换
vector代码应用
#include<vector>
#include<iostream>
using namespace std;
int main()
{
vector<int> vec;//vector<string> vec; 0 1 2 4 8 16 32 64
//默认的vector,底层是0个,没有开辟空间
//vec.reserve(20);//叫做给vector容器预留20个元素空间,但是不开辟元素对象
//reserve函数:避免在使用vector的过程中频繁让容器做扩容操作
vec.resize(20);//叫做给vector容器预留20个元素空间,而且还开辟相应的元素对象
cout << vec.empty() << endl;//是否是空的?是空的返回1,不是空的返回0
cout << vec.size() << endl; //int()
for (int i = 0; i < 20; ++i)
{
vec.push_back(rand() % 100 + 1);
//第一次插入1个元素,0扩容到1,第二次插入1个元素,1扩充到2,然后2-4,4-8一直扩容下去
//vector效率低,扩容太频繁
}
cout << vec.empty() << endl;
cout << vec.size() << endl;
//vector的operator[]运算符重载函数
int size = vec.size();
for (int i = 0; i < size; ++i)
{
cout << vec[i] << " ";
}
cout << endl;
//把vec容器中所有的偶数全部删除
auto it2 = vec.begin();
while (it2 != vec.end())
{
if (*it2 % 2 == 0)
{
it2 = vec.erase(it2);
//删除,必须提供earse返回值给it2更新,否则会失效
}
else
{
++it2;//从当前迭代器指向的元素值判断,因为删除后,后面的元素前移了
}
}
//通过迭代器遍历vector容器
auto it1 = vec.begin();
for (; it1 != vec.end(); ++it1)//vec.end()是容器末尾元素的后继位置
{
cout << *it1 << " ";
}
cout << endl;
//给vector容器中所有的奇数前面都添加一个小于奇数1的偶数 44 45 56 57
for (it1 = vec.begin(); it1 != vec.end(); ++it1)
{
if (*it1 % 2 != 0)
{
it1 = vec.insert(it1, *it1 - 1);
++it1;//插入后,后面元素向后挪动1个位置,所以要再++一次
}
}
for (it1 = vec.begin(); it1 != vec.end(); ++it1)
{
cout << *it1 << " ";
}
cout << endl;
return 0;
}
STL的deque介绍
deque:双端队列容器
底层数据结构:动态开辟的二维数组,一维数组从2开始,以2倍的方式进行扩容,每次扩容后,原来第二维的数组,从新的第一维数组的下标oldsize/2开始存放,上下都预留相同的空行,方便支持deque的首尾元素添加
第一维空间大小是MAP_SZIE=2,第二维和元素类型有关。
如果T是int类型,则第二维空间大小是4096/4=1024个。
双端队列,两个端可以做队头,或者队尾。
相当于2个方向都有2个角色。
first和last处于中间的位置(512):为了让两头都预留足够的空间,每一头都可以插入的。
随着元素的增加,相应的指针向后或者向前跑。
当我们元素入满的时候。
如果还想从右向左入,这个第二维已经没有空间了。
需要进行另外一个二维的开辟。
随着我们空间又添加满了
此时deque就需要扩容了
它扩容的是第一维,是按照2倍进行扩容的。
然后第二维也要挪过来了。是放在第一维的中间的位置。
因为是双端队列,任何一端都有可能进行元素的插入。给上下都预留空间。
方便元素的添加
然后deque又需要扩容。第一维扩容到原来的2倍。第二维挪过来,放在第一维的中间位置
添加元素
前后都可以添加,不涉及其他元素的挪动
deque<int> deq;
增加:
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一定要考虑迭代器失效的问题)
STL的list介绍
list:链表容器
底层数据结构:双向的循环链表 pre data next
增加元素
list<int> mylist;
增加:
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(1)
查询
查询搜索:
iterator(连续的insert和erase一定要考虑迭代器失效的问题)
deque和list,比vector容器多出来的增加删除函数接口:
push_front和pop_front
vector、deque、list对比
vector特点:动态数组,内存是连续的,以2倍的方式进行扩容,
vector<int> vec;//底层没有开辟任何空间
通过push_back或者insert给vector添加元素的方法:
0-1-2-4-8...
效率不高,在新的内存上用老内存的对象拷贝构造新对象,任何把老内存的对象析构,然后把老内存free
reserve(20)和resize区别?在前面内容讲述过
deque特点:动态开辟的二维数组空间,第二维是固定长度的数组空间,扩容的时候(第一维的数组进行2倍扩容,然后把原第二维的数据存放在第一维数组的中间,支持前后的插入和删除操作,都是O(1)的操作)
问题:deque底层内存是否是连续的?
并不是, 但是每一个第二维是连续的,所有二维之间是不连续的,因为二维是动态开辟的。
vector和deque之间的区别?
1.底层数据结构:不一样的。前面讲述过。
2.前中后插入删除元素的时间复杂度:
它们在末尾的插入和删除是一样的,时间复杂度都是 O(1)
它们在中间的插入和删除是一样的,时间复杂度都是O(n)
但是在最前面进行添加或者删除的时候, deque是O(1) vector 是O(n)
3.对于内存的使用效率:
vector 需要的内存空间必须是连续的 ,deque 可以分块进行数据存储,不需要内存空间必须是一片连续的,deque对于内存的使用效率高一些。
4.在中间进行insert或者erase
vector和deque它们的效率谁能好一点?
vector好一些。
从时间复杂度上来说,vector和deque,在中间进行insert或者erase,都要涉及数据的移动,数据量越大,移动所花费的次数和时间就多,它们的时间复杂度都是O(N),但是谁更好挪动一些?
当然是vector了。因为vector的内存是完全连续的,不管在这里进行元素的增加还是元素的删除,其他元素在进行挪动的时候是很简单的,得益于其内存是连续的,一个个往前放,或者一个个往后放,这样是很方便的。
但是如果把这个操作应用在deque中,就没有vector这么方便了。
假设把图中打叉的元素删除掉,后面的元素就要往前挪动,但是后面的一些元素是在另一个第二维的数组上,每个第二维数组之间的内存是不连续的。这样操作指令就多了,CPU执行起来就比较费时间,比较慢了。要先访问图中靠下面的那个第二维数组的第一维的下标值,然后访问到靠上面的那个第二维数组的第一维的下标值,存储的是上面那个第二维的起始地址,然后加上偏移量,才能找到靠下面的那个第二维数组的元素应该挪到靠上面的那个第二维的数组的哪个位置上。
容器的纵向考察:容器掌握的深度
容器的横向考察:各个相似容器之间的对比
vector和list之间的区别?
vector 数组: 在除末尾的增加删除的时间复杂度是O(n) 查询是 O(n) 但是随机访问是O(1) ([]下标访问,通过CPU直接下发1个地址,就可以直接访问到了)
list 链表: (我们要考虑搜索的时间,因为不一定就知道在哪里插入或者删除,对于链表来说,只能从头节点一个一个通过next偏移遍历,链表的搜索是O(n))增加删除的时间复杂度是O(1) 查询O(n)
链表的内存不是连续的
底层数据结构:vector是数组 list是双向循环链表
如果增加或者删除操作多,选择链表。如果随机访问多,选择vector