【问题标题】:Complexity requirements for std::deque::push_back/frontstd::deque::push_back/front 的复杂性要求
【发布时间】:2011-12-01 01:19:36
【问题描述】:

由于几天前this 的问题,有一些事情一直困扰着我关于std::deque::push_back/push_front 与实际std::deque 实现的复杂性要求。

上一个问题的结果是要求这些操作具有O(1) 最坏情况复杂度。我验证了c++11中确实是这种情况:

来自 23.3.3.4 的双端队列修饰符,指的是插入、推送/放置前/后

复杂度:复杂度与插入的元素数加上 到双端队列的开头和结尾的距离较小。插入单个 双端队列开头或结尾的元素总是花费恒定的时间,并且 导致对 T 的构造函数的一次调用。

这与索引的O(1) 复杂性要求相结合,通过operator[] 等。

问题在于实施并未严格满足这些要求。

msvcgcc 而言,std::deque 实现是一个块数据结构,由指向(固定大小)块的指针的动态数组组成,其中每个块存储许多数据元素。

在最坏的情况下,push_back/front etc 可能需要分配一个额外的块(这很好 - 固定大小分配是 O(1)),但它也可能需要调整块指针的动态数组的大小 - 这是不好,因为这是O(m),其中m 是块数,最终是O(n)

显然这仍然是摊销O(1) 的复杂性,因为通常m << n 在实践中会很快。但似乎一致性有问题?

另外一点,我看不出如何设计一个严格满足O(1) 复杂性的push_back/front etcoperator[] 的数据结构。你可以有一个块指针的链表,但这并没有给你想要的 operator[] 行为。真的可以做到吗?

【问题讨论】:

标签: c++ containers


【解决方案1】:

在 C++11 FDIS 中,我们可以阅读:

23.2.3 序列容器 [sequence.reqmts]

16/ 表 101 列出了为某些类型的序列容器提供的操作,而不是为其他类型的序列容器提供的操作。实现应为“容器”列中显示的所有容器类型提供这些操作,并应实现它们以占用摊销常数时间

其中表 101 被命名为可选序列容器操作,并为push_backpush_front 操作列出了deque

因此,您引用的段落似乎更像是一个轻微的遗漏。也许值得一份缺陷报告?

请注意,对构造函数的单个调用仍然有效。

【讨论】:

  • 这个问题最近似乎引起了一些讨论,例如:stackoverflow.com/questions/8627373/…stackoverflow.com/questions/8631531/…
  • @DarrenEngwirda:感谢您的指点 :) 我认为真正的问题是没有人知道如何实现满足标准要求的deque,只有近似值......我不知道也不知道。就我而言,随机访问/O(1) 推送/参考保存三部曲是无法管理的,即使是摊销的 O(1) 推送。
  • 这个答案中的标准参考有点误导,因为它不是唯一限制复杂性的地方。通过同样的条款,人们可以读到索引 std::vector 只需要花费 amortized 恒定时间,但在其他地方清楚地表明,对于任何单个实例,这种情况确实必须是恒定时间。同样对于std::deque<T>::push_front,详细条目(我的副本中的 23.3.3.4:3)说 总是需要恒定时间,正如 OP 中引用的那样,这显然似乎覆盖了 23.2.3:16 中所说的.
【解决方案2】:

我怀疑块指针的重新分配是通过几何增加的大小完成的——这是 std::vector 的常见技巧。我认为这在技术上是 O(log m),但正如您指出的 m

【讨论】:

  • 我知道你的意思,但标准并没有真正允许太多的移动空间,要求始终实现恒定时间。也许标准可以修改?
  • 实际上,几何增长使其摊销 O(1)(如vector)。
猜你喜欢
  • 1970-01-01
  • 2015-01-15
  • 1970-01-01
  • 2010-12-23
  • 2011-11-26
  • 1970-01-01
  • 2014-11-29
  • 1970-01-01
  • 2015-01-05
相关资源
最近更新 更多