C++ std::list中size()的时间复杂度

C++98中std::list的size()方法时间复杂度为O(N),但在C++11中,正常配置下时间复杂度优化为O(1)。这得益于_CXX11_ABI标志的使用,当该标志为1时,size()通过直接访问内部节点计数实现快速获取。然而,在需要兼容旧版库的场景下,如GCC7.3与GCC4.8混用,可能需要设_GLIBCXX_USE_CXX11_ABI=0,此时size()操作又将退化为O(N)。在性能敏感的场景中,需要注意这一差异。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

问题

C++中std::list的size()方法的时间复杂度是O(1)还是O(n)呢?

解答

C98中size()的时间复杂度是O(N), C++11中正常配置下,是O(1),其它情况下是O(N)

分析

查看GCC4.8.0的std::list源码

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

可以发现其时间复杂度为O(N)

C++11中,查看GCC7.3对应的代码, size()的实现如下:

/**  Returns the number of elements in the %list.  */
      size_type
      size() const _GLIBCXX_NOEXCEPT
      { return this->_M_node_count(); }

其中_M_node_count()的代码如下:

#if _GLIBCXX_USE_CXX11_ABI
      size_t _M_get_size() const { return *_M_impl._M_node._M_valptr(); }

      void _M_set_size(size_t __n) { *_M_impl._M_node._M_valptr() = __n; }

      void _M_inc_size(size_t __n) { *_M_impl._M_node._M_valptr() += __n; }

      void _M_dec_size(size_t __n) { *_M_impl._M_node._M_valptr() -= __n; }

      size_t
      _M_distance(const __detail::_List_node_base* __first,
		  const __detail::_List_node_base* __last) const
      { return _S_distance(__first, __last); }

      // return the stored size
      size_t _M_node_count() const { return *_M_impl._M_node._M_valptr(); }
#else
      // dummy implementations used when the size is not stored
      size_t _M_get_size() const { return 0; }
      void _M_set_size(size_t) { }
      void _M_inc_size(size_t) { }
      void _M_dec_size(size_t) { }
      size_t _M_distance(const void*, const void*) const { return 0; }

      // count the number of nodes
      size_t _M_node_count() const
      {
	return _S_distance(_M_impl._M_node._M_next,
			   std::__addressof(_M_impl._M_node));
      }
#endif

可以发现,当_GLIBCXX_USE_CXX11_ABI = 0时, size的时间复杂度是O(n),否则为O(1)。
通常情况下_GLIBCXX_USE_CXX11_ABI是非0的,也就是说C++11中 size的时间复杂度是O(1),但当系统中需要混用不同版本的GCC编译的库时,必须将_GLIBCXX_USE_CXX11_ABI 设置为0以保证ABI兼容,这种情况下 size的时间复杂度是O(n)。比如当前项目使用的是GCC7.3,但是系统中有用GCC4.8.0编译好的GTEST库,我们需要使用这个GTEST库,就必须将_GLIBCXX_USE_CXX11_ABI 设置为0,关于这点,在博客解决用gcc7.3链接gtest1.7的问题中提到过。

当list中的元素足够多的时候,O(N)的时间复杂度可能成为系统的瓶颈,在使用时一定要注意这点。

### C++ 中 `std::list` 使用教程 #### 创建和初始化 `std::list` 可以通过多种方式来创建并初始化一个 `std::list` 对象。最常见的方式是在声明时通过初始值列表来进行初始化。 ```cpp #include <iostream> #include <list> int main() { std::list<int> listOne{1, 2, 3, 4, 5}; } ``` 上述代码展示了如何利用大括号内的序列快速构建包含五个整数元素的列表[^2]。 #### 添加新元素到 `std::list` 对于 `std::list` 来说,添加新的成员是非常灵活的操作,既可以在头部也可以在尾部追加数据项;还可以指定某个具体的位置作为插入点。 ```cpp // 向前插入元素 listOne.push_front(0); // 向后插入元素 listOne.push_back(6); ``` 如果想要在一个确切位置处加入数值,则需先获取该位置对应的迭代器再调用 insert 函数: ```cpp auto it = listOne.begin(); advance(it, 3); // 移动至第四个位置之前 listOne.insert(it, 99); // 插入99于第四位前面 ``` 以上操作均能在接近恒定的时间复杂度 O(1) 下完成,体现了双端队列的优势所在[^3]。 #### 删除现有元素自 `std::list` 当需要从链表中去除某些项目时,同样提供了几种途径供开发者选用。比如直接清除整个容器内所有的条目,或是依据条件筛选出待移除的目标。 ```cpp // 清空全部内容 listOne.clear(); // 去掉首次出现的具体值 if (!listOne.empty()) { auto pos = find(listOne.begin(), listOne.end(), targetValue); if (pos != listOne.end()) listOne.erase(pos); } // 移除所有等于给定值得节点 listOne.remove(targetValue); // 根据谓词表达式过滤不符合要求的对象 listOne.remove_if([](const int& val){return val % 2 == 0;}); ``` 这里特别提到了 remove_if 方法的应用实例,它允许传入 lambda 表达式的逻辑判断语句以决定哪些成分应该被剔除出去[^5]。 #### 迭代遍历 `std::list` 由于 `std::list` 支持双向迭代特性,因此能够方便地从前向后或者反过来逐一遍历其中存储的数据单元。 ```cpp for(auto elem : listOne){ std::cout << elem << ' '; } ``` 这段简单的循环结构即实现了正序打印各元素的功能。而反方向则可通过 reverse_iterator 实现相同效果。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值