Containers in STL : Sequence Containers



 
In the previous article we understood basic structure of C++ Standard Template Library and understood basics about containers, algorithms and iterators.

A container is an object that stores a collection of elements (other objects). Each container manages the storage space for its elements and provides access to them through iterators and/or member functions.
Containers are implemented as classes in STL and thus provide several member functions.

Note : iterators are used to point to the containers in STL, so iterators are somewhat like pointers in C/C++.
There are three categories of containers in STL :

1. sequence containers (eg. vector, list etc.)
2. associative containers (eg. map, set etc. )
3. unordered containers (eg. unordered_set, unordered_map etc.)

To use containers or algorithms in STL, we need to use the corresponding header files, like

#include<vector>        // for using vector container
#include<deque>
#include<list>
#include<set>
#include<map>


Their use can be easily understood by looking at their names

If you want to avoid writing separate header files, you can use a single header file to encapsulate all the functionality provided by STL.That header is
#include<bits/stdc++.h>
 

All the STL functionality is implemented inside 'std' namespace. 'std' is abbreviation for standard. We need to use 'std' namespace for using any STL container or algorithm. Therefore I recommend importing 'std' namespace in your program to avoid preceding everything with 'std::'.

so we add :
using namespace std;

Now we are ready to start writing STL code.

Let's discuss sequence containers.

Sequence Containers :

They are used to implement data structures that are sequential in nature like arrays(array) and linked list(list).
Some sequence containers are : vector, deque, list, array etc.

Let's discuss some of them in detail.

1. vector

A vector is a dynamic array ( size not fixed) which grows in one direction(towards the end). It stores elements in contiguous memory locations.
 

=> Creating a vector :

vector provides different constructors for its creation.
For example to create an integer vector vec, we write

a. vector<int> vec;                // creates an empty vector. vec.size() = 0

                                             // vec.size() is a member function provided by vector which tells the current                                                 number of   elements in vector.
b. vector<int> vec(10);             // creates a vector of 10 uninitialized integers. vec.size()=10;

c. vector<int> vec(10,0);          // creates a vector of 10 integers initilaized to 0. vec.size()=10;

d. vector<int> vec(vec2);        // copy constructor copies all values of vec2 into vec

e. vector<int> vec = {2,3,5};

=> Adding/removing an element at end of vector

Since vector is dynamic, you can grow or shrink your vector from the end as per your requirement.

vector<int> vec;            // vec.size()=0
vec.push_back(10);       // inserts 10 at the end of vector vec={10}
vec.push_back(20);       // vec={10,20}

vec.pop_back();            // removes last element(20) from vector    vec={10};
 

=> Access an element of vector

vector provides random access just like array.

a. cout<<vec[1];        // prints 2nd element of vec(0 base indexing) but performs no range check

b. cout<<vec.at(1);    // also performs range check and throws exception if index out of range of vec


Note : 'auto' is introduced from C++ 11 and is useful for difficult type names. The compiler automatically infers the correct type from the data provided.

=> Traversing all the vector elements using for loop.

a.  traversal using index of elements

    for(int i=0;i<vec.size();i++)
        cout<<vec[i]<<" ";


b. using for-each version

    for(auto v : vec)        // v automatically gets assigned values of vector one by one but it is read only traversal
        cout<<v<<" ";       

c. using iterator

    for(auto it=vec.begin();it!=vec.end();it++)        // auto can be replaced by vector<int> :: iterator
        cout<<*it<<" ";


=> Traversal using iterator is recommended because :
1. This methods is faster than using random access operator.
2. This a universal way of traversing any container in STL and therefore very important.


Note : These are some common member  functions for all STL containers :

1. vec.empty()         // boolean function, returns true(0) if vector has no elements, false otherwise.

2. vec.size()            // returns current size of vector

3. vec.clear()          // removes all the elements of vector


Properties of vector :

a. Fast insertion/removal at the end. Time complexity O(1).

b. Slow insertion/removal at any place other than the end because vector is stored in contiguous memory locations so the other elements need to be shifted to make space for new insertion. Time complexity O(n) (linear).

c. Slow search of elements as there is no order among them. Time complexity O(n).
 

2. deque

deque is very similar to vector. The main difference is that vector grows in only one direction, whereas deque grows in both the directions, i.e from front as well as end.
 

=> Creating a deque is similar to creating a vector
     deque<int> dq;
 

=> Adding/removing an element at beginning or at end of vector

push_back(val), push_front(val)    // add new element at the end or front of vector respectively.

pop_back(), pop_front()                // remove an element from the end or front of vector respectively.
 

=> Traversing deque is similar to vector as deque also provides random access operator like vector.

Properties of deque:

a. Fast insertion/removal at beginning or at end. Time complexity O(1).

b. Slow insertion/removal at any other place. Time complexity O(n) (linear).

c. Slow search of elements as there is no order among them. Time complexity O(n).

 

3. list


A list in STL is a doubly linked list data structure in which each node has two pointers pointing to to its next node and previous node respectively.
The key feature of list is that we have fast insert/removal of any element of the list at any position but only if we have an iterator to the desired position.

=> Creating a list
list<int> lis ={2,20,10};
 

=> list also provides methods push_back(),push_front(), pop_back(), pop_front() provided by deque for insertion/removal at back or front respectively.

To insert an element at any other position, first we require iterator to the desired position.
Let's insert value 5 at position of 20.

we use find algorithm function(which we will discuss later) to get an iterator to position where 20 is sotred.

auto it = find(lis.begin(),lis.end(),20);        // it -> 20 it is of type list::iterator but we use auto

To insert new value 5 at position pointed by iterator,, we use insert member function
lis.insert(it,5);                                             // Complexity O(1). lis={2,5,20,10};

We can increment or decrement iterator to move to next or previous element in list respectively.

it++;                                                            // it -> 20

To erase an element pointed by iterator(20) use.
lis.erase(it);                                                // Complexity O(1), lis={2,5,10}


Properties of list :

a. Fast insertion/removal at any place in list. Time complexity O(1).

b. No random access operator like vector/deque. So traversal is only possible using iterators.

c. Slow search of elements. Time complexity O(n). But this searching is comparitively slower than vector because of locality of individual elements.    In  vector, elements are stored in contiguous memory locations, and cache memory gives more cache hits for contagious elements. On the other hand, list elements are stored randomly in memory so it gives more cache misses and we keep swapping elements in and out of cache.

d. Each node in list stores two extra pointers one pointing to next element and one pointing to previous element. Thus   list occupies more memory and this also leads to higher cache misses.


These were some of the mostly used sequence containers in STL. This completes our discussion of Sequence containers. If you have any doubts. please mention in the comments section.

Also check out other types of C++ STL containers if you missed them out :
1. Associative Containers
2. Unordered Containers

 

Thank you for your patience reading. If you enjoyed this post, I’d be very grateful if you’d help it spread by emailing it to a friend, or sharing it on Whatsapp or Facebook.

😇Happy Learning!!