6

Well, it's not exactly a queue if it allows for random reads. This question is about a data structure which behaves like a queue for insertions and a sorted dictionary for reads.

Imagine an algorithm that maintains a list of elements so that:

  1. It reads an element from the list based on its key.
  2. It then inserts that element as the last element in the list.
  3. On demand it returns all entities in the order in which they were inserted.

And I am looking for a solution that is faster than a naive implementation based on a linked list, which would have complexity O(n) for all three parameters - search, insert as the last element, and return all elements in the inserted order.

Such a data structure should probably:

  1. Allow for a quick lookup of the key (e.g. a tree, a hashmap)
  2. Maintain the order in which elements were inserted (e.g. a list, a queue)

A practical example is a list of agents receiving calls. When an agent is called, it's fetched from the list, and when it finishes the call, it's put at the back of the list. Therefore the agent that is first in that list is the most idle agent (waiting for a call the longest). In other words, the list would be sorted according to agent's idleness.


Another example implementation could be based on two structures, one dictionary and one array, referencing each other. The dictionary would keep the index in the array as the value and the array would keep the key as the value. Then the element would be removed from the array by looking up the value/index in the dictionary and the dictionary would be updated with the new index once the element was put as the last element in the array.

Greg
  • 239
  • 1
  • 5

1 Answers1

8

Take any dictionary data structure and link its entries in whichever order suits you. In essence, you retain the $\Theta$-costs from the basic structure.

In search trees, this is called threading. It gives you constant-time access to the first and last element, respectively, and maintaining the threading takes only constant-time overhead for each dictionary or queue operation. As a consequence, you get $\Theta(\log n)$ access by key, $O(1)$ access to front and back of the queue (without modification), and $\Theta(\log n)$ insertion and deletion.

If you use hashtables you get $O(1)$/$\Theta(n)$ (AC/WC) access by key, $O(1)$ access to front and back of the queue (without modification), and $O(1)$/$\Theta(n)$ (AC/WC) insertion and deletion. The usual caveats apply.

Raphael
  • 72,336
  • 29
  • 179
  • 389
  • How the deletion can be O(1)/Θ(n) or Θ(logn) if I need to find the element that is being moved to the back of the queue, and for a double-linked list that is always Θ(n)? Also, why does it have to be a double-linked list and not a single-linked list? – Greg Nov 01 '16 at 20:28
  • @Amiramix 1) The queue property is trivial to maintain using references to the first and last entry. 2) Single-linked may be enough depending on your needs, yes. – Raphael Nov 01 '16 at 20:32
  • The requirement is that I can pick any element from the data structure when moving it to end of the list, not only the first or the last. I agree that lookup will be quick but removing it from the linked list and putting it at the back of that list will be O(n) unless I am missing something? – Greg Nov 01 '16 at 20:36
  • @Amiramix Yes. You are missing than removing things from a linked list is possible in constant time (if you have a pointer to the element, which we have here) and that adding things to the end is possible in constant time, as well (if you have a a pointer to the end, which we have here). I recommend you implement what I propose and see for yourself. – Raphael Nov 01 '16 at 21:21
  • 2
    @Amiramix You can remove an element from a linked list if you have the last element around. If you reach an element without going through the list in order, you won't have that, so make it a doubly-linked list. – Gilles 'SO- stop being evil' Nov 01 '16 at 21:56
  • I understand the removal. And I agree that inserting an element to the linked list is in constant time (I can keep a single linked list, add always at the beginning and reverse when returning) - it doesn't matter. What I don't understand is why do you write in the answer that the complexity of deleting an element is O(1)/Θ(n)? Please remember that I am removing a random element which may be in the middle of the list and I have to search for it linearly. – Greg Nov 02 '16 at 07:46
  • @Amiramix No, you don't. You use the dictionary search. – Raphael Nov 02 '16 at 08:01
  • Removing an element in a Linked list. First Linked means that the structure is made up or (Linked or joined elements) by nodes like this[1<--21<--32<--15<--5<--150]. So if you remove the middle element or any other, let say 15. You just remove it by linking the node for 32 with the node for 5. After linking 32 and 5 your Linked list will appear like this: 32<--5, and 15 is gone and so you do not need to move any element forward like an Array or a List. That's why it's called a Linked list. – Juniar Nov 02 '16 at 14:19
  • The dictionary search gives me the element in the tree, not in the list. OK, so does it mean that I need to keep in the tree a pointer to the element in the list? – Greg Nov 02 '16 at 20:34
  • A Tree is a searching algorithm and not a data structure. You can design your Tree search with a data structure like linked list. In a Tree you have Left and Right child that is made up of R & L node elements. So you can do it by linking these nodes together with pointers (Head & Tail). – Juniar Nov 02 '16 at 21:32
  • I see. I thought that I need to maintain two separate structures - a tree and a list, whereas I should only add linking/threading to the elements in the tree. Thanks for explanation. – Greg Nov 03 '16 at 21:05
  • @Amiramix Yes, that's the idea; I wrote "link its entries". Was that unclear? Should I add an image? – Raphael Nov 03 '16 at 23:13
  • It's clear when you have the proper mindset. I am usually programming in Erlang or JavaScript these days and I don't have access to pointers directly. So I tried to understand the answer in the context of those pointer-less languages using only the structures that are available there. Where you wrote "link its entries" I thought about a list holding those entries. When switching back to C/C++ it starts to make sense. An image would be great but I don't want you to spend too much time on this question, you've already helped a lot. – Greg Nov 04 '16 at 15:04
  • @Amiramix References are quite enough to implement this. – Raphael Nov 06 '16 at 19:26
  • Do you know any example implementations? – Greg Nov 07 '16 at 21:23
  • I don't know any library implementations, no. – Raphael Nov 07 '16 at 23:46