14. LinkedQueue
Given the definition of a queue, how can it be implemented?
Remember, the what and the how are separate
Things needed for the implementation
A container
A way to keep track of the front/head
A way to keep track of the rear/tail
A way to keep track of the size
14.1. Implementing a Queue with a Linked Structure
14.1.1. Edge Cases
What would the
LinkedQueue
look like if the queue is empty?What if it only had one element?
14.2. Implementation
5/**
6 * Implementation of a queue with a linked structure as the container. The class contains a static inner class defining
7 * a node for the linked structure.
8 *
9 * @param <T> Type of elements that are to be in the queue
10 */
11public class LinkedQueue<T> implements Queue<T> {
12
13 private Node<T> front;
14 private Node<T> rear;
15 private int size;
16
17 /**
18 * Create an empty LinkedQueue.
19 */
20 public LinkedQueue() {
21 front = null;
22 rear = null;
23 size = 0;
24 }
Like the
Stack
implementations, theLinkedQueue
implements theQueue
interfaceThe constructor creates an empty queue
Both
front
andrear
referencenull
since there are noNode
objects containing any elements
14.2.1. enqueue
29 @Override
30 public boolean enqueue(T element) {
31 Node<T> toEnqueue = new Node<>(element);
32
33 // If nothing is in the queue, then the front is null and the enqueued element will become the front. If the
34 // queue is not empty, the enqueued element is added after the current rear.
35 if (isEmpty()) {
36 front = toEnqueue;
37 } else {
38 rear.setNext(toEnqueue);
39 }
40 // Either way, the rear of the queue will be the newly enqueued element.
41 rear = toEnqueue;
42 size++;
43 return true;
44 }
The above
enqueue
method has some nuance to it
14.2.1.1. Enqueuing into an Empty Queue
This is an edge case
If the queue is empty, both
front
andrear
referencenull
When this is the case, the element being enqueued will end up being the only element in the queue
Thus, both
front
andrear
need to reference the newNode
containing the enqueued elementThe new
Node
will be both the first and last thing in the queue
When looking at the code, this would result in
Create a new
Node
with the element to be enqueuedSet
front
to reference the newly addedNode
Update
rear
to reference the newly addedNode
Update
size
14.2.1.2. Enqueuing into a Nonempty Queue
This is the general case
Notice how this is just adding to the rear of a linked structure
If the queue is not empty, both
front
andrear
reference aNode
It is possible for
front
andrear
to reference the sameNode
This happens where there is only one element in the queue
When looking at the code, this would result in
Create a new
Node
with the element to be enqueuedAdd the new
Node
after the currentrear
Node
Update
rear
to reference the newly addedNode
Update
size
What is the computational complexity of an
enqueue
?
14.2.2. dequeue
& first
48 @Override
49 public T dequeue() {
50 if (isEmpty()) {
51 throw new NoSuchElementException("Empty queue");
52 }
53 T returnElement = front.getData();
54 front = front.getNext();
55 size--;
56 // The front will update correctly regardless. The rear must be set to null if the queue is now empty.
57 if (isEmpty()) {
58 rear = null;
59 }
60 return returnElement;
61 }
62
63 @Override
64 public T first() {
65 if (isEmpty()) {
66 throw new NoSuchElementException("Empty queue");
67 }
68 return front.getData();
69 }
Like
LinkedStack
andArrayStack
, trying to access something from the empty queue throws an exceptiondequeue
does a remove/delete from the front of a linked structureNotice the edge case of a
dequeue
resulting in an empty queueThe update to the
front
will not be a problemfront = front.getNext()
will setfront
tonull
sincefront.getNext()
in this situation would returnnull
The trouble is with
rear
— typicallyrear
is left alone with adequeue
However, if the
dequeue
resulted in an empty queue,rear
would be left referencing theNode
that was just removedThe solution is to simply set it to
null
if the stack is empty after the dequeue
Note
Although not true in general, with the provided implementation of the LinkedQueue
, missing this edge case in the
dequeue
would not actually cause a problem since the enqueue
was written such that it also checks for the
empty case.
What is the computational complexity of a
dequeue
?
14.3. Queue Variation — Priority Queue
Think of triage at a hospital
It somewhat operates on a first-come-first-served basis
But if someone is there for a cut thumb and someone else comes in with an arrow sticking out of their knee, they will be helped first
In other words, it’s first-come-first-served, but those with a priority value deemed more important will jump the line
14.3.1. What
Everything would be the same except
dequeue
It would need to get the element with the most important priority of all those in the queue
If there is a tie, then use first-come-first-serve to break the tie
14.3.2. How
There is an implementation decision to be made
On
enqueue
, insert the element into the queue such that the queue is always ordered based on priority and time of arrivalThis requires a linear search to find the right place to insert
This would make
enqueue
\(O(n)\)This makes
dequeue
\(O(1)\) since it simply removes from the front of the queue
On
dequeue
, search for and remove the element with the most important priorityThis requires a linear search to find the element to remove
This would make
dequeue
\(O(n)\)This makes
enqueue
\(O(1)\) since it simply adds to the rear of the queue
Which implementation is better?
14.4. For Next Time
Read Chapter 5 Section 6
6 pages
14.4.1. Playing Code
Download and play with
If everything was done correctly, the following code from
PlayingLinkedQueue
should work
1import java.util.NoSuchElementException;
2
3public class PlayingLinkedQueue {
4 public static void main(String[] args) {
5 // Create a LinkedQueue
6 Queue<Integer> myQueue = new LinkedQueue<>();
7
8 // Check queue is empty
9 System.out.println(myQueue.size());
10 System.out.println(myQueue.isEmpty());
11 System.out.println(myQueue);
12
13 // Test enqueue
14 myQueue.enqueue(0);
15 myQueue.enqueue(1);
16 myQueue.enqueue(2);
17 myQueue.enqueue(3);
18 myQueue.enqueue(4);
19 System.out.println(myQueue.size());
20 System.out.println(myQueue.isEmpty());
21 System.out.println(myQueue);
22
23 // Test enqueue more
24 myQueue.enqueue(10);
25 myQueue.enqueue(11);
26 myQueue.enqueue(12);
27 myQueue.enqueue(13);
28 myQueue.enqueue(14);
29 System.out.println(myQueue.size());
30 System.out.println(myQueue.isEmpty());
31 System.out.println(myQueue);
32
33 // Test first
34 System.out.println(myQueue.first());
35 System.out.println(myQueue.size());
36 System.out.println(myQueue.isEmpty());
37 System.out.println(myQueue);
38
39 // Test dequeue
40 System.out.println(myQueue.dequeue());
41 System.out.println(myQueue.dequeue());
42 System.out.println(myQueue.dequeue());
43 System.out.println(myQueue.dequeue());
44 System.out.println(myQueue.dequeue());
45 System.out.println(myQueue.dequeue());
46 System.out.println(myQueue.dequeue());
47 System.out.println(myQueue.dequeue());
48 System.out.println(myQueue.dequeue());
49 System.out.println(myQueue.dequeue());
50 System.out.println(myQueue.size());
51 System.out.println(myQueue.isEmpty());
52 System.out.println(myQueue);
53
54 // Test first and dequeue throwing exception
55 try {
56 myQueue.first();
57 } catch (NoSuchElementException e) {
58 e.printStackTrace();
59 }
60 try {
61 myQueue.dequeue();
62 } catch (NoSuchElementException e) {
63 e.printStackTrace();
64 }
65 }
66}