CSC 151-02, Fall 2006 : Schedule : Reading 39


Reading 39: Queues

Summary: We consider a new abstract data type, the queue. Queues provide a different order of access to data than do stacks.

Contents:


Review of Stacks

As you may recall, the stack is an abstract data type that provides a few key operations: push something on to the stack and pop the most recently pushed value. Stacks have a host of applications, including mimicing the steps of recursion, providing efficient computation in reverse-polish notation, and matching tags in HTML. In all of these cases, it is important to be able to deal with objects in a last in, first out (LIFO) order.

An Alternative: Queues

However, there are also times we want a data type that provides access to its elements on first-in, first-out basis. For example, you would consider it unfair if a clerk at a store handled customers so that the one who had waited the least amount of time was served first. Similarly, it might be prudent to treat that pile of unpaid bills a little differently, adding new elements at the bottom of the pile rather than the top. Paying off the most recent bill first, as in a stack, can make one's other, older creditors a little testy.

A data structure that supports this pattern is called a queue. Like a line of people waiting for some service, a queue acquires new elements at one end (the rear of the queue) and releases old elements at the other (the front). Here is the abstract data type definition for queues, with the conventional names for the operations:

Queues in Scheme

An obvious way to implement queues in Scheme is with a single list. However since the first in is the first out, we cannot use the same end of the list to both enqueue and dequeue values. If we choose to represent the back of the queue as the front of the list then we can implement enqueue with cons, but then dequeue will be more complex, and time consuming. Similarly, if we choose to represent the front of the queue as the front of the list then we can implement dequeue with car and cdr, but then enqueue will be more complex, and time consuming.

An Alternative Implementation

A solution to this problem is to do both. We represent the rear of the queue with a list, queue-rear, and the front of the queue with a list, queue-front. For instance, if the items in the queue were inserted in the order: a, b, c, d, the a were dequeued, and then e and f were inserted, then queue-front might be the list (b c d) and queue-rear might be the list (f e). If we want to enqueue an item, we simply cons it onto queue-rear. If we want to dequeue an item from the queue, and queue-front is not empty, then we simply return the car of queue-front and set queue-front equal to the cdr

For example,

Command queue-front queue-rear Returns
After some steps (q r s) (v u)
enqueue 'w (q r s) (w v u)
enqueue 'x (q r s) (x w v u)
dequeue (r s) (x w v u) q
dequeue (s) (x w v u) r
dequeue () (x w v u) s

What if queue-front is empty and all the items in the queue are in queue-rear? Notice that the item that we want to dequeue is the first one enqueued, which is the last item in queue-rear. So we want to set queue-front equal to the reverse of queue-rear and then set queue-rear to null. Then we can return the car of queue-front and set queue-front equal to the cdr of queue-front.

Continuing our example above:

Command queue-front queue-rear Returns
After some steps () (x w v u)
dequeue (v w x) () u
enqueue y (v w x) (y)

While this makes some calls of dequeue rather slow, in general it is fast since each item is only transfered from queue-rear to queue-front once.

In our implementation an extra field keeps track of the number of elements in the queue, the length of queue-front plus the length of queue-rear.

Here is the constructor for queue objects:

;;; Procedure:
;;; make-queue
;;; Parameters:
;;; (none)
;;; Produces:
;;; queue, a procedure
;;; Preconditions:
;;; (none)
;;; Postconditions:
;;; (1) queue initially represents the empty queue.
;;; (2) Whenever queue is invoked with the argument :empty?, it reports
;;; whether it is empty.
;;; (3) When queue has been invoked with the first argument :enqueue! and
;;; a second argument, say new-value, new-value is at the rear, and
;;; all other values in the queue are in front of it, in order by
;;; time of enqueuing.
;;; (4) When queue has been invoked with the argument :dequeue!, it
;;; returns the longest-enqueued value, the one at the front, and
;;; retains all other values, in order by time of enqueuing.
;;; (5) When queue is invoked with the argument :front, it returns the
;;; value at the front (available for dequeuing).
;;; (6) When queue is invoked with the argument :size, it returns the
;;; number of values in the queue.
;;; (7) It is an error to give queue any other argument when invoking it.
;;; (8) When queue is invoked with the first argument :empty?, :dequeue!,
;;; :front, or :size, it is an error to give it two or more arguments.
;;; (9) When queue is invoked with the first argument :enqueue!, it is an
;;; error to give it only one argument or three or more.
(define make-queue
(let ((set-field! (lambda (field val) (vector-set! field 0 val)))
(get-field (lambda (field) (vector-ref field 0))))
(lambda ()
(let ((front (vector null))
(rear (vector null))
(size (vector 0)))
(lambda (message . arguments)
(cond
((eq? message ':type)
'queue)

((eq? message ':->string)
"#<queue>()")

((eq? message ':empty?)
(if (null? arguments)
(zero? (vector-ref size 0))
(error 'queue:empty? "no arguments are permitted")))

((eq? message ':enqueue!)
(cond
((null? arguments)
(error "queue:enqueue!: an argument is required"))
((not (null? (cdr arguments)))
(error "queue:enqueue!: only one argument permitted"))
(else
; Place the new value on the rear of the queue.
(set-field! rear (cons (car arguments) (get-field rear)))
; Increment the size.
(set-field! size (+ 1 (get-field size))))))

((eq? message ':dequeue!)
(cond ((not (null? arguments))
(error "queue:dequeue!: no argument is permitted"))
((zero? (vector-ref size 0))
(error "queue:dequeue!: the queue is empty"))
(else
; Move stuff from rear to front if necessary.
(if (null? (get-field front))
(begin
(set-field! front (reverse (get-field rear)))
(set-field! rear null)))
; Recover the first element of the queue.
(let ((removed (car (get-field front))))
; Remove the element to be dequeued.
(set-field! front (cdr (get-field front)))
; Decrement size.
(set-field! size (- (get-field size) 1))
; Return that which we removed
removed))))

((eq? message ':front)
(cond ((not (null? arguments))
(error "queue:front: no argument is permitted"))
((zero? (get-field size))
(error "queue:front: the queue is empty"))
(else
; Copy stuff from rear to front if necessary.
(if (null? (get-field front))
(begin
(set-field! front (reverse (get-field rear)))
(set-field! rear null)))
; Return the appropriate element
(car (get-field front)))))

((eq? message ':size)
(if (null? arguments)
(get-field size)
(error "queue:size: no argument is permitted")))

(else (error 'queue "unrecognized message"))))))))

Janet Davis (davisjan@cs.grinnell.edu)

Created November 26, 2006 based on http://www.cs.grinnell.edu/~rebelsky/Courses/CS151/2006F/Readings/queues.html
Last Modified November 26, 2006
With thanks to Sam Rebelsky, John David Stone, Henry Walker