1

A lot of modern languages usually have a "list" or "vector" structure which allows for amortized $O(1)$ append and removal from back as well as amortized $O(1)$ random access.

I'm curious if a double sided (deque) style data structure can exist that also supports fast random access. This doesn't feel like it is asking for too much. But I've never seen any implemented in practice and google scholar turned up empty handed so perhaps there might be a wall here.

I conjecture more generally that if one specifies a FINITE list of a percentiles $p_0, ... p_r$ (the case of the list is $(p_0 = 100\%)$ and the deque is $(p_0 = 100\%, p_1 = 0\%)$ that a data structure can exist that allows amortized $O(1)$ append and removal from those percentile locations $p_i$ as well as amortized $O(1)$ random access.

Sidharth Ghoshal
  • 375
  • 1
  • 2
  • 10

3 Answers3

2

Yes, I believe so. Build an array, formatted as follows:

[left buffer zone] [...entries in the deque...] [right buffer zone]

Each buffer zone contains empty unused entries. Maintain indices that record the end of the left buffer zone and the start of the right buffer zone.

To insert at the left (prepend), insert into the rightmost entry in the left buffer zone (and decrement the index that records the end of the left buffer zone). To insert at the right (append), insert into the leftmost entry in the right buffer zone (and increment the index that records the start of the right buffer zone).

If a buffer zone becomes empty, then we allocate a new larger array with $3n$ entries, so that we have $n$ unused entries in each buffer zone, copy over the data to the new array, deallocate the old array, and continue.

I believe this will have $O(1)$ amortized prepend and append and random access.

For better usage of space, if you like, you can also shrink the array if any buffer zone becomes too large (e.g., any buffer zone has $\ge 2n$ empty unused entries), while still maintaining $O(1)$ amortized time.

D.W.
  • 167,959
  • 22
  • 232
  • 500
2

Lists/vectors normally support O(1) random access, not just amortized O(1).

C++'s std::deque guarantees O(1) (not amortized) random access and insertion and deletion at the ends. Together with some other requirements not shared by std::vector (such as insertion at the ends not invalidating element references), this means it can't be implemented in the ways that were suggested in the two earlier answers. I think it's usually implemented as a deque of pointers to fixed-size arrays of elements, with the inner deque implemented as in the earlier answers. How they avoid amortized O(1) runtimes for resizing the inner deque I don't know; they may cheat and neglect it since that deque is small. Even if they cheat, this implementation still satisfies your requirement of O(1) random access.

I suppose your percentile-access structure could be implemented as $r$ deques covering the ranges $p_0$ to $p_1$, $p_1$ to $p_2$, etc. Adding or removing an element might require moving other elements from the end of one deque to the beginning of the next or vice versa, but at most $r-1$ would need to be moved, so this is still O(1) unless you're considering the run time as a function of $r$ also.

benrg
  • 2,511
  • 6
  • 13
1

Here is a simple idea for designing such a data structure:
Recall that a simple array-based simple queue where insertion, deletion, and access take $O(1)$-time. The only drawback here is that, after a deletion operation, the vacated space remains unused. A circular queue solves this issue by allowing us to circularly spill over and fill in the empty spaces in the front of the array. All three operations still remain constant. Now, to accommodate more and more elements in the queue, dynamic queue implementations have been proposed. An array-based dynamic queue would expand (or shrink) the array depending on the load. It is easy to observe that the resizing cost can be amortized into the update operations, making everything under control (i.e. $O(1)$-time). The same idea also applies to a circular queue. For your case, we just need a circular doubly-eneded queue with dynamic array resizing.

codeR
  • 1,983
  • 7
  • 17