I'm struggling to get my head around this data structure. In my course we use the following code to implement a Queue using linked lists:
class Queue
{
private Item sentinel , tail;
public Queue () {
sentinel = new Item(0,null);
tail = sentinel;
}
public void enqueue(int value) {
tail.next = new Item(value , null);
tail = tail.next;
}
public int dequeue ()
{
int value = sentinel.next.value;
sentinel.next = sentinel.next.next;
return value;
}
}
I don't see how this should work, when we call the constructor method we have sentinel[0|null]
and then we let tail=sentinel
, so tail[0|null]
. By calling .enqueue(x)
, we get the following:
[0|pointer to tail], tail[x|null]
If we now call .dequeue()
, sentinel.next
is null
.
I asked my lecturer about this and I got the following reply, which doesn't really make it clearer for me: "When we call the constructor via Queue q = new Queue();
we will create the dummy item sentinel, whose value is zero and whose next pointer is null. At the same time we let tail point to sentinel. So clearly adding elements to the queue is not a problem."
I don't see where we let let tail point to the sentinel :/
Your intuition is, in fact, correct. This class does not work properly.
When you enqueue stuff, it works well - adds things at the tail, and everything works.
The trouble starts when you dequeue all the items. When you do that,
sentinel.next
will becomenull
- you have removed everything from the queue - buttail
will still be pointing to the last item you enqueued. So basically you have a tail that's disconnected from yoursentinel
.You can enqueue stuff then, and it will be appended after the old tail, but it will no longer be reachable from
sentinel
. If you try to dequeue anything further, you'll get aNullPointerException
.To demonstrate this, I added the following method to your
Queue
class (and added the Item class since you didn't put it in your post):Now, with this main program:
You get:
What you should have gotten was:
To achieve this, you have to correct the tail when you reach it when dequeuing.
In truth, one should also protect the
dequeue()
method against being called when the queue is empty. Throwing aNullPointerException
is not nice, a more sensible exception would be nicer. And in fact it helps create a more elegantdequeue()
, where instead of correcting the tail, we simply change the sentinel - throw out the old sentinel and use the item we just dequeued as our new sentinel:If we didn't null-check, then
sentinel
would becomenull
when we attempt to dequeue, and then we'd never be able to dequeue again. By null-checking, we make sure we have an item to dequeue, and it becomes the sentinel. If it also happens to be the last item in the queue, then we havetail
andsentinel
pointing to the same item, as they did in the beginning, so we know we can continue adding items and they will be reachable throughsentinel
.Note that method for checking whether the queue was empty before attempting to dequeue would also come in handy.