【问题标题】:std::set erase complexity anomality?std::set 擦除复杂性异常?
【发布时间】:2014-04-04 07:13:05
【问题描述】:

我试图弄清楚从 std::set 中擦除多个元素的复杂性。我使用this page 作为来源。

它声称使用迭代器擦除单个项目的复杂度摊销为 O(1),但使用范围形式擦除多个项目是 log(c.size()) + std::distance(first, last) (即 - 集合大小的日志 + 删除的元素数)。

从表面上看,如果要擦除的元素数(n)远小于集合中的元素数(m),这意味着循环遍历要擦除的元素并以一个时间比一次调用擦除它们要快(O(n))(O(log m)假设n

显然,如果真的是这样的话,第二种形式的内部实现只会执行上述循环。

这是网站的错误吗?规格中的错误?我只是错过了什么吗?

谢谢, 沙查尔

【问题讨论】:

  • 使用范围形式擦除多个项目是 log(c.size()) + std::distance(first, last) (即 - 集合大小的对数 + 元素数已删除)。 - 固定集的大小与 O(n) 完全一样,其中 n 是删除的元素数,这是您从一个一个删除它们中得到的。
  • 这很有趣,我想您可以通过两种方式测试它以查看差异。在每次单独擦除后重置迭代器可能会有一些开销(因为我认为这会使迭代器无效)
  • @Cthulhu,只要在复杂性中使用加号,同样的逻辑也适用。任何你假设为常数(甚至是有界)的东西都会自动具有 O(1) 的复杂度。

标签: c++ stl time-complexity stdset


【解决方案1】:

集合的内部元素存储在平衡二叉树中。平衡树是指任意节点左右子树高度差最大为1的树。

保持平衡结构对于确保在最坏情况下搜索树(集合中)的任何元素都采用O(log(n)) 步骤很重要。

删除一个元素可能会破坏平衡。要恢复平衡,必须进行旋转。在某些情况下,一次删除会导致多次旋转,因此操作需要 O(log(n)) 步,但平均而言,一次删除需要 O(1) 步。

因此,当必须逐个删除分散在集合中的多个元素时,每次删除的摊销成本很可能为O(1)

删除范围内的几个元素(first, last,其中一个元素紧随其后)几乎肯定会破坏平衡,是什么导致复杂性中的日志因素:log(n) + std::distance(first, last)

【讨论】:

    【解决方案2】:

    似乎问题隐藏在“摊销”这个(有点狡猾的)词背后。单项擦除具有 log(c.size()) 的 O 复杂度,但 O(1) 的摊销复杂度。

    因此,在循环中执行多个单次擦除将花费 log(c.size()) + 擦除次数,这正是范围形式的复杂性。

    沙查尔

    【讨论】:

    • “摊销”不是一个狡猾的词。这是一个成熟的计算机科学术语:en.wikipedia.org/wiki/Amortized_analysis
    • 好吧,“鼬鼠”有点苛刻。我确实认为放弃严格的上限声明会造成混乱。如果您只提供操作的摊销复杂性,您通常会剥夺程序员了解预期内容的能力。我同意仅使用 O 复杂度可能同样具有误导性(如此处所示),但我认为标准应该同时提及两者。
    猜你喜欢
    • 2012-01-19
    • 2018-07-18
    • 2015-10-04
    • 2011-03-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-05-22
    • 2017-08-06
    相关资源
    最近更新 更多