【问题标题】:Data structure for storing elements in sorted order and allowing fast indexing?用于按排序顺序存储元素并允许快速索引的数据结构?
【发布时间】:2013-01-02 15:44:05
【问题描述】:

我需要一个排序和索引的容器。我想基于 std:set 或 std::vector 构建它。通过使用 set,我需要昂贵的 std::distance 来计算其元素的索引。通过使用向量,我知道添加或删除元素的成本很高。假设我的元素是一个指针(小对象)。我知道两个操作的复杂性是相同的。但是哪个更快?谢谢。

【问题讨论】:

  • 你试过测量它吗?此外,std::set 有其他限制(例如唯一值),std::vector 未排序。你想做什么?
  • 测量它。当查找次数比突变次数多得多时,排序向量可能可能更好,但理论上无法预测这一点。
  • 不要猜测,测量!提示:vector 具有较高的局部性
  • 这真的取决于使用的元素数量......
  • 为了节省您的时间,boost::flat_set。那里还链接了一篇您可能感兴趣的文章。

标签: c++ data-structures vector set


【解决方案1】:

如果您需要一个支持排序顺序和按索引查找的数据结构,您绝对应该查看 order statistic tree,它是专门为支持这些操作而设计的数据结构。它支持在 O(log n) 时间内插入和删除,在 O(log n) 时间内查找元素的索引,以及在 O(log n) 时间内按值或按索引查找,因此它应该比向量或集合方法。

不幸的是,STL 没有构建它的顺序统计树,因此您必须搜索第三方库 (this earlier question and answer provides an example of one)。也就是说,您应该期望从订单统计树中获得的加速应该值得投资。

希望这会有所帮助!

【讨论】:

    【解决方案2】:

    根据各位的建议,我测了下作为底层代码(flat_set加了)。调试版本的结果是

    设置:4.720976s 墙,4.711230s 用户 + 0.000000s 系统 = 4.711230s CPU (99.8%) (也测试了 set::insert 需要很少的时间)

    对于向量:1.407571s 墙,1.404009s 用户 + 0.000000s 系统 = 1.404009s CPU (99.7%)

    对于 flat_set:0.327714s 墙,0.327602s 用户 + 0.000000s 系统 = 0.327602s CPU (100.0%)

    和发布版本(我需要写出结果让编译器优化不会过度杀伤我的代码)每个版本的速度提高了大约 10 倍。

    我的结论是向量快 2-3 倍,而 boost flat_set 是最好的。对于少于数百个(例如 200 个)的条目数,flat_set 插入并不比 std::set 慢(即不计算索引)。

    int N = 10000;
    {
        boost::timer::auto_cpu_timer t;
        std::set<int> s;
        for (int i = 0; i < N; ++i)
        {
            auto it = s.insert(i);
            int index = std::distance(s.begin(), it.first);
        }
    }
    
    {
        boost::timer::auto_cpu_timer t;
        std::vector<int> v;
        for (int i = 0; i < N; ++i)
        {
            v.insert(v.begin(), i);
        }
    }
    
    {
        boost::timer::auto_cpu_timer t;
        boost::container::flat_set<int> s;
        for (int i = 0; i < N; ++i)
        {
            auto it = s.insert(-i); // negative sign is used to make insertion
                                    // (at the beginning) expensive
            int index = std::distance(s.begin(), it.first);
        }
    }
    

    【讨论】:

      【解决方案3】:

      将存储和索引分开:

      有一个整数向量 {I} 索引您的对象类型 {T} 的存储向量。

      {I} 通过比较它在 {T} 中指向的对象进行排序。 对 {I} 的插入/删除比对 {T} 的插入/删除便宜。

      每当您将新项目添加到 {T} 向量时,您都会将其索引插入到已排序的 {I} 中。

      当你删除一个项目时,删除 {I} 中的索引,但你可以保持 {T} 中的对象不变,只需将刚刚删除的索引 push_back 到重用向量 {I'} 中。下次添加新项目时,可以将 {I'} 中的索引 pop_back 并重用存储。

      如果您知道曾经使用过的项目数,您可以在启动时在 {T} 上调用 resize() 以避免在运行时(重新)分配。

      这种方法类似于使用指针向量,优点是动态分配更少,缓存更友好,因为对象存储在连续内存中。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2017-04-05
        • 1970-01-01
        • 2010-12-11
        • 1970-01-01
        • 2016-04-01
        • 2011-07-09
        • 1970-01-01
        相关资源
        最近更新 更多