C++ 应该使用插入排序或构造堆来提高性能?

C++ 应该使用插入排序或构造堆来提高性能?,c++,algorithm,sorting,stl,boost,C++,Algorithm,Sorting,Stl,Boost,我们有结构的大(100000+个元素)有序向量(运算符您可能需要考虑使用 Read()/以避免对整个向量进行过度的重新分配。如果您知道它将增长到什么程度,您可以通过自己执行resrve()s来获得一些性能(而不是让Implementation使用内置的启发式自动执行) 执行二进制搜索以查找插入位置。然后调整大小并将插入点之后的所有内容向上移动1以腾出空间 思考:你真的想使用向量吗?也许集合或映射更好 与下界相比,二进制搜索的优势在于,如果插入点接近向量的末端,则不必支付θ(n)复杂度。使用二进制

我们有结构的大(100000+个元素)有序向量(运算符<重载以提供排序):

这并不完全是我们的代码看起来的样子,因为我知道这个示例可以通过不同的方式进行优化,但是它让您了解性能可能是一个问题,因为我在每次
推回
后进行排序

我认为我基本上有两种选择来提高性能:

  • 使用(手工制作的?)插入排序,而不是
    std::sort
    ,以提高排序性能(在部分排序的向量上进行插入排序非常快)

  • 使用
    std::make_heap
    std::push_heap
    来创建堆,以维护排序顺序

  • 我的问题是:

    • 我应该实现插入排序吗?Boost中有什么东西可以帮助我吗?

    • <强>我应该考虑使用堆吗?我该怎么做?


    编辑:

    谢谢你的回复。我知道我给出的示例远远不是最优的,它不能完全代表我现在代码中的内容。这仅仅是为了说明我所经历的性能瓶颈——也许这就是为什么这个问题没有获得很多赞成票的原因:)

    多亏了你,最简单的答案往往是最好的,也许是我对问题的过度分析让我看不到最明显的解决方案。我非常喜欢您概述的直接插入到预排序向量的简洁方法


    正如我所评论的,我现在只能使用向量,所以std::set、std::map等都不是一个选项。

    为什么不使用二进制搜索来查找插入新元素的位置呢?然后您将准确地插入到所需位置。

    有序插入不需要升压:

    vectorMyTypes.insert(
        std::upper_bound(vectorMyTypes.begin(), vectorMyTypes.end(), newType),
        newType);
    
    upper\u bound
    提供了一个有效的插入点,前提是向量从开始就进行了排序,因此只要您只在元素的正确位置插入元素,就完成了插入。我最初说的是
    下限
    ,但是如果向量包含多个相等的元素,那么
    上限
    选择需要较少工作的插入点

    这确实需要复制O(n)个元素,但您说插入排序“快得惊人”,而且速度更快。如果不够快,您必须找到一种方法来批量添加项目并在最后进行验证,或者放弃连续存储并切换到维护顺序的容器,例如
    set
    multiset

    堆不维护底层容器中的顺序,但适合优先级队列或类似队列,因为它可以快速删除最大元素。你说你想保持向量的顺序,但是如果你从来没有按顺序迭代过整个集合,那么你可能不需要对它进行完全排序,而这正是堆有用的时候。

    为什么不使用


    注意:
    boost::multi_index
    不提供内存连续性,这是
    std::vectors
    的一种属性,通过该属性,元素在单个内存块中彼此相邻地存储。

    您需要做几件事

    >P>您可能需要考虑使用<代码> Read()/<代码>以避免对整个向量进行过度的重新分配。如果您知道它将增长到什么程度,您可以通过自己执行
    resrve()
    s来获得一些性能(而不是让Implementation使用内置的启发式自动执行)

  • 执行二进制搜索以查找插入位置。然后
    调整大小
    并将插入点之后的所有内容向上移动1以腾出空间

  • 思考:你真的想使用向量吗?也许
    集合
    映射
    更好


  • 与下界相比,二进制搜索的优势在于,如果插入点接近向量的末端,则不必支付θ(n)复杂度。

    使用二进制搜索查找插入位置不会大大加快算法的速度,因为插入仍然是O(n)(考虑在向量的开头插入-必须将每个元素向下移动一个以创建空间)

    树(又名heap)将被O(log(N))插入,性能更好


    请注意,除非是平衡的树(例如AVL树),否则树在插入时仍将具有最差的O(N)性能。

    如果需要将大量元素插入到已排序的序列中,请使用
    std::merge
    ,可能先对新元素排序:

    void add( std::vector<Foo> & oldFoos, const std::vector<Foo> & newFoos ) {
        std::vector<Foo> merged;
        // precondition: oldFoos _and newFoos_ are sorted
        merged.reserve( oldFoos.size() + newFoos.size() ); // only for std::vector
        std::merge( oldFoos.begin(), oldFoos.end(),
                    newFoos.begin(), newFoos.end(),
                    std::back_inserter( merged );
        // apply std::unique, if wanted, here
        merged.erase( std::unique( merged.begin(), merged.end() ), merged.end() );
        oldFoos.swap( merged ); // commit changes
    }
    
    void add(std::vector和oldFoos,const std::vector和newFoos){
    std::向量合并;
    //前提条件:oldFoos_和newFoos_已排序
    merged.reserve(oldFoos.size()+newFoos.size());//仅适用于std::vector
    合并(oldFoos.begin(),oldFoos.end(),
    newFoos.begin(),newFoos.end(),
    std::背面插入器(合并);
    //如果需要,请在此处应用std::unique
    merged.erase(std::unique(merged.begin()、merged.end()、merged.end());
    oldFoos.swap(合并);//提交更改
    }
    
    根据Meyers的有效STL第23项,如果应用程序在3个阶段使用其数据结构,则应使用排序向量。从本书中可以看出,它们是:

  • 设置。通过向其中插入大量元素来创建新的数据结构。在此阶段,几乎所有操作都是插入和擦除。在不存在的数据结构上很少进行查找
  • 查找。查阅数据结构以查找特定的信息。在此阶段,几乎所有操作都是查找。插入和擦除很少或根本不存在。查找太多,此阶段的性能会导致其他阶段的性能意外
  • <
    vectorMyTypes.insert(
        std::upper_bound(vectorMyTypes.begin(), vectorMyTypes.end(), newType),
        newType);
    
    void add( std::vector<Foo> & oldFoos, const std::vector<Foo> & newFoos ) {
        std::vector<Foo> merged;
        // precondition: oldFoos _and newFoos_ are sorted
        merged.reserve( oldFoos.size() + newFoos.size() ); // only for std::vector
        std::merge( oldFoos.begin(), oldFoos.end(),
                    newFoos.begin(), newFoos.end(),
                    std::back_inserter( merged );
        // apply std::unique, if wanted, here
        merged.erase( std::unique( merged.begin(), merged.end() ), merged.end() );
        oldFoos.swap( merged ); // commit changes
    }