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

2023-05-16

很奇怪的,或者说是一个不应成为问题的问题...
std::list 的 size() 方法时间复杂度是多少?第一感觉应该是 O(1) 没错吧,多一个变量用于储存链表长度应该是很轻易的事情。于是有了下面这段代码:

#include<iostream>
#include<list>
#include<ctime>
using namespace std;

int main ( ) {
    time_t start, finish;
    int num = 0;
    list<int> coll;

    start = clock ( );
    for ( int i= 0;i< 10000;++i ) {
        coll. push_back (i );
        num += coll. size ( );
    }
    finish = clock ( );
    cout<<finish - start<<  num:"<<num<<endl;

    coll. clear ( );
    start = clock ( );
    for ( int i= 0;i< 10000;++i ) {
        coll. push_back (i );
    }
    finish = clock ( );
    cout<<finish - start<<endl;
    return 0;
}

对两个循环分别计时比较。前一个循环只比后一个多了一句 num += coll.size(); 为了使编译器确实生成 list::size() 的代码。
在 MinGW 5.1.4 中 (GCC 3.4.5) 编译结果运行如下:

450   num:50005000
10

可以看到,前一个循环居然比后一个多花了几乎 45 倍的时间...当我把循环次数从 10000 加到 100000 时程序半天没出结果...

由此有理由猜测 std::list 的 size() 方法难道是 O(N) 的?果然,在头文件中发现了这一段:

size_type
size ( ) const
{ return std:: distance (begin ( ), end ( ) ); }

 

直接调用 <algorithm> 算法库函数 distance() 计算元素个数……怪不得这么慢。然后又用 VS2008 (VC9.0)编译,结果如下:

30   num:50005000
60

奇怪的是前一个循环居然比后一个还快...不过至少知道 VS2008 (VC9.0)里的 size() 应该是 O(1) 的。同样查看了一下代码,如下:

size_type size ( ) const
    {    // return length of sequence
    return (_Mysize );
    }

_Mysize 是一个 size_type 类型的变量。疑问解决。不过又有了新问题:

--------------- 咱 -- 是 -- 分 -- 隔 -- 线 ------------------

为什么 GCC 里要把 list::size() 的复杂度搞成 O(N)?

一通搜索后终于看到有这样的讨论:关于 list::splice() 函数。

list 是链表结构,它的优势就在于可以 O(1) 的时间复杂度任意插入删除甚至拼接 list 片段(删除时可能不是,因为要释放内存),list::splice() 是一个很强大的功能,它可在任意位置拼接两个 list,这正是 list 的优势。如果我们在类内部以一个变量储存 list 的长度,那么 splice() 之后新 list 的长度该如何确定?这是一个很严峻的问题,如果要在拼接操作时计算拼接部分的长度,那么将把 O(1) 的时间变成 O(N),这么一来 list 相对 vector 的优势就消失殆尽。

面对这个问题,GCC 和 VC 的 STL 库作者们做了不同的选择。GCC 选择舍弃在 list 内部保存元素数量,而在 size() 时直接从头数到尾,这便出现了开头看到的 O(N) 时间才算出 size();相反,VC 中有了变量 _Mysize ,无论在 insert() erase() splice() 或是 push() pop() 时都需要对其做相应修改。在上面的两个试验中已经看出同样是 10000 个 push_back() 操作,VC 花的时间比较长,不过也仅仅是一个 inc 指令,差别很小就是了。上面几种会改变 list 内容的操作中,大部分对元素数量的影响只是 +1 或 -1,只有 splice() 需要计算拼接部分元素个数,这个差别就大了,咱还是继续用实验证明吧:

#include<iostream>
#include<list>
#include<ctime>
using namespace std;

int main ( ) {
    time_t start,finish;
    list<int> col;
    col. push_back ( 1 );
    col. push_back ( 10000 );

    list<int> col2;
    start = clock ( );
    for ( int i= 2;i< 10000;++i )
        col2. push_back (i );
    finish = clock ( );
    cout<<finish - start<<endl;

    int num = 0;
    start = clock ( );
    for ( int i= 0;i< 10000;++i ) {
        col. splice (++col. begin ( ),col2,++col2. begin ( ),--col2. end ( ) );
        num += * (++col. begin ( ) );
        col2. splice (++col2. begin ( ),col,++col. begin ( ),--col. end ( ) );
        num += * (++col2. begin ( ) );
    }
    finish = clock ( );
    cout<<finish - start<<  num:"<<num<<endl;
    return 0;
}

首先是 MinGW (GCC 3.4.5) 的结果:

10
 num:60000

可以看到 10000 次 push 是 10,相对的 20000 次 splice() 几乎没花时间 = =

然后是 VS2008 (VC9.0):

20
2714   num:60000

差别非常明显,花了2秒多才完成。当我把循环次数改成 100000 后 GCC 仍是眨眼间的事,VC 却长时间运行无结果……

怎么说呢,GCC 显然是追求效率至上,尽量体现出 list 的优势所在,不过我觉得这么一来倒不如干脆不提供 list 的 size() 方法,有需求的程序员可以自己维护一个变量记录长度,以免误认为 size() 是 O(1) 的而犯下严重错误。相对的 VC 强调功能性和整体效率,可能在实际中需要对链表一段内容做 splice() 操作的机会远远小于求 size() 的操作,所以舍弃前者而保留后者,不过要维护 _Mysize 其他相关函数中也增加了开销。一个见仁见智的问题,我觉得还是 GCC 的选择比较好,list 的优势应该保留,但能在 size() 函数处给个 warning 什么的就好了。

我想还有一个选择是这样:在 list 内部用一个 bool 变量指示当前内部 size 值是有效还是无效。在通常操作时 bool 保持 true,这样在 size() 时直接返回原值即可;在 splice() 后将此 bool 值置为 false 并不计算长度,直到最后又有需要 size() 时发现 bool 是 false 则从头再来一遍 distance() 并再将 bool 置为 true。暂时只想出这么一个算是折中的方法,基本上都能保持两边 O(1) 的效率,但相应其他各关于元素数量的函数内部都要多一个判断当前 size 值是有效还是无效并选择是否改变其值。反正总是不能非常完美

嘛...本来只是发现 size() 的效率问题,没想到却扯出这么一桩事出来...也算长知识了吧


http://blog.sina.com.cn/s/blog_476a25110100magc.html

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

std::list 中 size() 的时间复杂度 的相关文章

  • Java 中的撤消和重做实现

    我想问一个关于Java中List的问题 很容易实现列表中元素的删除 添加和搜索 但是Java中如何实现列表的撤销和重做呢 谁可以帮我这个事 您可能希望实施一个Command Design Pattern为了这 一个不错的简化示例List可以
  • 在 Python 中使用 .split() 和 .join()

    我目前正在 Treehouse 中学习一些 Python 但我遇到了这个挑战 并且不知道我做错了什么 挑战分为三个部分 如下所示 包含提示和我编写的代码 我好像在第三部分犯了错误 Part 1 我想是时候吃点零食了 幸运的是 我有一串各种各
  • Python选择列表中最长字符串的最有效方法?

    我有一个可变长度的列表 并且正在尝试找到一种方法来测试当前正在评估的列表项是否是列表中包含的最长字符串 我正在使用Python 2 6 1 例如 mylist abc abcdef abcd for each in mylist if co
  • Java 阻止列表实现

    我在 SO 和 Google 上搜索了这个问题的答案 但到目前为止找不到合适的解决方案 我目前正在研究图形路由问题中的 LayerManager 管理器负责提供和重置一组固定的层 我想使用阻止列表来实现消费者 生产者模式 以便只要没有可用的
  • 省略号列表[...]并将列表连接到自身[重复]

    这个问题在这里已经有答案了 EDIT 我在最初的例子中很粗心 当我添加列表时不会发生该行为A本身 而是当我添加一个列表时含有 list A to A本身 请参阅下面更正的示例 我试图理解省略号如何列出 那些显示为 当你有一个列表引用本身时发
  • 如何使用 CUDA/Thrust 对两个数组/向量根据其中一个数组中的值进行排序

    这是一个关于编程的概念问题 总而言之 我有两个数组 向量 我需要对一个数组 向量进行排序 并将更改传播到另一个数组 向量中 这样 如果我对 arrayOne 进行排序 则对于排序中的每个交换 arrayTwo 也会发生同样的情况 现在 我知
  • 列表到优先队列

    我有一个 C 大学编程项目 分为两个部分 在开始第二部分时应该使用priority queues hash tables and BST s 我 至少 在优先级队列方面遇到了麻烦 因为它迫使我自己重做第一部分中已经实现的许多代码 该项目是关
  • 使用 Linq 返回具有最大计数的列表

    使用 C 和 Linq 如何返回具有最大大小 计数的 List 我假设您有一个名为的列表集合lists并且您想要返回此集合中元素最多的列表 如果是这样 请尝试以下操作 var listWithLargestCount lists Order
  • 如何在 LINQ 中执行 String.Replace?

    这是我正在尝试做的事情 但没有成功 我想打电话from x in list1 and join y in list2 where regex Match x Value Success 完成这些步骤后我需要打电话String Replace
  • Python 有不可变列表吗?

    python 有不可变列表吗 假设我希望具有元素有序集合的功能 但又想保证它不会改变 如何实现呢 列表是有序的 但它们可以改变 是的 它被称为一个tuple 所以 而不是 1 2 这是一个list并且可以突变 1 2 is a tuple并
  • 在不同进程之间共享列表?

    我有以下问题 我编写了一个函数 它将列表作为输入 并为列表中的每个元素创建一个字典 然后我想将这本字典附加到一个新列表中 这样我就得到了一个字典列表 我正在尝试为此生成多个进程 我的问题是 我希望不同的进程访问由其他进程更新的字典列表 例如
  • 如何在 switch 语句中将向量作为参数传递

    我对问题的谷歌搜索没有返回有用的结果和文档 switch没有告诉我如何做 所以我希望我能在这里得到答案 假设我有一个向量 cases lt c one two three 我想使用 switch 语句并将这些元素作为 switch 语句的参
  • c++11 中的 std::thread 问题

    我在尝试从标准模板库编译具有多线程的程序时遇到一些麻烦 当我尝试编译以下程序时 它返回一个晦涩的错误 include
  • 如何将列表转换为元组列表?

    我想转换 z z a z z a a z to z 2 a 1 z 2 a 2 z 1 我该怎么做 所以 我需要累积以前的值 它的计数器和元组列表 我已创建记录 record acc previous counter tuples 重新定义
  • 分配列表的多个值

    我很想知道是否有一种 Pythonic 方式将列表中的值分配给元素 为了更清楚 我要求这样的事情 myList 3 5 7 2 a b c d something myList So that a 3 b 5 c 7 d 2 我正在寻找比手
  • Python 将列表追加到列表中

    我正在尝试编写一个通过矩阵的函数 当满足条件时 它会记住该位置 我从一个空列表开始 locations 当函数遍历行时 我使用以下方法附加坐标 locations append x locations append y 函数末尾的列表如下所
  • 如何将标准库与C++模块一起使用? (例如:“导入 std.io”)

    中给出的基本示例如何在 Clang 中使用 C 模块 https stackoverflow com questions 33307657 how do i use c modules in clang对我有用 但不导入标准库 例如通过im
  • Scala 中的随机列表[重复]

    这个问题在这里已经有答案了 我对 scala 中的随机播放列表有疑问 使用scala util Random 例如我有 val a cyan val b magenta val c yellow val d key val color Ra
  • 当顺序很重要时如何从元组列表中删除重复项

    我看过一些类似的答案 但我找不到针对这种情况的具体内容 我有一个元组列表 5 0 3 1 3 2 5 3 6 4 我想要的是仅当元组的第一个元素先前出现在列表中并且剩余的元组应该具有最小的第二个元素时 才从该列表中删除元组 所以输出应该是这
  • Prim 的迷宫生成算法:获取相邻单元格

    我基于 Prim 算法编写了一个迷宫生成器程序 该算法是 Prim 算法的随机版本 从充满墙壁的网格开始 选择一个单元格 将其标记为迷宫的一部分 将单元格的墙壁添加到墙壁列表中 While there are walls in the li

随机推荐

  • android Linphone SDK

    LinphoneLauncherActivity 是APP的入口组件 xff0c 在这个组件里 xff0c 它会启动LinphoneService这个后台服务 xff0c 然后不断地判断这个后台服务是否已经启动完毕 xff0c 如果已经启动
  • 安装应用提示与已安装应用签名不同

    出现这个情况 xff1a 1 如果你已经安装了 xff0c 就先卸载已经安装的应用再安装 xff1b 2 如果本地没有安装 xff0c 去文件管理中找与安装应用相同包名的文件夹删除 xff1b 3 以上都没问题的话 xff0c 你手机是不是
  • 使用Glide 设置ImageView 简单的圆形图片

    RequestOptions mRequestOptions 61 RequestOptions circleCropTransform 设置圆形图片 以下是额外设置一些配置 diskCacheStrategy DiskCacheStrat
  • 用 dism 合并与删除 wim 映像

    一 合并 假设 installA wim 有 3 个映像 xff0c installB wim 有 1 个映像 1 全部合并 将 installA wim 中的 3 个卷映像合并到 installB wim 中 xff0c 这样 insta
  • Manjaro XFCE桌面安装I3-wm

    运行两条命令 xff1a sudo pacman S i3 manjaro sudo pacman S i3 manjaro resolving dependencies looking for conflicting packages m
  • java学习一路总结

    1 源码包和jar包的区别 从spring网站down下最新的spring源码包spring framework 2 0 rc1 with dependencies zip 原以为直接放到工程里就可以用了 其实不然 真正要用到的是里面dis
  • 超级好看的windows终端美化教程

    mac使用强大的 oh my zsh 先上效果图 Powershell美化官方教程 官方教程比较简单 xff0c 可以设置图片 xff0c 模糊背景 xff0c 改变颜色 xff0c 不过可以作为oh my posh基础 如果不需要直接略过
  • LAMP源码编译安装之Apache

    LAMP源码编译安装之Apache 一 LAMP的基本架构概述1 LAMP架构2 各组件的主要作用如下 二 编译安装Apache httpd服务1 关闭防火墙 xff0c 将安装Apache所需软件包传到 opt目录下2 安装环境依赖包3
  • 算法

    从一个数组中找出 N 个数 xff0c 其和为 M 的所有可能 span class token comment 参数依次为目标数组 选取元素数目 目标和 span span class token keyword const span s
  • Android漏洞挖掘第三期:客户端完整性未校验

    引言 xff1a 每一期都有相同的内容部分 xff0c 主要为了让大家单独看一期依旧能看懂 xff01 xff01 xff01 0x01 APK文件 依然从APK文件开始说起 xff0c 相信大家看我之前的帖子 xff0c 已经知道APK文
  • 查看LIBC版本

    如果题目提供了 so文件 xff0c 可以尝试直接从 so文件中获取GLIBC的版本 strings so span class token operator span span class token function grep span
  • 荔枝派 Nano 全志 F1C100s 编译运行 Linux 笔记

    首先是荔枝派的官方文档 xff0c 写的不是很细 xff0c 应当说我们必须明确几点 xff1a 出厂时 SPI Flash 自带了一个 U Boot 43 Linux Kernel xff08 出厂的时候可能烧过了 xff09 xff0c
  • Linux安装火狐并使用国内书签

    span class token function wget span qO span class token string 39 https download mozilla org product 61 firefox esr late
  • 构建 Kubernetes 文档

    访问 kubernetes io 实在是有点慢 xff0c 所以决定自行构建 span class token comment Install HUGO if not installed span span class token comm
  • 使用 vuetify

    Vuetify 是一个非常优秀的前端组件库 xff0c 天生的响应式和 Material Design 风格 2022 11 01 终于迎来了 Vuetify 3 0 xff0c 完整支持了 Vue 3 语法 对于现有使用 VueCLI 和
  • VB6.0中提示:该部件的许可证信息没有找到,在设计环境中,没有合适的许可证使用该功能”的解决办法

    用VB6 0中的某些控件时总是提示 该部件的许可证信息没有找到 xff0c 在设计环境中 xff0c 没有合适的许可证使用该功能 xff01 xff08 主要是因为VB6 0精简版 xff09 具体解决方法 xff1a 这里需要一个工具 x
  • 实现黑客帝国数字雨效果

    今日闲得慌 xff0c 折腾了一个黑客帝国数字雨效果 xff0c 还蛮不错的 操作 xff1a 新建一个文本文档 xff0c 输入 以下代码 xff0c 再将扩展名修改为 Bat xff0c 运行即可 命令提示符代码 xff1a xff08
  • Android Studio电脑不支持HAXM的解决办法

    测试APP时出现以下错误信息 xff1a Intel HAXM is required to run this AVD Your CPU does not support required features VT x or SVM Unfo
  • Relocations in generic ELF (EM: 62) 错误的解决方案

    Android studio 或者 xcode 使用第三方库时可能出现这个问题 xff0c could not read symbols File in wrong format 这是由于自己编译的 a 静态库 或 so 动态库 与目标平台
  • std::list 中 size() 的时间复杂度

    很奇怪的 xff0c 或者说是一个不应成为问题的问题 std list 的 size 方法时间复杂度是多少 xff1f 第一感觉应该是 O 1 没错吧 xff0c 多一个变量用于储存链表长度应该是很轻易的事情 于是有了下面这段代码 xff1