【问题标题】:How to efficiently delete elements from vector c++如何有效地从向量 C++ 中删除元素
【发布时间】:2015-06-03 05:34:31
【问题描述】:

我有一个由一对向量(V1,V2)组成的向量,称为pairV1V2,形式如下:

(1,2,3),(938,462,4837) -> (V1,V2)
(3,9,13),(938,0472,944)
(81,84,93),(938,84,845)

那我需要保留以下内容:

(1,2,3),(938,462,4837) -> (V1,V2)
(3,9,13),(938,0472,944)
(81,84,93),(84,845)

我需要从头开始扫描pairV1V2,如果任何两个V1 不相等,我需要从V2 中删除相交的元素。我写了下面的代码来做同样的事情。然而,我的代码效率非常低,因为我的向量对 V1V2 很大,而且它在 V2 中有很多元素(大约十亿)。

int main(int argc, char** argv) {
    std::vector<std::pair<std::vector<unsigned>, std::vector<unsigned> > > pairV1V2;
    std::vector<std::pair <std::vector<unsigned>,std::vector<unsigned> > >::iterator itm2,lm2=pairV1V2.end();
    for(std::vector<std::pair <std::vector<unsigned>,std::vector<unsigned> > >::iterator itm=pairV1V2.begin(), lm=pairV1V2.end(); itm!=lm; ++itm)
    {
        //Outer values
        vector<unsigned> outerV1=(*itm).first;
        vector<unsigned> outerV2=(*itm).second;
        sort(outerV2.begin(), outerV2.end());
        itm2=itm;
        itm2++;
        for(itm2;itm2!=lm2;++itm2)
        {
            vector<unsigned> innerV1=(*itm2).first;
            vector<unsigned> innerV2=(*itm2).second;
            vector<unsigned> setDiffV1;
            std::set_difference(innerV1.begin(), innerV1.end(), outerV1.begin(), outerV1.end(),
                                                      std::inserter(setDiffV1, setDiffV1.end()));            
            if(setDiffV1.size()==0) //check whether any two V1's are different
            {                 
                sort(innerV2.begin(), innerV2.end());
                if((itm->second.size()!=0)&&(itm2->second.size()!=0)){                                
                    std::vector<unsigned> delIntersectingElem;
                    std::set_intersection(outerV2.begin(),outerV2.end(),innerV2.begin(), innerV2.end(),
                              std::back_inserter(delIntersectingElem));

                   if(delIntersectingElem.size()!=0) //if there are intersecting V2's
                   {                    
                        for(std::vector<unsigned>::iterator its=(itm2->second).begin(),ls=(itm2->second).end();its!=ls;)
                        { 
                            //if *its is present in delIntersectingElem then delete it.
                            if(!(std::find(delIntersectingElem.begin(), delIntersectingElem.end(), (*its)) == delIntersectingElem.end()))
                            {
                                (itm2->second).erase(its); //delete intersecting elements from inner v2
                                ls--;
                            }else{
                                ++its;
                            }
                        }                    
                    }
                }
            } 
        }
    }    
    return 0;
}

有人可以帮我改进我现在的代码吗——它给出了正确的答案(在这个例子中,为了简洁起见,我可能遗漏了几个案例——但代码处理了所有这些)但是非常慢(因为对角化通过性能)。如果在我目前的代码中提出改进建议,我将不胜感激。但是,如果两个代码的逻辑相同,那么新的算法也是可以接受的

【问题讨论】:

  • 如果您要进行大量擦除和顺序访问,您是否考虑过使用 std::list?
  • @user4581301 好的..我不知道 std::list,你能告诉我如何使用 std::list 改进我目前的代码
  • 为了帮助改进代码,你真的应该访问codereview.stackexchange.com而不是堆栈溢出。
  • 您似乎应该复制构建外部和内部向量。这么多内存分配/释放真的有必要吗?
  • 使用列表在很大程度上取决于您在代码的其他部分中如何使用要从中删除的向量。如果您所做的只是添加到向量中,然后从中删除,那么列表带来的删除速度比向量所能提供的要快得多。如果您要进行筛选和排序,那么列表很糟糕。

标签: c++ c++11


【解决方案1】:

有一个未被充分利用的 STL 算法称为remove_if,它允许您有效地 (O(n)) 从容器中删除与谓词匹配的所有元素。如果您有vectordeque,则它最有用,因为它们对“中间”元素进行了昂贵的(O(n))擦除操作。但是,您需要注意 remove_if 实际上并没有删除任何元素,它只会将所有 匹配谓词的元素移动到您指定的范围的前面。因此,执行“erase_if”的规范方法是(在本例中,所有奇数都将被删除):


std::vector ints = …;
ints.erase(std::remove_if(begin(ints), end(ints), [](int i) { return i%2 != 0; }), end(ints));

解释:remove_if 将所有 匹配谓词的整数(即本例中的偶数整数)移到前面,并在最后一个元素之后返回一个迭代器。然后,我们实际上使用vector&lt;int&gt;::erase的范围重载擦除了从这个开始到向量末尾的所有元素。

例如,假设我们有ints == {5,7,4,10,9,16,20,6}remove_if 将把ints 变成{4,10,16,20,6,UNSPEC,UNSPEC,UNSPEC},我使用UNSPEC 来表示任何未指定的值,它还将返回一个指向第一个UNSPEC 元素的迭代器。然后,我们擦除所有未指定值的元素,得到{4,10,16,20,6},也就是我们想要的结果。

更新:关于上一个答案,我想指出remove_if 是稳定的,即它不会改变其余元素的顺序。

【讨论】:

    【解决方案2】:

    从向量中删除元素的最有效方法是反向交换技巧,但这仅适用于您不关心顺序的情况。

    #include <vector>
    #include <iostream>
    
    int main()
    {
        std::vector<int> v { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
        auto it = v.begin() + 5;
        // replace the current element with the back of the vector,
        // then shrink the size of the vector by 1.
        *it = std::move(v.back());
        v.pop_back();
    
        for (auto n : v) {
            std::cout << n << " ";
        }
        std::cout << "\n";
    }
    

    http://ideone.com/0jbWHZ

    如果你知道会有很多删除或一个非常大的向量,你可以通过使用这个技巧来保持效率,记住在删除之后不要 ++ 你当前的迭代器,并且std::sort()ing 向量当你到达终点。

    --- 编辑---

    #include <algorithm>
    #include <iostream>
    #include <vector>
    
    //! Efficiently remove an element from a vector without
    //! preserving order. If the element is not the last element
    //! in the vector, transfer the last element into its position
    //! using a move if possible.
    //! Regardless, we then shrink the size of the vector deleting
    //! the element at the end, which will either be destructed or
    //! the element we were deleting.
    //! @note: Effectively invalidates the current iterator.
    template<class ValueType>
    bool unstable_remove(
        typename std::vector<ValueType>& container,
        typename std::vector<ValueType>::iterator it
        )
    {
        // Leave in-situ if we are already the tail element.
        auto lastEl = container.end() - 1;
        if (it != lastEl) {
            // overwrite this element with what is in the last,
            // which should have the same effect as deleting this.
            *it = std::move(*lastEl);
        }
        // release the last cell of the vector, because it should
        // now either be destructed or contain the value we were
        // deleting.
        container.pop_back();
    }
    
    int main()
    {
        std::vector<int> ints { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
        auto it = ints.begin();
        while (it != ints.end()) {
            if ((*it % 3) == 0) {
                unstable_remove(ints, it);
                // do not pass go / ++it
                continue;
            }
            ++it;
        }
        std::cout << "after removes:\n";
        for (auto val : ints)
            std::cout << val << " ";
        std::cout << "\n";
        std::sort(ints.begin(), ints.end());
        std::cout << "after sort:\n";
        for (auto val : ints)
            std::cout << val << " ";
        std::cout << "\n";
    }
    

    生产 (http://ideone.com/hGZPOC)

    after removes:
    1 2 10 4 5 8 
    after sort:
    1 2 4 5 8 10 
    

    --- 编辑 2 ---

    为了便于阅读,这里对您的代码进行了清理,我还放弃了您的最终捕获,因为...您正在删除元素。

    #include <vector>
    #include <cstdint>
    
    using vec_t = std::vector<uint32_t>;
    using vecpair_t = std::pair<vec_t, vec_t>;
    using pairvec_t = std::vector<vecpair_t>;
    
    int main(int argc, char** argv) {
        pairvec_t pairV1V2;
        for(auto itm = pairV1V2.begin(); itm != pairV1V2.end(); ++itm)
        {
            //Outer values
            auto& outerV1 = itm->first; // NOTE '&' - reference not copy!
            auto& outerV2 = itm->second;
            sort(outerV2.begin(), outerV2.end());
            for(auto itm2 = itm + 1; itm2 != pairV1V2.end(); ++itm2)
            {
                auto& innerV1 = itm2->first;
                auto& innerV2 = itm2->second;
                vec_t setDiffV1;
    

    至于另一种优化方法 - 因为您的列表已排序 - 同时遍历两个列表比较值。

    template<typename ValueType>
    void dedupe_vectors(
        typename std::vector<ValueType>& lhs,
        typename std::vector<ValueType>& rhs
        )
    {
        auto lit = lhs.begin();
        auto rit = rhs.begin();
        while (rit != rhs.end) {
            while (lit != lhs.end() && *lit < *rit)
                ++lit;
            if (lit == lhs.end())
                break;
            if (*lit == *rit) {
                v2.erase(rit);
                continue;
            }  
            ++rit;
        }
    }
    

    我知道 - 我们测试了 litlhs.end 两次。看看你的编译器用 -O3 生成的代码,看看它自己是否没有检测到这一点。如果是这样,那么您可以担心对其进行优化。

    【讨论】:

    • 或者如果您不关心订单并且可以进行更改,请考虑std::unordered_set
    猜你喜欢
    • 1970-01-01
    • 2021-07-03
    • 2014-05-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-01-01
    • 1970-01-01
    相关资源
    最近更新 更多