Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why not always use circular array deques instead of array lists?

Almost all programming languages have some implementation of a list that uses a dynamic array, which automatically expands when it reaches a certain capacity. For example, Java has ArrayList, and C++ has std::vector.

Recently I learned about circular array deques, which are also implemented using dynamic arrays. They keep track of the starting index of the list, and use modular arithmetic to access elements. Like array lists, they allow O(1) lookup and O(1) insertion at the end, and space proportional to O(N). However, they also allow O(1) insertion at the beginning.

(While Java's ArrayDeque implements this data structure, it doesn't allow lookup of elements. C++'s std::deque appears to use a different implementation.)

If these array deques have the same or better performance characteristics as array lists, then why not always use them as the default implementation for lists?

like image 764
gengkev Avatar asked Jul 11 '15 06:07

gengkev


People also ask

What is circular array How is it different with array?

A circular array list has amortized O(1) append and prepend operations. An array list has an O(1) operation to remove the last element. A circular array list has O(1) operations to remove the first and last elements.

What is the point of a circular array?

An array is called circular if we consider the first element as next of the last element. Circular arrays are used to implement queue (Refer to this and this).

Is Deque always circular?

Implementation: A Deque can be implemented either using a doubly-linked list or a circular array.

What is the need for circular array to implement queue?

By treating the array as a circular buffer, pointing the head of the queue to the next item when one is removed becomes as simple as a single assignment, which is obviously much more performant.


1 Answers

If you don't need insertion at the head of the list, then a circular deque gives unnecessary overhead, e.g. for indexed methods. In a circular deque, accessing index i requires adding i to the index of the head element and looping around (e.g. using modulo, or bitwise operators) before accessing the underlying array, whereas with a vanilla vector, no manipulation to i is necessary before accessing the underlying array.

To demonstrate this overhead, a typical deque implementation might look like:

template <typename T>
class Deque {
  private:
    T* array; // The underlying array, holding elements of the deque.
    int size; // The number of elements held by the deque.
    int capacity; // The size of array ** must be a power of two **.
    int head; // The index of the head element within array.

  public:
    T& operator[](int index) {
        // Notice the calculation required before accessing array.
        return array[(head + index) & (capacity - 1)]
    }
};

In addition, if you require access to the underlying array where your elements are contiguous beginning at index 0, a deque would not deliver.

See question Why would I prefer using vector to deque, which is very similar to yours.

like image 181
Kevin L. Stern Avatar answered Sep 25 '22 07:09

Kevin L. Stern