Добавление элементов в std::vector

На собеседованиях по С++ задают много вопросов про контейнеры STL. И самый безобидный из них, как думал, std::vector. Но вот и по нему попался интересный вопрос.

Представим, что стратегия управления внутренним буфером контейнера std::vector (в реальности, она иная) такова: изначально размер буфера равен нулю, и он будет увеличивается вдвое каждый раз, когда в нем уже нет места под следующий элемент.

Вопрос: оценить вычислительную сложность последвательного добавления в контейнер k элементов (как уже говорилось, начальная длина контейнера нулевая). Элементы добавляются в конец.

Как я полагаю, в среднем ожидается, что отвечать стоит практически сразу.

На всякий случай: мой ответ будет завтра.

А сейчас мини эксперимент с реальным std::vector (компилятор, и сообразно STL — Sun C++ 5.9 SunOS_sparc) для выяснения реальной стратегии роста буфера в векторе:

#include <vector>
#include <iostream>
#include <iomanip>
#include <cstdlib>

int main() {
  int last = -1;
  std::vector<int> a;
  std::cout << std::setw(12) << "Capacity" << " " 
            << std::setw(12) << "Size"
            << std::setw(12) << "Ratio" << std::endl
            << std::setw(12) << std::setfill('-') << "-" << " " 
            << std::setw(12) << "-" << " "
            << std::setw(12) << "-" << std::endl;

  std::cout << std::setfill(' ') << std::fixed;
  while (1) {
    if (a.capacity() != last) {
      last = a.capacity();
      std::cout << std::setw(12) << a.capacity() << " "
                << std::setw(12) << a.size() << " "
                << std::setw(12) << std::setprecision(6)
                << (float)a.capacity() / a.size() << std::endl;
    }
    a.push_back(std::rand());
  }
  return 0;
}

А вот и результат:

    Capacity         Size       Ratio
------------ ------------ ------------
           0            0          NaN
          32            1    32.000000
          64           33     1.939394
         103           65     1.584615
         166          104     1.596154
         268          167     1.604790
         433          269     1.609665
         700          434     1.612903
        1132          701     1.614836
        1831         1133     1.616064
        2962         1832     1.616812
        4792         2963     1.617280
        7753         4793     1.617567
       12544         7754     1.617746
       20296        12545     1.617856
       32838        20297     1.617875
       53131        32839     1.617924
       85965        53132     1.617952
      139091        85966     1.617977
      225049       139092     1.617987
      364129       225050     1.617992
      589160       364130     1.617994
      953260       589161     1.617996
     1542374       953261     1.617998
     2495561      1542375     1.617999
     4037817      2495562     1.617999
     6533187      4037818     1.617999
    10570696      6533188     1.618000
    17103386     10570697     1.618000
    27673278     17103387     1.618000
    44775363     27673279     1.618000
    72446537     44775364     1.618000
   117218496     72446538     1.618000
   189659526    117218497     1.618000
   306869113    189659527     1.618000

Выходит, что для моей STL - это какой-то магический коэффициент 1.618.

Update: В комментариях подсказали хорошую ссылку на тему стратегии управления размером вектора.

Update 2: Лично мой ответ на тему вычислительной сложности последовательного добавления элементов в вектор, если вектор будет удваивать размер буфера при переполнении.

Так как мы добавляем k элементов, то это как минимум O(k). А так как по условию вектор удваивает буфер каждый раз, когда нет места, то произойдет log2(k) раз (так как по условию элементы поступают последовательно).

Получаем в этоге: O(k*log2(k)).

Update 3: В комментариях меня поправили: O(k + log2(k)) или просто O(k).


Оригинальный пост | Disclaimer

Комментарии