有序元素的最佳容器

时间:2016-03-25 17:37:12

标签: c++ boost stl containers multiset

我正在开发一个时间关键型应用程序,我正在寻找最佳容器来处理以下类型的元素集合:

class Element
{
    int  weight;
    Data data;
};

考虑到我的应用程序的时间关键步骤,在一个独特的线程中定期执行,如下:

  • 从容器中提取Element weight data,并处理Element;
  • 新的weight的数字n> = 0,随机(*)Element被插入容器中。

容器的某些Element可能具有相同的重量。容器中任何时候元素的总数都很高,平均几乎是静止的(几十万)。上述提取/处理/插入序列所需的时间必须尽可能低。 (注意(*):新的权重实际上是根据数据计算的,但在这里被认为是随机的,以简化。)

在对不同的STL容器进行一些搜索和尝试之后,我最终使用了 std :: multiset 容器,其执行速度比命令 std :: vector 快约5倍,速度提高了16倍订购标准:列表。但是,考虑到我的应用程序的瓶颈仍然存在于提取/插入操作中,我想知道是否可以实现更好的性能。

请注意,虽然我只尝试了有序的容器,但我没有在我的要求中提到“有序容器”。我不需要在容器中订购<Style x:Key="myImageAnimateStyle"> <Style.Triggers> <EventTrigger RoutedEvent="FrameworkElement.Loaded"> <BeginStoryboard> <Storyboard> <DoubleAnimation Storyboard.TargetProperty="(UIElement.Opacity)" BeginTime="0:0:0" Duration="0:0:0.5" From="1.0" To="0.0" RepeatBehavior="Forever" AutoReverse="True"/> </Storyboard> </BeginStoryboard> </EventTrigger> </Style.Triggers> </Style> ,我只需要尽快执行“提取最低加权元素”/“插入新元素”操作。我不仅限于STL容器,如果适合的话,可以使用boost或任何其他实现。

谢谢你的帮助。

4 个答案:

答案 0 :(得分:3)

  

我不需要在容器中订购元素,我只需要尽快执行“提取最低加权元素”/“插入新元素”操作。

然后您应该vector<T>尝试priority_queue<T>,或对Element使用make_heap/push_heap/pop_heap操作。

由于您正在寻找最小堆,而不是最大堆,因此您需要提供一个自定义比较器,以相反的顺序对{{1}}个对象进行排序。

答案 1 :(得分:1)

我认为在STL中,懒惰的std::vector会给出最好的结果。

建议的伪代码可能如下所示:

  • 在向量的最后添加新元素
  • 仅当您想要最小元素时,对数组进行排序并获取第一个元素

通过这种方式,您可以获得vector的摊销插入时间,相对较少的内存分配和良好的缓存局部性。

答案 2 :(得分:1)

考虑不同的候选人以及您的假设将如何影响最终选择是有益的。当您的需求发生变化时,切换容器变得更加容易。

通常,大小为N的容器的基本访问/修改操作大致有3种复杂性类别:(摊销)O(1)O(log N)O(N)

您的第一个要求(找到最低权重元素)为您提供了大约三个具有O(1)复杂度的候选人,以及一个每个元素具有O(N)复杂度的候选人

  1. O(1) std::priority_queue<Element, LowestWeightCompare>

  2. O(1) std::multiset<Element, LowestWeightCompare>

  3. O(1) boost::flat_multiset<Element, LowestWeightCompare>

  4. O(N) std::unordered_multiset<Element>

  5. 第二个要求(随机插入新元素)为上述四个选项中的每一个提供了以下复杂度每个元素

    1. O(log N) std::priority_queue

    2. O(log N) std::multiset

    3. O(N) boost::flat_multiset

    4. O(1)

    5. 分摊std::unordered_multiset

      在前三个选项中,boost::multiset应该由大N的其他两个选项支配。在剩余的两个中,std::priority_queue优于std::multiset的缓存行为可能占上风。但是:衡量,衡量,衡量

      std::unorderd_multiset是否与其他三个竞争是先验不明确的。根据随机插入元素的数量nfind(1)-insert(n)的每批总费用O(N) search + O(n) insertionstd::unordered_multisetO(1) search + O(n log N) insertionstd::multiset。同样,衡量,衡量,衡量

      这些考虑因素对您的要求有多强大?如果您必须在每批中找到k最低权重元素,则故事会发生如下变化。然后你必须比较find(k)-insert(n)的费用。搜索成本大致会缩放为

        {li> O(k log N) std::priority_queue {li> O(1) std::multiset {li> O(1) boost::flat_multiset {li> O(k N) std::unordered_multiset

        请注意,priority_queue只能有效地访问top元素,而不是k个顶级元素,而不会实际调用pop(),每次调用的复杂度为O(log N)。如果您希望代码可能会从find(1)-insert(n)批处理模式更改为find(k)-insert(n),那么选择std::multiset或者至少记录哪种界面可能是个好主意它需要的改变。

        奖励:两全其美?!您可能还想尝试使用Boost.MultiIndex并使用类似的内容(查看文档以获得正确的语法)

        boost::multi_index<
            Element, 
            indexed_by<
                ordered_non_unique<member<Element, &Element::weight>, std::less<>>,
                hashed_non_unique<>
            >
        >
        

        上面的代码将创建一个基于节点的容器,该容器实现两个指针结构,以跟踪Element权重的排序,并允许快速哈希插入。这将允许O(1)查找最低权重Element,并允许O(n)随机插入n个新元素。

        对于大N,它应该比前面提到的四个容器更好地扩展,但同样,对于中等N,指针追逐到随机存储器中引起的缓存效应可能会破坏其理论上的优势{{1} 1}}。我是否提到了衡量,衡量,衡量的口号?

答案 3 :(得分:0)

尝试其中任何一种:

std::map<int,std::vector<Data>>

std::unordered_map<int,std::vector<Data>>

以上int是重量。

这两者都有不同的查找,删除和添加速度,具体取决于许多不同因素,例如元素是否存在。 (如果有,unordered_map .find更快,如果没有,map .find更快)