list :: size()真的是O(n)吗?

时间:2008-10-23 08:08:58

标签: c++ list stl complexity-theory big-o

最近,我注意到有些人提到std::list::size()具有线性复杂性 根据{{​​3}} some,这实际上取决于实施,因为标准没有说复杂性必须是什么。
评论sources说:

  

实际上,这取决于你的STL   正在使用。 Microsoft Visual Studio V6   将size()实现为{return(_Size);   而gcc(至少在版本中)   3.3.2和4.1.0)将其作为{return std :: distance(begin(),end());这个   第一个是恒速,第二个   有o(N)速度

  1. 所以我的猜测是,VC ++人群size()与Dinkumware一样具有复杂性 从VC6开始,可能不会改变这个事实。我在那儿吗?
  2. gcc目前的情况如何?如果真的是O(n),为什么呢? 开发商选择这样做吗?

7 个答案:

答案 0 :(得分:68)

在C ++ 11中,对于任何标准容器,.size()操作必须以“常量”复杂度(O(1))完成。 (表96-容器要求)。以前在C ++ 03中.size() 应该具有恒定的复杂性,但不是必需的(参见Is std::string size() a O(1) operation?)。

标准的变化由n2923: Specifying the complexity of size() (Revision 1)引入。

但是,libstdc ++中.size()的实现仍然在gcc中使用O(N)算法,最高可达4.8:

  /**  Returns the number of elements in the %list.  */
  size_type
  size() const _GLIBCXX_NOEXCEPT
  { return std::distance(begin(), end()); }

另见Why is std::list bigger on c++11?详细说明为何以这种方式保存。

在C ++ 11模式(或更高版本)中使用gcc 5.0 时,

更新 std::list::size()properly O(1) )。


顺便说一句,libc ++中的.size()正确为O(1):

_LIBCPP_INLINE_VISIBILITY
size_type size() const _NOEXCEPT     {return base::__sz();}

...

__compressed_pair<size_type, __node_allocator> __size_alloc_;

_LIBCPP_INLINE_VISIBILITY
const size_type& __sz() const _NOEXCEPT
    {return __size_alloc_.first();}

答案 1 :(得分:50)

Pre-C ++ 11回答

你是正确的,标准没有说明list :: size()的复杂性必须是什么 - 但是,它确实建议它“应该具有恒定的复杂性”(表65中的注释A)。

Here's an interesting article by Howard Hinnant解释了为什么有些人认为list :: size()应该具有O(N)复杂度(主要是因为他们认为O(1)list :: size()使得list :: splice()具有O(N)复杂度)以及为什么O(1)list :: size()是一个好主意(在作者看来):

我认为论文的要点是:

  • 保持内部计数的情况很少,因此list::size()可以是O(1)导致拼接操作变为线性
  • 可能还有更多的情况,有人可能不知道可能发生的负面影响,因为他们调用了O(N)size()(例如他的一个例子,其中list::size()被调用一把锁。)。
  • 代替允许size()为O(N),为了“最小惊喜”,标准应该要求任何实现size()的容器以O(1)方式实现它。如果容器无法执行此操作,则根本不应实现size()。在这种情况下,容器的用户将知道size()不可用,如果他们仍然想要或需要获取容器中的元素数量,他们仍然可以使用container::distance( begin(), end())来获取值 - 但他们会完全意识到这是一个O(N)操作。

我认为我倾向于同意他的大多数推理。但是,我不喜欢他对splice()重载的建议添加。传入必须等于n的{​​{1}}以获得正确的行为似乎是难以诊断错误的方法。

我不确定应该做什么或可以做什么,因为任何改变都会对现有代码产生重大影响。但就目前而言,我认为现有的代码已经受到影响 - 对于应该已经明确定义的内容,行为可能在一个实现与另一个实现之间存在相当大的差异。也许onebyone关于将大小“缓存”并标记为已知/未知的评论可能效果很好 - 你得到了摊销的O(1)行为 - 唯一一次得到O(N)行为的是当某些splice()操作修改了列表时。关于这一点的好处是它可以由实现者在今天完成而无需更改标准(除非我遗漏了一些东西)。

据我所知,C ++ 0x并没有改变这方面的任何内容。

答案 2 :(得分:14)

之前我必须查看gcc 3.4的list :: size,所以我可以这样说:

  1. 它使用std :: distance(head,tail)
  2. std :: distance有两个实现:对于满足RandomAccessIterator的类型,它使用“tail-head”,对于仅满足InputIterator的类型,它使用依赖于“iterator ++”的O(n)算法,直到它为止击中给定的尾巴。
  3. std :: list不会使RandomAccessIterator饱和,因此大小为O(n)。
  4. 关于“为什么”,我只能说std :: list适用于需要顺序访问的问题。将大小存储为类变量会在每次插入,删除等时引入开销,并且根据STL的意图浪费是一个很大的禁忌。如果你真的需要一个常量时间大小(),请使用std :: deque。

答案 3 :(得分:11)

我个人不认为splice是O(N)的问题是允许大小为O(N)的唯一原因。 你不支付你不使用的东西是一个重要的C ++座右铭。在这种情况下,无论您是否检查列表的大小,维护列表大小都需要在每次插入/删除时额外增加/减少。这是一个很小的固定开销,但它仍然很重要。

很少需要检查列表的大小。从头到尾迭代而不关心总大小是无比多的。

答案 4 :(得分:4)

我会去source。 SGI的STL页面表示允许线性复杂性。我相信他们遵循的设计准则是允许列表实现尽可能通用,从而允许更灵活地使用列表。

答案 5 :(得分:1)

这个错误报告:[C++0x] std::list::size complexity,以极其详细的方式捕获GCC 4.x中的实现是线性时间以及C ++ 11的转换到恒定时间的过程如何缓慢(5.0中可用) )由于ABI兼容性问题。

GCC 4.9系列的联机帮助页仍包含以下免责声明:

  

仍然支持C ++ 11   实验性的,可能会在未来版本中以不兼容的方式发生变化。

此处引用了相同的错误报告:Should std::list::size have constant complexity in C++11?

答案 6 :(得分:0)

如果您正确使用列表,则可能没有注意到任何差异。

列表适用于您想要重新排列而无需复制的大数据结构,以及您希望在插入后保留有效指针的数据。

在第一种情况下它没有区别,在第二种情况下我更喜欢旧的(较小的)size()实现。

无论如何,标准更多地是关于正确性和标准行为以及“用户友好性”而不是原始速度。