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
使用库函数,传统的删除其实是remove加erase,如:
// 删除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);

浙公网安备 33010602011771号