擦除删除成语的性能增益从何而来

Where is the performance gain of the erase-remove idiom coming from

本文关键字:删除 成语 性能 擦除      更新时间:2023-10-16

我需要从向量中删除满足特定条件的所有元素。

我的第一个方法是遍历向量并在所有满足条件的元素上调用 vector::eras。

据我了解,vector::erase对于这个用例来说性能很差,因为它从底层数组中删除了项目,并将矢量的其余部分向前移动了一个元素(如果您擦除一系列元素,则移动更多(。 移除多个元素时,每次移除时都会移动后部元素。

remove算法获取所有要删除的元素,并将它们移动到矢量的末尾,因此您只需删除矢量的后部,这不涉及移动。

但为什么这比擦除更快?(它更快吗?(

将元素移动到末尾是否意味着像vector::erase一样向前移动所有以下元素?

为什么,删除只有O(n(的复杂性?

这里的性能问题不是关于擦除要删除的元素,或者将它们移动到末尾(实际上并没有发生(,而是关于移动要保留的元素

如果您在要删除的每个元素上使用erase,则需要在这些元素之后移动所有元素...对于每次调用erase.通常,如果要删除k元素,则将元素移动到最新元素(在矢量中(之后k次,而不是仅移动一次。

但是如果你调用remove,你只会移动一次(见下面的示例(。

一个小示例可以更好地理解这两种方法的工作原理:

假设您有一个大小为 1000 的向量,并且要删除的元素位于位置 17 和 37。

erase作用于要删除的两个元素:

  • 当你为第 17 个元素调用erase()时,您需要将元素 18 移动到 999、982 个元素。
  • 当你为第 36 个元素
  • 调用erase()时(现在是第 36 个元素!(,您需要将元素 37 移动到 998、962 个元素。

您总共移动了 962 + 982 = 1944 个元素,其中 962 个元素被白白移动了两次。

使用remove,发生的情况如下:

element 0 does not change;
element 1 does not change;
...
element 17 is "discarded";
element 18 is moved at position 17;
element 19 is moved at position 18;
...
element 36 is moved at position 35;
element 37 is "discarded";
element 38 is moved at position 36;
...
element 999 is moved at position 997.

您总共移动了 998 个元素(1000 减去您删除的两个元素(,这比以前方法的 1943 个元素要好得多。如果要删除的元素超过 2 个,那就更好了。

您可以查看 en.cppreference.com 上可能的实现,以更好地了解std::remove的工作原理。

优点在于std::remove一次只删除一个元素。例如,如果对std::remove的调用导致删除向量的前 10 个元素,它会将第 11 个元素直接移动到第 1 个位置,将第 12 个元素直接移动到第 2 个位置,依此类推...... 然而,如果您一次擦除一个前 10 个元素,它会将您擦除的元素之后的每个元素移回 1。然后你会删除下一个,每个元素都必须再次移动。这将对每个被擦除的元素重复。

此外,删除的元素不必是顺序的,此优势就会发生。例如,如果对 remove 的调用导致从第一个元素开始的所有其他元素被删除。首先,第二个元素将移动到第一个位置,这将留下两个元素的间隙,直到下一个可保留的元素。然后第 4 个元素将直接移动到第 2 个位置,留下 3 个元素的间隙,依此类推。

另外,稍作修正:

remove 算法获取所有要删除的元素,并将它们移动到向量的末尾

删除算法不会这样做。它不关心要删除的元素会发生什么。它们只是被将要保留的元素所取代。未指定调用删除后末尾的元素值。您描述的算法是分区(具有反向比较函数(。