开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第2天,点击查看活动详情
vector数组
vector原理:
vector的数据安排及操作方式与array非常相似。两者的唯一差别在于空间运用的灵活性。
array是静态空间,一旦配置好了就不能改变了,如果程序需要一个更大的array,只能自己再申请一个更大的array,然后将以前的array中的内容全部拷贝到新的array中。
vector是动态空间,随着元素的加入,它的内部机制会自动扩充空间以容纳新的元素。vector的关键技术在于对大小的控制以及重新分配时的数据移动效率。
vector采用的数据结构是线性的连续空间,他以两个迭代器start和finish分别指向配置得来的连续空间中目前已将被使用的空间。迭代器end_of_storage指向整个连续的尾部。
vector在增加元素时,如果超过自身最大的容量,vector则将自身的容量扩充为原来的两倍。扩充空间需要经过的步骤:重新配置空间,元素移动,释放旧的内存空间。一旦vector空间重新配置,则指向原来vector的所有迭代器都失效了,因为vector的地址改变了。
1、vector的内存是在栈中?
看STL源码剖析,vector的空间配置器是data_allocator,也就是simple_alloc,simple_alloc的实现就是std::alloc,根据申请的内存大小,决定用第一级配置器(malloc、free)还是第二级配置器(内存池),所以vector应该是分配在堆上的。
2.vector扩容:
1.新增元素:Vector通过一个连续的数组存放元素,如果集合已满,在新增数据的时候,就要分配一块更大的内存,将原来的数据复制过来,释放之前的内存,在插入新增的元素;
2.对vector的任何操作,一旦引起空间重新配置,指向原vector的所有迭代器就都失效了;
3.初始时刻vector的capacity为0,塞入第一个元素后capacity增加为1;
4.不同的编译器实现的扩容方式不一样,VS2015中以1.5倍扩容,GCC以2倍扩容。
3.push_back和emplace_back的区别:
使用push_back()向容器中加入一个右值元素(临时对象)时,首先会调用构造函数构造这个临时对象,然后需要调用拷贝构造函数将这个临时对象放入容器中。原来的临时变量释放。这样造成的问题就是临时变量申请资源的浪费。
emplace_back在容器尾部添加一个元素,这个元素原地构造,不需要触发拷贝构造和转移构造。
push_back()时间复杂度分析:
分为两种情况:
剩余空间足够: 无需重新分配空间,此时直接在数组尾部插入;
剩余空间不足时: 需要重新分配空间(扩充空间,重新配置、移动数据,释放原空
间) 其容量(capacity)会增大到原来的 m 倍。现在我们来均摊分析方法来计算 push_back 操作的时间复杂度。 假定有 n 个元素,倍增因子为 m。那么完成这 n 个元素往一个 vector 中的push_back操作,需要重新分配内存的次数大约为 logm(n),第 i 次重新分配将会导致复制 m^i (也就是当前的vector.size() 大小)个旧空间中元素,因此 n 次 push_back操作所花费的总时间约为 n*m/(m - 1); 很明显这是一个等比数列,那么 n 个元素,n 次操作,每一次操作需要花费时间为 m / (m - 1),这是一个常量. 所以,我们采用均摊分析的方法可知,vector 中 push_back 操作的时间复杂度为常量时间.O(1)
设分配i次,即分配m^i大小的空间
m^i=n
i=logm(n)
那么n次push_back操作所花费的时间为:
n*m/(m-1)等比数列
所花费时间为m/(m-1)次,所以使用均摊分析可得:
时间复杂度为O(1)
简单来说就是,均摊(Amortized)时间复杂度为O(1)。