Какую структуру данных из STL выберешь для хранения большого количества целых чисел с частой операцией добавления?

«Какую структуру данных из STL выберешь для хранения большого количества целых чисел с частой операцией добавления?» — вопрос из категории Алгоритмы и структуры данных, который задают на 25% собеседований C/C++ Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Для сценария с частым добавлением элементов в конец оптимальным выбором будет std::vector<int>.

Причины:

  1. Амортизированная константная сложность O(1) для push_back(). Хотя при исчерпании capacity происходит дорогостоящее перевыделение памяти и копирование, стратегия геометрического роста (обычно в 1.5-2 раза) гарантирует, что эти операции редки, а их стоимость «размазывается» по многим дешёвым добавлениям.
  2. Отличная локальность данных. Элементы хранятся в непрерывном блоке памяти, что максимально эффективно для кэша процессора.
  3. Минимальные накладные расходы на хранение. Только данные и небольшой служебный overhead (указатели на начало, конец и конец выделенной памяти).

Критически важная оптимизация: Использование reserve() для предварительного выделения памяти, если примерный размер известен.

#include <vector>
#include <iostream>
#include <chrono>

int main() {
    std::vector<int> data;
    data.reserve(1'000'000); // Ключевая оптимизация: избегаем множественных перевыделений

    for (int i = 0; i < 1'000'000; ++i) {
        data.push_back(i); // Быстрое добавление
    }
    std::cout << "Size: " << data.size() << ", Capacity: " << data.capacity() << 'n';
    return 0;
}

Альтернативы и когда их рассматривать:

  • std::deque: Подходит, если нужны частые вставки и в начало, и в конец (O(1)), но с чуть худшей локальностью, чем у vector.
  • std::list / std::forward_list: Стоит выбрать только если нужны частые вставки/удаления в произвольных позициях (O(1) при наличии итератора), а не только в конце. Они имеют большое overhead на хранение указателей и плохую локальность.