У меня есть большой вектор отсортированных целых чисел. Мне нужно быстро найти и удалить восемь значений из массива.
Например, вектор a включает элементы
{1, 4, 7, 15, 16, 19, 24, 26, 31, 53, 67, 68, 73, 75, 77, 82}
вектор b включает в себя восемь значений
{4, 15, 19, 24, 67, 68, 73, 75}
После завершения операции вектор a теперь должен иметь
{1, 7, 16, 26, 31, 53, 77, 82}
Мое старое решение было довольно медленным:
for (vector<int>::iterator val = b.begin(); val != b.end(); val++)
a.erase(remove(a.begin(), a.end(), *val), a.end());
Есть ли более быстрый метод?
РЕДАКТИРОВАТЬ:
На самом деле мой вектор «A» намного больше, чем мой вектор «B». Может быть, лучше просто искать отдельные элементы с помощью бинарного поиска и удалять их?
EDIT2:
Возможно, вектор не является хорошим контейнером для такого рода операций. Я не думаю, что могу использовать forward_list, потому что не могу скомпилировать с C ++ 11. Может быть, я могу использовать другой контейнер, а затем скопировать результаты в вектор?
Я бы, наверное, сделал что-то вроде:
std::vector<int> temp;
std::set_difference(a.begin(), a.end(),
b.begin(), b.end(),
std::back_inserter(temp));
std::swap(a, temp);
Редактировать на основе отредактированного вопроса:
Учитывая, что ваш a
вектор существенно больше вашего b
вектор, есть второй вопрос, который я хотел бы рассмотреть: вам нужно a
остаться отсортированным после завершения?
Если перестановка элементов в a
разрешено, тогда вы можете существенно улучшить скорость: вместо того, чтобы делать удаление / стирание, чтобы удалить элементы из середины a
, вы можете поменять элемент, который будет удален, с последнего элемента a
затем удалите его с конца (что имеет постоянную сложность). Это делает постоянные удаления, поэтому общая сложность составляет O (N log M) (где N = b.size () и M = a.size ()).
Если вам нужно поддерживать порядок, вы все равно можете немного улучшить скорость: вместо удаления элементов из a
, затем немедленно стереть удаленный элемент, сделать std::remove_if
найти все элементы a
это необходимо удалить, а затем один раз (когда это будет сделано) сделать одно удаление, чтобы удалить все эти элементы.
В настоящее время вы используете отдельный вызов remove
для каждого элемента a
что вы удалите. Все элементы a
после каждой точки удаления копируются (или перемещаются, если применимо) для каждого remove
, Это означает, что если вы удалите 10 элементов из a
копируешь (в среднем) половину a
В 10 раз больше Используя один remove_if
вместо этого вы копируете каждый элемент a
только однажды.
К несчастью, remove_if
не дает вам хороший способ воспользоваться заказом в b
, Вы можете использовать бинарный поиск, который помогает некоторым, но не так сильно, как хотелось бы.
Если вы не против написания собственного цикла, вы можете в полной мере воспользоваться тем, что оба a
а также b
отсортированы, примерно так:
#include <vector>
#include <iostream>
// Compute the difference between two "set"s in-place. Each 'set' must be a
// sorted sequence.
//
template <class FwdIt, class InIt>
FwdIt
inplace_set_difference(FwdIt b1, FwdIt e1, InIt b2, InIt e2) {
FwdIt pos = b1;
while (pos != e1 && b2 != e2) {
if (*pos < *b2)
*b1++ = *pos++;
else if (*b2 < *pos)
++b2;
else
++pos;
}
while (pos != e1)
*b1++ = *pos++;
return b1;
}
int main() {
std::vector<int> a{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 };
std::vector<int> b{ 2, 5, 9 };
auto it = inplace_set_difference(a.begin(), a.end(), b.begin(), b.end());
a.erase(it, a.end());
for (auto i : a)
std::cout << i << '\t';
}
Если вы хотите сделать это максимально универсальным, вы, вероятно, захотите изменить его, чтобы использовать только постинкремент на итераторах, позволяя пользователю передавать оператор сравнения вместо использования <
непосредственно и т. д. Они оставлены как страшное «упражнение для читателя».
Вероятно, стоит отметить, что это в основном тот же алгоритм, что и set_difference
обычно используется, только с незначительной регулировкой для работы на месте. Это вызывает существенные изменения в интерфейсе: поскольку он может (и создает) повторяющиеся элементы в коллекции, он может быть применен к отсортированной последовательности (vector, deque, hash и т. Д.), Но не в ассоциативный контейнер ([unordered_][multi_](set|map)
).
Так как это траверсы a
а также b
ровно по одному O(N + M)
, но так как мы начинаем с идеи, что M
(= b.size ()) мало, это эффективно O(N)
Примечание: в тестовом коде интенсивно используются функции C ++ 11 (например, для инициализации двух векторов), но я считаю, что сама реализация алгоритма должна быть в порядке в C ++ 98/03.
Вместо удаления элементов просто добавьте элементы в новый вектор. Предполагая, что ваши входные данные являются «оригинальными» (A) и «toRemove» (B), просто создайте итераторы для оригинала и toRemove:
Если следующий элемент в оригинале соответствует следующему элементу в toRemove, удалите его. В противном случае скопируйте его в результат.
Если он больше, чем следующий элемент в toRemove, перейдите к следующему элементу в toRemove и снова запустите сравнение.
Таким образом, вы выполняете итерацию по каждому списку только один раз, а не постоянно копируете значения массива во время операции удаления.
Это решение будет работать в O (A + B), что быстрее, чем ваше текущее (и предлагаемое) решение.
Для сравнения:
Ваше существующее решение, которое примерно равно O (A * A * B) (A для удаления, A для неоптимизированного поиска, B для итерации по B).
Ваше редактирование предлагает сделать бинарный поиск, чтобы удалить элементы; это только улучшит ваше исходное решение до O (logA * A * B) (A для удаления, logA для оптимизированного поиска, B для итерации по B).
Встроенные функции в векторном заголовке работают быстрее. Например,
вектор vect;
и после добавления всех элементов, вы можете использовать
сортировать (vect.begin (), vect.end ());
Это отсортировало бы список в порядке возрастания. Для спуска, возможно, вам придется выполнить
обратное (vect.begin, vect.end ());
на Сортированный список.