2.7. Analysis of Vector Operators

Accessing data using an index and assigning to an index position that already exists both take the same amount of time no matter how large the vector is. When an operation like this is independent of the size then it is \(O(1)\).

As we have seen, one way to create a longer vector is to use the push_back() method. The push_back() method is typically \(O(1)\), provided there is adequate capacity in the underlying array.

First we’ll use push_back() method. The following code shows the code for making our vector.

#include <vector>
using std::vector;

void test_push_back(int size){
    vector<int> vect;
    for (int i = 0; i < size; ++i){
        vect.push_back(i);
    }
}

And we can time how long it takes to push 10,000 values into a vector.

In the experiment above the statement that we are timing is the function call to test_push_back. From the experiment, we see the amount of time taken by the push_back operation. Not only is the push_back() function call duration being measured, but the time to allocate space is being measured.

We can improve the runtime a bit further by setting an adequate reserve for the vector in advance. Doing this will keep us from having to move the entire vector to an adequately sized space in memory as the vector grows.

A graph of the loops in the preceding code should look something like this:

Comparison of vector::push_back times

Now that we have seen how performance can be measured concretely you can look at the table below to see the Big-O efficiency of some basic vector operations. When pop_back() is called, the vector size is reduced by 1 and it takes constant time: \(O(1)\). However, when erase() is called the time is \(O(n)\). The reason for this lies in how C++ chooses to implement vectors. When an item is taken from the front of the vector, in C++ implementation, all the other elements in the vector are shifted one position closer to the beginning. This implementation also allows the index operation to be \(O(1)\). This is a trade-off that the C++ implementers thought was a good one.

Big-O Efficiency of C++ Vector Operators

Operation

Big-O Efficiency

index []

O(1)

index assignment =

O(1)

push_back()

amortized O(1)

pop_back()

O(1)

erase(i)

O(n)

insert(i, item)

O(n)

find(b, e, item)

O(n)

reserve()

O(n)

begin()

O(1)

end()

O(1)

size()

O(1)

The push_back() operation is \(O(1)\) unless there is inadequate capacity, in which case the entire vector is moved to a larger contiguous underlying array, which is \(O(n)\). However, since over the long term, as \(n\) grows large, then number of vector copies is small. So on average, even though there are some \(O(n)\) operations, it turns out that push_back() is constant time.

As a way of demonstrating the difference in performance between pop_back() and erase(), let’s do another timing experiment. Our goal is to be able to verify the performance of the pop_back() operation on a vector of a known size when the program pops from the end of the vector using pop_back(), and again when the program pops from the beginning of the vector using erase(). We will also want to measure this time for vectors of different sizes. What we would expect to see is that the time required to pop from the end of the vector will stay constant even as the vector grows in size, while the time to pop from the beginning of the vector will continue to increase as the vector grows.

The following code shows one way to measure the difference between the pop_back() and erase().

Although erase is \(O(n)\), a graph showing how much faster pop_back() can be as the size of a vector grows can still be surprising.

Self Check


You have attempted of activities on this page