Асимптотическая сложность std :: remove_if

Я работаю над методом стирания для структуры данных с жестко запрограммированным максимальным количеством элементов, N, который опирается на std::array чтобы избежать кучи памяти. Хотя std::array содержит N элементов только с некоторым числом, M, из них являются «соответствующими» элементами, где M меньше или равно N. Например, если N равно 10, а массив выглядит так:

std::array<int, N> elements = { 0, 1, 2, -1, 4, -1, 6, -1, -1, 9 };

…и если M равно 7, только первые 7 элементов являются «релевантными», в то время как остальные считаются ненужными (окончание { -1, -1, -9 } являются мусором). я использую int здесь для примера SO, но реальная программа хранит объекты, которые реализуют operator==, Ниже приведен рабочий пример, который удаляет все -1 и обновления М:

#include <algorithm>
#include <array>
#include <iostream>

constexpr unsigned N = 10;
unsigned           M = 7;
std::array<int, N> elements = { 0, 1, 2, -1, 4, -1, 6, -1, -1, 9 };

int main() {
for (unsigned i = 0; i < M; ++i)
std::cout << elements[i] << ' ';
std::cout << '\n';

auto newEnd = std::remove_if(
std::begin(elements), std::begin(elements) + M,
[](const auto& element) {
return -1 == element;
}
);

unsigned numDeleted = M - std::distance(std::begin(elements), newEnd);
M -= numDeleted;
std::cout << "Num deleted: " << numDeleted << '\n';

for (unsigned i = 0; i < M; ++i)
std::cout << elements[i] << ' ';
std::cout << '\n';

return 0;
}

Вопрос, который я имею, состоит в том, какова асимптотическая сложность std::remove_if? Я полагаю, что между std::remove_if а также std::distance это всего O (2M) или O (M), где std::remove_if это более дорогая операция. Однако я не уверен, что std::remove_if равно O (N * M) из-за смещения элементов при удалении

редактироватьЯ понимаю, что это должно применяться к предикату M раз, но мне интересно, применяются ли N сдвигов каждый раз, когда предикат истинен

1

Решение

редактировать

В ретроспективе этот ответ затрагивает более сложный вопрос, чем тот, который был задан — как можно реализовать функцию «возврата к концу» за линейное время. Относительно конкретного заданного вопроса — касающегося remove_if — Ответ @ millenimumbug решает эту проблему лучше.


Я понимаю, почему вы думаете, что сложность будет Θ (м н), как каждый из м удаленные элементы, возможно, должны быть перемещены Θ (п) расстояние.

Это действительно возможно сделать вовремя Θ (п) и дополнительные O (1) пробел (всего несколько дополнительных итераторов).

Рассмотрим следующую диаграмму, показывающую итерацию возможной реализации алгоритма.

введите описание изображения здесь

Красные элементы — это непрерывная группа распознанных элементов, которые должны быть удалены до конца, на данный момент (вам просто нужно две точки, чтобы записать это). Зеленый элемент — это элемент, который сейчас рассматривается (другой указатель).

Если зеленый элемент должен быть удален, красная группа просто увеличивается, добавляя его. Это показано на следующей диаграмме, где красная группа расширяется. На следующей итерации зеленый элемент будет справа от него.введите описание изображения здесь

Если нет, всю красную группу нужно сдвинуть мимо нее. Некоторая мысль может убедить вас, что это можно сделать за линейное время в красной группе (даже если итераторы гарантированно будут только прямыми итераторами).

Почему сложность линейна? Потому что вы можете представить, что это эквивалентно смещению зеленого элемента влево относительно левой группы. Обоснование сходно с обоснованием амортизированного анализа.

Следующая диаграмма показывает второй случай. На следующей итерации зеленый элемент (рассматриваемый) снова будет справа от красной группы.

введите описание изображения здесь

2

Другие решения

От cppreference:

сложность:
Именно так std::distance(first, last) приложения предиката.

На удаленных элементах нет операций сдвига, поскольку они могут иметь неопределенное значение после вызова std::remove_if

4

По вопросам рекламы [email protected]