【问题标题】:C++0x issue: Constant time insertion into std::setC++0x 问题:常量时间插入 std::set
【发布时间】:2011-09-27 14:35:06
【问题描述】:

根据this page,如果我使用我可以实现恒定时间插入

iterator std::set::insert ( iterator position, const value_type& x );

我直接提供的 position 迭代器“先于”正确的(按顺序)插入点。

现在我关心的情况是,如果我知道我插入的值在最后(因为它是最大的),例如:

set<int> foo = {1, 2, 3};
foo.insert(4); // this is an inefficient insert

根据上述标准,我应该将最后一个元素 foo.end()-1 传递给 insert foo.end()。我的理解正确吗?如果我通过foo.end() 会发生什么?是O(log n) 插入还是O(1) 插入。所以,选项是:

// Option A
foo.insert(foo.end()-1, 4);

// Option B
foo.insert(foo.end(), 4);

// Safer version of Option A
if(foo.empty())
    foo.insert(4);
else
    foo.insert(foo.end()-1, 4);

我问是因为我正在编写一个在容器上模板化的函数。我想在传入的任何容器的末尾插入一个元素(我知道它是最大的)。使用上面的“选项 A”对于像 vector 这样的容器有不同的行为:

foo.insert(foo.end()-1, 4);
// result is {1, 2, 3, 4} if foo is an std::set
// result is {1, 2, 4, 3} if foo is an std::vector

正如@Bo_Persson 所暗示的,这里的问题是 C++03 说“一般是对数,但如果 t 在 p 之后插入,则为摊销常数”。而 C++0x 表示“一般是对数,但如果 t 插入到 p 之前,则为摊销常数。”

PS:我在启用了 C++0x 支持的 Ubuntu 11.04 上使用 GCC 4.5。

编辑:我在启用和禁用 C++0x 支持以及 posted the results in an answer 的情况下运行了经验测试。基本上,结论是提供end() 作为插入提示同样好(而且显然更安全)。然而,这显然只是一个经验观察。如前所述,该标准在这方面仍然令人困惑。

【问题讨论】:

    标签: c++ stl insert set complexity-theory


    【解决方案1】:

    运行测试而不是阅读库规范是作弊吗?

    对于 g++-4.4 -O2 对于整数 0 &lt;= i &lt; 5000000 我的运行时间 标准插入是

    real    0m14.952s
    user    0m14.665s
    sys 0m0.268s
    

    我使用end() 作为提示插入的运行时间是

    real    0m4.373s
    user    0m4.148s
    sys 0m0.224s
    

    据我所知,在end() - 1 的插入速度一样快,但使用起来更麻烦,因为end() - 1 是非法操作(您必须使用operator--())并且如果设置发生它会崩溃为空。

    #include <set>
    
    typedef std::set<int> Set;
    
    void insert_standard(Set& xs, int x)
    {
        xs.insert(x);
    }
    
    void insert_hint_end(Set& xs, int x)
    {
        xs.insert(xs.end(), x);
    }
    
    int main()
    {
        const int cnt = 5000000;
        Set xs;
        for (int i = 0; i < cnt; i++) {
            // insert_hint_end(xs, i);
            insert_standard(xs, i);
        }
    }
    

    【讨论】:

    • 很好的测试。我假设这是启用了 C++0x 支持
    【解决方案2】:

    position 应该指向插入点之前还是之后并不完全清楚。一些实现适用于任何一种。

    另一方面,如果您想为不同的容器提供不同的行为,为什么不为您的函数编写两个重载,一个用于具有push_back 函数的容器,一个用于std::set

    【讨论】:

    • 最好检查几个当前的实现,看看它是如何处理的。我相信 GCC 发生了变化,他们从“检查几个相邻值”到只检查提示然后中止。但是由于无法评估end(),我会冒险猜测它可以用来成功提示插入比所有现有元素都大的元素;这将是一件明智且容易实施的事情。
    • 这里的一个问题是 C++03 说“一般是对数,但如果 t 插入到 之后 p 的右侧,则为摊销常数”。而 C++0x 说“一般是对数,但如果 t 插入到 之前 p 的右侧,则为摊销常数”。一个糟糕的编译器应该怎么做? :-)
    • @Bo Persson:我宁愿说,一个可怜的图书馆作家应该怎么做?他应该遵循他编码的标准,即使用 C++0x 标准作为库的 C++0x 风格(具有emplace* 方法和所有方法的库),这似乎是“显而易见的”。我认为这种变化是为了更容易地适应end() 的情况。
    • @Matthieu - 但这意味着破坏旧代码,这并不好。我认为这种变化可能是由于新的 C++0x 要求 multiset/map 以保持等效节点的插入顺序。
    • C++0x 版本修复了set&lt;int&gt; foo = {1, 2, 3}; foo.insert(some_iterator, 0),实际上可以将foo.begin() 用作some_iterator
    【解决方案3】:

    只提供一个在新值之后立即落入的迭代器才有意义。

    那是因为在 N 个元素的集合中,有 N+1 个可能的插入点。存在一个迭代器,它在之后一个值高于任何预先存在的元素,但是没有迭代器在所有元素之前的值之前

    【讨论】:

    • 我没有想到这一点。这是一种很好的表达方式。 std::set 的标准应该反映这一事实。
    【解决方案4】:

    跟随@antonakos 的脚步,我正在扩展“作弊”解决方案并进行实证测试。我正在使用 GCC 4.5 进行优化 (-02) 并考虑未启用 C++0x 支持和使用-std=c++0x 的情况。 40,000,000 次插入的结果如下(显示系统时间,因为本例中的其他值并不特殊):

    • 不支持 C++0x
      • 无提示:26.6 秒
      • 提示end()5.71 秒
      • 提示--end():5.84 秒
    • 启用 C++0x 支持
      • 无提示:29.2 秒
      • 提示end()5.34 秒
      • 提示--end():5.54 秒

    结论:当end()作为插入提示提供时,GCC(无论是否启用C++0x)插入效率很高。

    我使用的代码是基于@antonakos 的:

    #include <set>
    typedef std::set<int> Set;
    
    void insert_standard(Set & xs, int x) {
        xs.insert(x);
    }
    
    void insert_hint_end(Set & xs, int x) {
        xs.insert(xs.end(), x);
    }
    
    void insert_hint_one_before_end(Set & xs, int x) {
        xs.insert(--xs.end(), x);
    }
    
    int main() {
        const int cnt = 40000000;
        Set xs;
        xs.insert(0);
        for (int i = 1; i < cnt; i++) {
            //insert_standard(xs, i);
            //insert_hint_one_before_end(xs, i);
            insert_hint_end(xs, i);
        }
    
        return 0;
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2023-01-13
      • 1970-01-01
      • 2013-03-16
      • 2022-10-22
      • 2014-08-10
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多