如果数组/向量非常大,有没有办法在数组/向量中的任何位置插入数据?
如果我使用vector :: insert,向量将移动我的项目之后的所有项目,这将花费很多时间,例如,如果向量得到1b项并且这是在向量的中间执行,则向量将移动500米项目。
使用C style / C ++数组或Vector吗?
是否有任何有效的方法答案 0 :(得分:7)
理论(特别是Big O表示法)表示链接列表具有O(1)插入和删除的复杂性,动态数组具有O(n),这是由于擦除/插入需要的容器移位。
但那就是理论。计算机不仅仅是理论。在实践中它的差异很大。
现代计算机的内存排列在所谓的内存层次结构中,即按照其速度排序的一组不同类型的内存设备:
+---------------+
| CPU registers | ^
+---------------+ |
| L1 cache | |
| ... | | Less apacity
| LN cache | | Faster access
+---------------+ | More expensive hardware
| RAM | |
+---------------+
| HDD |
+---------------+
如图所示,层次结构是通过内存访问的速度来组织的。但请注意,更高的速度意味着更昂贵的硬件,因此这转换为许多慢速访问内存和一些快速内存。 因此提高程序性能的一种方法是在快速内存中保存频繁使用的数据,并且只在必要时才转到慢速内存(程序请求未加载到快速内存中的数据,例子)。
这就是硬件的作用,假设有两种行为:
当然,内存是有限的,因此将在层次结构的某个级别加载的新数据请求会丢弃之前的数据。
所以,为什么这对于不同容器的性能很重要?
记住链接列表(std::list
是一个链接列表)如何炒作:
链表是通过指针在它们之间连接的分离节点链:
+---+ +---+ +---+
| 1 | --> | 2 | --> ... --> | N |
+---+ +---+ +---+
另一方面,动态数组(std::vector
是动态数组)是连续的内存块:
+---+---+-----+---+
| 1 | 2 | ... | N |
+---+---+-----+---+
正如我上面所说,理论上说链表插入/删除具有O(1)复杂性,因为“只是改变指针”。但是考虑一下你如何访问内存来做到这一点。 您是否注意到流程不符合空间位置规则?。因此,这有很多未命中(缓存未命中),即请求快速内存中的新内存,并且性能下降。
事实上,即使理论上说跨越链表具有O(n)复杂性,实际上与复杂性一起是由于缓存未命中导致的连续性能命中。
现在考虑动态数组是如何工作的:插入/移除具有O(n)复杂度是正确的,因为你必须在数组的一侧移动一个位置为新元素留一个间隙,或者如果你正在擦除。
但请记住,数组是一个连续的内存块,所以如果你使用它,可能该数组完全(或几乎部分)加载到快速内存中( sapce-locality ),所以轮班过程真的很快。
正如您所看到的,动态数组远远快于现代架构中的链接列表。
一般来说,std::vector
有许多与std::list
相对应的优势:
std::list
不是,而且“缓存友好”对于当今的性能非常重要。这样可以快速插入/移除,快速随机访问和快速复制。malloc()
/ free()
(即,调用操作系统来检索/离开堆内存)需要花费大量时间。另一方面,动态数组仅以O(logn)平均值进行de / allocations。但不是全部:在少数情况下,std::list
必须是首选,复制/移动元素的成本非常高的情况。 std::vector
性能的要点是基于在缓存中完成的廉价转换过程,但是如果向量的元素具有某些昂贵的复制/移动语义,则根本没有任何好处并且列表执行比矢量更好。
阅读this article以获取有关该主题的更多信息。
答案 1 :(得分:2)
C ++向量实际上是具有智能调整大小的数组,如果需要在容器的前面插入,则需要考虑其他类似链接列表。但是,请记住,如果性能对您非常关键,那么这些其他容器通常在内存中不连续,因此当您遍历它们并在内存中跳转时,通常会引发内存分页问题。这总是一种权衡。