为什么我更喜欢使用vector进行双端队列


86

以来

  1. 它们都是连续的内存容器;
  2. 从功能上来说,双端队列几乎具有向量所有的内容,但更多,因为在前端插入更为有效。

为什么有人对子级宁愿std::vectorstd::deque


2
的Visual C ++实现std::deque具有很小的最大块大小(如果我没记错的话,最大块大小约为16个字节;也许是32个),因此对于实际应用程序来说效果不佳。甲deque<T>其中sizeof(T) > 8(或16?这是一个小数目)具有大约相同的性能特征为vector<T*>,其中每个元素是动态分配的。其他实现具有不同的最大块大小,因此使用很难编写在不同平台上具有相对相同的性能特征的代码deque
James McNellis 2011年

12
Deque不是连续的存储容器。
Mohamed El-Nakib 2014年

@ravil不,这是重复的,指向这个问题。

1
难以置信的事实错误未解决的问题,以34票的平衡票
underscore_d

2
@underscore_d这就是为什么这是一个问题。不同的故事,如果这是一个答案;)
Assimilater

Answers:


115

中的元素在内存deque连续;vector元素被保证是。因此,如果您需要与需要连续数组的普通C库进行交互,或者如果您(非常)在意空间局部性,那么您可能会更喜欢vector。另外,由于还有一些额外的簿记,所以其他操作可能(比)同等vector操作成本更高。另一方面,使用的许多实例vector可能会导致不必要的堆碎片(降低对的调用new)。

另外,如StackOverflow上其他地方所指出的,此处还有更多精彩的讨论:http : //www.gotw.ca/gotw/054.htm


3
似乎到“其他位置”的链接现在已经失效(由于审核?)。
丝绸

37

要知道差异,应该知道deque一般如何实现。内存以大小相等的块分配,并且被链接在一起(作为数组或向量)。

因此,要找到第n个元素,请找到合适的块,然后访问其中的元素。这是恒定时间,因为它总是精确地2次查找,但仍然比向量更多。

vector也可以与需要连续缓冲区的API一起使用,因为它们要么是C API,要么在获取指针和长度方面更加通用。(因此,您可以在其下方有一个向量或一个规则数组,然后从内存块中调用API)。

其中deque最大的优点是:

  1. 从两端增加或缩小集合时
  2. 当您处理非常大的集合大小时。
  3. 在处理布尔值时,您真的想要布尔值而不是bitset。

其中的第二个是鲜为人知的,但是对于非常大的集合来说:

  1. 重新分配的成本很大
  2. 查找连续内存块的开销是有限的,因此您可以更快地耗尽内存。

过去,当我处理大型集合并将其从连续模型转移到块模型时,在32位系统内存不足之前,我们能够存储大约5倍的大型集合。部分原因是,在重新分配时,实际上需要在复制元素之前存储旧块和新块。

综上所述,您可能会std::deque在使用“乐观”内存分配的系统上遇到麻烦。尽管它尝试为a重新分配请求较大的缓冲区大小的尝试vector可能会在某个时候被拒绝bad_alloc,但分配器的乐观性质很可能始终会允许对a请求的较小缓冲区的请求deque,这很可能导致操作系统杀死一个进程以尝试获取一些内存。它选择的任何一个都可能不太愉快。

在这种情况下,解决方法是设置系统级标志以覆盖乐观分配(并不总是可行),或者以某种方式手动管理内存,例如使用您自己的分配器来检查内存使用情况或类似情况。显然不理想。(谁可能会回答您的问题,以便选择向量...)


29

我已经实现了向量和双端队列多次。从实现的角度来看,双端队列非常复杂。这种复杂性将转化为更多的代码和更复杂的代码。因此,当您在向量上选择双端队列时,通常会看到命中的代码大小。如果您的代码仅使用向量擅长的事物(即push_back),则也可能会遇到速度下降的情况。

如果您需要双头队列,那么双端队列无疑是赢家。但是,如果您要在背面进行大部分插入和擦除操作,vector无疑是赢家。不确定时,请使用typedef声明容器(以便轻松地来回切换)并进行测量。


3
问题-委员会是否考虑过将两者的混合体(例如“套牌”)添加到C ++?(即双端vector。)我在答案中写了一个与下面链接的实现。它的速度可以与应用范围一样快,vector但适用范围更广(例如,在进行快速排队时)。
user541686 2013年

5

std::deque不能保证连续的内存-索引访问通常要慢一些。双端队列通常被实现为“向量列表”。


14
我认为“向量列表”是不正确的:我的理解是,大多数实现都是“数组指针的向量”,尽管这取决于您对“列表”的定义(我将“列表”读为“链接列表” ,”)。这不会满足复杂性要求。)
James McNellis 2011年

2

根据http://www.cplusplus.com/reference/stl/deque/,“与向量不同,双端队列不能保证其所有元素都位于连续的存储位置,从而消除了通过指针算法进行安全访问的可能性。”

双端队列比较复杂,部分原因是它们不一定具有连续的内存布局。如果需要该功能,则不应使用双端队列。

(以前,我的回答引起了标准化的不足(来自与上述相同的来源,“双端队列可能由特定的库以不同的方式实现”),但实际上几乎适用于任何标准库数据类型。)


5
std::deque标准化不亚于std::vector。我不认为std::deque连续存储可以满足其复杂性要求。
杰里·科芬

1
也许我的措辞很差:据我所知,标准化确实是不彻底的,尽管确实如此,但将向量标准化为连续序列,而不是双端队列。这似乎是一个决定因素。
patrickvacek 2011年

1
@JerryCoffin:deque连续存储不能满足哪些复杂性要求?
user541686

1
@Mehrdad:说实话,我不记得自己的想法。我最近对标准的那部分内容还不够了解,因此我可以轻松地断言我先前的评论是错误的,但是现在来看,我也想不到它是正确的。
杰里·科芬

3
@JerryCoffin:复杂性要求实际上是微不足道的:您可以分配一个大数组,然后开始从中间向外推序列(我想这是Mehrdad实现的功能),然后在结束时重新分配。这种方法的问题在于,它不满足的要求之一deque,即在末端插入不应使引用现有元素无效。此要求意味着不连续的内存。
Yakov Galka '10 -10-20

0

双端队列是允许随机访问其元素的序列容器,但不能保证具有连续存储。


0

我认为对每个案例进行性能测试的好主意。并根据此测试做出决策。

std::dequestd::vector大多数情况下更喜欢。


1
问题(如果可以从事实错误和遗漏单词中提炼出来)是为什么有人会喜欢vector。我们可以推断出为什么不是一个必然结果。deque出于未知原因,说您喜欢从未指定的测试中选择自己,这不是答案。
underscore_d


0

一方面,向量经常比双端队列快。如果您实际上并不需要双端队列的所有功能,请使用向量。

另一方面,有时您确实需要矢量无法提供的功能,在这种情况下,您必须使用双端队列。例如,我向任何人发起挑战,要求他们尝试尝试重写此代码,而无需使用双端队列,也无需极大地改变算法。


事实上,在同一系列push_backpop_back操作,deque<int>总是比至少快20%,vector<int>在我的测试(GCC与O3)。我想这就是为什么这样deque的标准选择std::stack……
igel

-1

请注意,随着数组的增长,向量内存将重新分配。如果您有指向矢量元素的指针,它们将变得无效。

同样,如果删除一个元素,则迭代器将变为无效(但不会变为“ for(auto ...)”)。

编辑:将“双端队列”更改为“矢量”


-1:在开始或结尾处插入和擦除不会使对其他元素的引用和指针无效。(尽管在中间插入和擦除)
Sjoerd

@Sjoerd我将其更改为“ vector”。
皮埃尔(Pierre)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.