最近,我注意到有些人提到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)速度
size()
与Dinkumware一样具有复杂性
从VC6开始,可能不会改变这个事实。我在那儿吗?gcc
目前的情况如何?如果真的是O(n),为什么呢?
开发商选择这样做吗?答案 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)
你是正确的,标准没有说明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)导致拼接操作变为线性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,所以我可以这样说:
关于“为什么”,我只能说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()实现。
无论如何,标准更多地是关于正确性和标准行为以及“用户友好性”而不是原始速度。