如何删除C++容器中满足特定条件的元素

例子

有一个vector数组,里面包含10000000个整数,如何高效的函数其偶数位置的元素?

这道题,如果我们用迭代器删除会遇到一些问题。

  • 效率问题
    每次在erase删除元素时,可能会触发容器自动收缩,可能会导致 O ( n 2 ) O(n^2) O(n2)的时间复杂度。
  • 迭代器失效的问题
    容器在删除当前位置的元素时,会导致迭代器失效。

初步方案

比较好的方案是:

1 创建一个新 vector 来存储奇数位置的元素。
2 使用 std::copy_if 算法将奇数位置的元素复制到新 vector 中。
3 用新 vector 替换原始 vector。

#include 
#include 
#include 
#include 

int main() {
    std::vector numbers = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
    std::vector result;

    // 使用 std::copy_if 复制奇数位置的元素
    std::copy_if(numbers.begin(), numbers.end(), std::back_inserter(result),
                 [&numbers](const int &value) {
                     return &value % 2 == 1;
                 });

    // 用新 vector 替换原始 vector
    numbers.swap(result);

    // 输出处理后的容器
    for (int n : numbers) {
        std::cout << n << ' ';
    }
    // 输出结果:1 3 5 7 9
}

这个方法的时间复杂度为 O(n),因为它只需要遍历一次原始 vector。同时,空间复杂度也为 O(n),因为需要创建一个新 vector 来存储奇数位置的元素。虽然它需要额外的空间,但在大多数情况下,这种方法在性能上可以接受。

时间空间都更优的方案

有没有更好的方法呢?时间复杂度没法优化了,空间复杂度呢?
当然有。

我们可以使用一种更好的方法来实现,这种方法在原地修改 vector,无需创建新的 vector。我们可以使用两个迭代器,一个用于遍历原始 vector,另一个用于在原地移动奇数位置的元素。

#include 
#include 

int main() {
    std::vector numbers = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};

    // 创建一个用于移动元素的迭代器
    auto write_iter = numbers.begin();

    // 遍历 vector,移动奇数位置的元素
    for (auto read_iter = numbers.begin(); read_iter != numbers.end(); ++read_iter) {
        if ((read_iter - numbers.begin()) % 2 == 1) {
            *write_iter++ = *read_iter;
        }
    }

    // 使用 erase 删除多余的元素
    numbers.erase(write_iter, numbers.end());

    // 输出处理后的容器
    for (int n : numbers) {
        std::cout << n << ' ';
    }
    // 输出结果:1 3 5 7 9
}

这种方法的时间复杂度仍为 O(n),但空间复杂度降低为 O(1),因为它在原地修改 vector,无需创建新的 vector。这是一种更好的方法,因为它不仅高效,而且避免了额外的空间开销。

更通用的方法

试试remove-erase idiom。

C++ 中的 remove-erase idiom 是一种用于删除容器(如 vector、list、deque 等)中满足特定条件的元素的方法。它结合了标准库中的 remove 或 remove_if 算法与容器的 erase 成员函数,实现了高效且简洁的元素删除。

以下是 remove-erase idiom 的基本步骤:

使用 std::remove 或 std::remove_if 算法将要删除的元素移至容器的末尾。这两个算法返回一个迭代器,指向第一个被移动的元素。

std::remove 删除所有与给定值相等的元素。
std::remove_if 删除所有满足特定条件(谓词)的元素。
使用容器的 erase 成员函数删除从 std::remove 或 std::remove_if 返回的迭代器到容器末尾的元素范围。

#include 
#include 
#include 

int main() {
    std::vector numbers = {1, 42, 3, 42, 5, 42};

    // 使用 remove-erase idiom 删除等于 42 的元素
    numbers.erase(std::remove(numbers.begin(), numbers.end(), 42), numbers.end());

    // 输出处理后的容器
    for (int n : numbers) {
        std::cout << n << ' ';
    }
    // 输出结果:1 3 5
}

同样,可以使用 std::remove_if 删除满足特定条件的元素。例如,以下代码删除所有偶数元素:

numbers.erase(std::remove_if(numbers.begin(), numbers.end(), [](int n) { return n % 2 == 0; }), numbers.end());

remove-erase idiom 提供了一种简洁、高效的方式来删除容器中的元素,同时避免了在遍历过程中进行删除操作可能导致的问题。

总结

C++ 中的 remove-erase idiom 解决了在删除容器(如 vector、list、deque 等)中满足特定条件的元素时可能遇到的以下问题:

遍历与删除的问题:当我们在遍历容器的过程中删除元素时,可能会导致迭代器失效,从而引发未定义行为。remove-erase idiom 通过将要删除的元素移至容器末尾,并在遍历结束后统一删除,避免了这个问题。

  • 效率问题:直接使用容器的 erase 函数逐个删除元素可能导致低效的操作。对于具有连续存储的容器(如 vector 和 deque),每次删除元素时,后续的所有元素都需要移动,这可能导致 O(n^2) 的时间复杂度。而 remove-erase idiom 仅需线性时间复杂度,因为它只需一次遍历和一次删除操作。

  • 代码简洁性:remove-erase idiom 提供了一种简洁的方式来删除容器中的元素。它将标准库中的 remove 或 remove_if 算法与容器的 erase 成员函数结合,使代码更易于理解和维护。

  • 通用性:remove-erase idiom 可以用于各种容器类型,包括 vector、list、deque 等。这意味着你可以使用相同的方法处理不同类型的容器,提高代码的通用性和可重用性。

总之,C++ 中的 remove-erase idiom 解决了在删除容器中满足特定条件的元素时可能遇到的遍历与删除、效率、代码简洁性和通用性等问题。

你可能感兴趣的:(C++高性能编程,高性能计算,c++,算法,数据结构)