STL deque accessing by index is O(1)?
I found this deque implementation from Wikipedia:
Storing contents in multiple smaller arrays, allocating additional
arrays at the beginning or end as needed. Indexing is implemented by
keeping a dynamic array containing pointers to each of the smaller
arrays.
I guess it answers my question.
how does random access of an element in deque gives constant time complexity?
A deque is typically implemented as a two-layer structure, the top level is an array of pointers to fixed-sized chunks. Using knowledge of that size it is constant complexity to determine which chunk holds the item of interest and constant complexity to determine which item in the chunk is being requested.
Just because it is constant does not indicate that it is always a simple direct memory access, only that all accesses for that particular operation will require the same steps.
Does deque provide O(1) complexity when inserting on top
The cppreference entry for std::deque gives the following complexity:
The complexity (efficiency) of common operations on deques is as follows:
- Random access - constant O(1)
- Insertion or removal of elements at the end or beginning - amortized constant O(1)
- Insertion or removal of elements - linear O(n)
which is consistent with the draft C++ standard section 23.3.3.1
Class template deque overview which says (emphasis mine):
A deque is a sequence container that, like a vector (23.3.6), supports random access iterators. In addition, it supports constant time insert and erase operations at the beginning or the end; insert and erase in the middle take linear time. That is, a deque is especially optimized for pushing and popping elements at the beginning and end. As with vectors, storage management is handled automatically.
For std::vector cppreference says:
The complexity (efficiency) of common operations on vectors is as follows:
- Random access - constant O(1)
- Insertion or removal of elements at the end - amortized constant O(1)
- Insertion or removal of elements - linear in distance to the end of the vector O(n)
which is consistent with the draft standard section 23.3.6.1
Class template vector overview:
A vector is a sequence container that supports random access iterators. In addition, it supports (amortized) constant time insert and erase operations at the end; insert and erase in the middle take linear time. Storage management is handled automatically, though hints can be given to improve efficiency.[...]
What really is a deque in STL?
A deque is somewhat recursively defined: internally it maintains a double-ended queue of chunks of fixed size. Each chunk is a vector, and the queue (“map” in the graphic below) of chunks itself is also a vector.
There’s a great analysis of the performance characteristics and how it compares to the vector
over at CodeProject.
The GCC standard library implementation internally uses a T**
to represent the map. Each data block is a T*
which is allocated with some fixed size __deque_buf_size
(which depends on sizeof(T)
).
Complexity of stl deque::insert()
std::deque is normally (always?) implemented as a collection of chunks of memory, but it won't normally insert a whole new chunk just for you to insert one new element in the middle of the collection. As such, it'll find whether the insertion point is closer to the beginning or end, and shuffle the existing elements to make room for the new element in an existing chunk. It'll only add a new chunk of memory at the beginning or end of the collection.
Random access iterators and deque
The RandomAccessIterator
concept requires that the +
and -
operations must be implemented in constant time:
From [iterator.concept.random.access]:
The RandomAccessIterator concept adds support for constant-time advancement with +=, +, -=, and -, as well as the computation of distance in constant time with -. Random access iterators also support array notation via subscripting.
This means that any standard-compliant container that implements a random-access iterator must provide constant-time element retrieval.
std::deque is contiguous memory container or not?
There is no conflict between the two quotes. Scott uses the term "contiguous container" in a sense a little wider than you might have seen it used elsewhere.
Scott writes (emphasize mine):
Contiguous-memory containers (also known as array-based containers] store their elements in one or more (dynamically allocated) chunks of memory, [...]
As the quote from cppref states, std::deque
uses multiple arrays to store the elements. Within each array the elements are contiguous. Scott does not claim that all elements in a std::deque
are contiguous in one chunk of memory.
deque::insert() at index?
There is a deque::insert(iterator pos, const T&x)
function taking the position pos
as deque::iterator
and a single element. Using this method you could insert all elements one by one. pos
can easily be obtained (if you have an index before which you want to insert the element) by deque.begin()+index
. The insert
method returns an iterator for the newly inserted element, simply increment this returned iterator to get the next position:
deque::iterator it = myDeque.begin()+index;
while(n--) {
it = myDeque.insert(it, currentElement);
it++;
currentElement = ... // However you get your next element ...
}
This however cantake O(n*k)
time, since insertion of a single element is (iirc) a linear time operation iirc.
The second overload is deque::insert(iterator pos, InputIterator f, InputIterator l)
: Remember that simple pointers also fulfill the requirements of an STL input iterator, so if you have a C-Style array T array[]
of length n
containing your elements, you could insert all elements from this array with
d.insert(pos, array, array+n);
This operation can be carried out in linear time, i.e. O(n+k)
. I'm not sure if this is guaranteed by the standard, but I suppose that most implementation will do it efficiently.
EDIT
I quickly checked with Microsoft's implementation, they do it by a sequence of either push_back
or push_front
, whatever is closer to pos
and then rotating the elements to their final place, which guarantees the above O(n+k)
complexity. Of course, that could also be done "by hand" like:
size_type _Off = pos - d.begin();
size_type _Oldsize = d.size();
if (_Off <= d.size() / 2)
{ // closer to front, push to front then rotate
while (hasMoreElements())
push_front(nextElement()); // prepend flipped
size_type _Num = d.size() - _Oldsize;
std::reverse(d.begin(), d.begin() + _Num); // flip new stuff in place
std::rotate(d.begin(), d.begin() + _Num, begin() + _Num + _Off);
}
else
{ // closer to back
while (hasMoreElements())
push_front(nextElement()); // prepend flipped
std::rotate(begin() + _Off, begin() + _Oldsize, end());
}
(I copied the code from Microsofts implementation of deque::insert
, removing debug code and exception handling,
Deque index-iterator conversion
we need to make begin() + i which will take linear time.
No it won't.
Iterators that support operator+
should do it in constant time (this is true for the standard iterators, user-defined iterators could define operator+
that is linear, but that would be unconventional).
Iterators that don't support it in constant time need to be advanced with std::advance
not operator+
std::deque::iterator
is a random-access iterator, so begin() + i
is valid and takes constant-time.
Related Topics
Implementing the Visitor Pattern Using C++ Templates
How to Calculate Perspective Transform for Opencv from Rotation Angles
Type Erasure in C++: How Boost::Shared_Ptr and Boost::Function Work
Inspecting Stl Containers in Visual Studio Debugging
How to Install/Configure Opencv3.2.0 with C++, Visual Studio 2017
When Is It Safe to Call This-> in Constructor and Destructor
C++, How to Call a Constructor Directly, Without New
Where Does One Get the "Sys/Socket.H" Header/Source File
Counting the Number of Lines in a Text File
C++ Implementing Timed Callback Function
Increment Void Pointer by One Byte? by Two
Opencv Grouprectangles - Getting Grouped and Ungrouped Rectangles
Differencebetween Std::Quick_Exit and Std::Abort and Why Was Std::Quick_Exit Needed
C++ Cout and Cin Buffers, and Buffers in General
How to Get Cmake to Recognize Pthread on Ubuntu