Queues

Course links

Queues as an abstract data type

Sometimes we want a data structure that provides access to its elements on ``first-in, first-out'' basis, rather than the ``last-in, first-out'' constraint that a stack imposes. (For example, 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.)

Such a structure 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

The implementation of queues in Scheme is somewhat trickier than the implementation of stacks. Again, we'll keep the elements of the queue in a list. However, it turns out that the enqueue operation can be slightly faster if we represent an empty queue by a list containing one element, a ``dummy header,'' and store the actual queue elements after this header, oldest first. The dummy header is not inserted by enqueue and cannot be removed by the dequeue. It is not there to provide a value, but just to keep the list from becoming null, so that one can always apply the set-cdr! procedure to it without first testing to see whether it is null. The fact that the underlying list never becomes completely null is an invariant of this implementation of queues.

The other novel feature of this implementation is that we'll actually be accessing the list through three different fields, fore, aft, and size. The fore field always contains the entire list structure, beginning with the dummy header; (cdr fore) is the list of the actual elements of the queue, and (cadr fore) is the first real element of the queue (when it is not empty). The aft field, on the other hand, is always a one-element list; it contains the last element of the queue, except when the queue is empty, in which case the aft field contains the dummy header. The size field keeps track of the number of elements in the queue, not including the dummy header. Again, it is an invariant of this implementation that size is one less than the length of the underlying list.

The following box-and-pointer diagram shows a queue into which the symbols a, b, and c have been enqueued, in that order:

(box-and-pointer queue diagram)

Here is the constructor for queue objects:

;;; make-queue: construct and return a queue

;; Givens:
;;   None

;; Result:
;;   QUEUE, a procedure

;; Preconditions:
;;   None.

;; Postconditions:
;;   (1) QUEUE is initially empty.
;;   (2) When 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
  (lambda ()
    (let* ((fore (list 'dummy-header))
           (aft fore)
           (size 0))
      (lambda (message . arguments)
        (cond ((eq? message ':empty?)
               (if (null? arguments)
                   (zero? size)
                   (error "queue:empty?: no arguments are required")))

              ((eq? message ':enqueue!)
               (cond ((null? arguments)
                      (error "queue:enqueue!: an argument is required"))
                     ((not (null? (cdr arguments)))
                      (error "queue:enqueue!: no more than one argument is required"))
                     (else

                      ; Attach a new cons cell behind the current aft
                      ; element.

                      (set-cdr! aft (list (car arguments)))

                      ; Advance AFT so that it is once more a list
                      ; containing only the last element.

                      (set! aft (cdr aft))

                      ; Increment SIZE.

                      (set! size (+ size 1)))))

              ((eq? message ':dequeue!)
               (cond ((not (null? arguments))
                      (error "queue:dequeue!: no argument is required"))
                     ((null? (cdr fore))
                      (error "queue:dequeue!: the queue is empty"))
                     (else

                      ; Recover the first element of the queue (not
                      ; including the dummy header).

                      (let ((removed (cadr fore)))

                        ; Splice out the element to be dequeued.

                        (set-cdr! fore (cddr fore))

                        ; If we just spliced out the last element of the
                        ; queue, reset AFT so that it holds the dummy
                        ; header.

                       (if (null? (cdr fore))
                           (set! aft fore))

                       ; Decrement SIZE.

                       (set! size (- size 1))

                       removed))))

              ((eq? message ':front)
               (cond ((not (null? arguments))
                      (error "queue:front: no argument is required"))
                     ((null? (cdr fore))
                      (error "queue:front: the queue is empty"))
                     (else
                      (cadr fore))))

              ((eq? message ':size)
               (if (null? arguments)
                   size
                   (error "queue:size: no argument is required")))

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

Acknowledgements

The principal author of this reading is Professor Henry Walker.