5 如何高效删除C++ vector中所有下标为偶数的元素?

引言

如何高效删除C++ vector中所有下标(从0开始或者从1开始都可以,本文默认从0开始)为偶数的元素?看似简单的问题,实际包含对容器vector的理解以及对STL库函数的使用熟练度考察。

方法一,普通的倒序删除

对于vector容器,如果从前往后删除,vector会保持内部数据的顺序以及地址联系性,会导致后续的元素往前移动,让迭代器或索引失效。采用从后往前删除的方式,其后的元素已被处理,不会影响后续的元素。

#include <iostream>
#include <vector>
using namespace std;

int main() {
    vector<int> vec = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
    for (int i = vec.size() - 1; i >= 0; i--) {
        if (i % 2 == 0) {
            vec.erase(vec.begin() + i);
        }
    }
    for (int num : vec) {
        cout << num << " ";
    }
    return 0;
}

错误示范

删除所有的奇数,但由于删除时会导致后续元素的移动,最后得到的答案与预期的是不一致的,删掉第一个元素时,i自增到1,而这时元素往前移动变成{3, 5, 7, 9},于是第二次删除将跳过元素3,直接删除元素5,最终得到的答案为{3, 7},这与我们的目标是不一致的。

#include <iostream>
#include <vector>
using namespace std;

int main()
{
    std::vector<int> v = {1, 3, 5, 7, 9};

    for (size_t i = 0; i < v.size(); ++i)
    {
        if (v[i] % 2 == 1)
        {
            v.erase(v.begin() + i); // 删除奇数
            // i 不减回去,导致元素“跳过”
        }
    }
    for (auto &e : v)
        cout << e << " ";
    return 0;
}

方法二,使用 remove_if + erase

使用库函数,传统的删除其实是removeerase,如:

// 删除vector中所有的元素3
v.erase(std::remove(v.begin(), v.end(), 3), v.end());

在这里我们需要注意的是:
std::remove不真的删除元素,而是把不需要删除的元素搬到前面去,返回一个新的“逻辑末尾”迭代器,配合std::erase才能做到元素的真正删除。

remove的设计理由

remove的使用往往令人困惑,它的名字是具有迷惑性的,remove却不真的remove,只是将需要去除的元素往后移,这种设计我们可以给出以下解释:

erase的时间复杂度是O(n),如果我们遍历整个容器,对每个元素进行判断来进行erase,可想而知,最坏的情况下,时间复杂度能达到O(n^2)!

这是我们所不能接受的,于是我们在这基础上把想要的删除的元素通通搬移到容器末尾,最后统一erase。一次remove的代价是O(n),再加上erase的O(n),两者相加还是O(n)。

这里我没要求删除索引为偶数的值,需要对remove增加一个lambda条件判断,可以使用remove_if

代码示例如下:

vector<int> vec = {1, 3, 5, 7, 9};

// 使用 lambda 表达式和捕获下标
int index = 0;
vec.erase(remove_if(vec.begin(), vec.end(),
    [&index](const int&) { return index++ % 2 == 0; }), vec.end());

这里我们可以用一种更加简洁的方式,直接捕获容器vec的地址,定义一个容器地址值,与vec[0]的地址相减对2取余也可判断出索引的奇偶值。代码如下:

vec.erase(remove_if(vec.begin(), vec.end(),
        // 注意这里用的 auto& 而不是 auto
        [&vec] (auto& num) { return (&num - &vec[0]) % 2; }), vec.end());

方法三,使用C++20特性,erase_if

自C++20起,STL提供了std::erase_if,直接一步到位,其时间复杂度也是O(n)。代码如下:

vec.erase_if(vec, [vec] (auto& num) { return (&num - &vec[0]) % 2== 0; });

方法四,循环与move配合

代码如下:

// arr:   10 20 30 40 50 60
// index: 0  1  2  3  4  5
// vec[0] = vec[1]
// vec[1] = vec[3]
// vec[2] = vec[5]
// 可以看出 i 应该从 1 开始
for (auto i = 1; i < vec.size(); i += 2) {
    vec[i / 2] = move(vec[i]);
}
vec.resize(vec.size() / 2);

参考文章

1、如何高效删除 C++ vector 中所有下标为偶数的元素?

posted @ 2025-06-11 10:03  Y314Y  阅读(72)  评论(1)    收藏  举报