CS140 Lecture notes -- Queues

  • Jim Plank
  • Directory: ~cs140/www-home/notes/Queues
  • Lecture notes: http://web.eecs.utk.edu/~jplank/plank/classes/cs140/Notes/Queues/
  • Wed Sep 19 15:05:45 EDT 2007

    Queues

    A queue is a second linked data structure. It is a ``FIFO'' data structure, meaning ``First-in, first-out.'' Most real-life waiting situations are queues -- at the bank teller, at the Kroger, at the traffic light, etc.

    A queue has three main operations:

    Of course, Queue is a (void *).

    These operations are defined in /home/plank/cs140/include/queue.h. To use them, you must link your code with /home/cs140/objs/queue.o. /home/plank/cs140/objs/queue.o. Since queue.h makes use of Jval's, you'll have to link with libfdr.a as well.

    Queuesimp.c shows a very simple example of using a queue. First, we enqueue three integers on the queue -- 1, 2 and 3. Then we call queue_dequeue() twice, and print out the values. Finally, we push 4 onto the queue, and call queue_dequeue() twice more, printing out the values.

    Here's the code:

    #include <stdio.h>
    #include "jval.h"
    #include "queue.h"
    
    main()
    {
      Queue q;
      int i;
    
      q = new_queue();
    
      queue_enqueue(q, new_jval_i(1));
      queue_enqueue(q, new_jval_i(2));
      queue_enqueue(q, new_jval_i(3));
    
      i = jval_i(queue_dequeue(q));
      printf("First dequeue: %d\n", i);
      i = jval_i(queue_dequeue(q));
      printf("Second dequeue: %d\n", i);
    
      queue_enqueue(q, new_jval_i(4));
    
      i = jval_i(queue_dequeue(q));
      printf("Third dequeue: %d\n", i);
      i = jval_i(queue_dequeue(q));
      printf("Fourth dequeue: %d\n", i);
    }
    
    

    And here's its output:

    UNIX> queuesimp
    First dequeue: 1
    Second dequeue: 2
    Third dequeue: 3
    Fourth dequeue: 4
    UNIX> 
    
    Queues are conceptually simpler than stacks -- a queue is simply like any line you wait in -- first come, first serve. To give it the same visual picture as the stack, again, we maintain a list of items, but this time we put items in one side, and take them off the other.

    Thus, after the first three calls to queue_enqueue(), we have a list (1,2,3). The first call to queue_dequeue() removes the 1, and leaves (2,3). The second call to queue_dequeue() removes the 2, and leaves (3). The last call to queue_enqueue() turns the list into (3,4), and the subsequent queue_dequeue() calls return 3 and 4 respectively.


    Queuetail

    Writing the tail program is much more straightforward with queues than with stacks. In fact, a queue is the proper data structure for the operation. You simply enqueue each line, and when the number of lines read becomes greater than n, you also dequeue a line for each line you enqueue. When you are done, the queue holds either the last n lines, or the entire file (if it is less than n lines). So you simply dequeue each element and print it out as you dequeue.

    Here's the code (in queuetail.c):

    #include <stdio.h>
    #include <string.h>
    #include "queue.h"
    #include "fields.h"
     
    main(int argc, char **argv)
    {
      Queue q;
      int n, i;
      IS is;
      Jval v;
    
      if (argc != 2) {
        fprintf(stderr, "usage: queuetail n\n");
        exit(1);
      }
    
      n = atoi(argv[1]);
      if (n < 0) exit(1); 
    
      q = new_queue();
      is = new_inputstruct(NULL);
    
      while (get_line(is) >= 0) {
        queue_enqueue(q, new_jval_s(strdup(is->text1)));
        if (queue_size(q) > n) {
          v = queue_dequeue(q);
          free(v.s);
        }
      }
    
      while (!queue_empty(q)) {
        printf("%s", jval_s(queue_dequeue(q)));
      }
    } 
    


    Implementation Details

    Like stacks, you may implement queues by having a linked data structure. Unlike a stack, each node on a queue points to the next node enqueued, so the pointers go from the front of the queue to the rear. Moreover, you must have two pointers in your header structure -- a pointer to the front and a pointer to the rear. The code is in queue.c.

    First, here are the struct declarations:

    typedef struct queue_node {
      struct queue_node *link;
      Jval val;
    } Queue_Node;
    
    typedef struct {
      int size;
      Queue_Node *front;
      Queue_Node *rear;
    } True_Queue;
    
    And here are three examples of queues:

    As with stacks, we'll only go over the subtle routines -- queue_enqueue() and queue_dequeue(). First, queue_enqueue() allocates a new node, and puts it behind the rear node:


    void queue_enqueue(Queue q, Jval jv)
    {
      True_Queue *tq;
      Queue_Node *qn;
      
      tq = (True_Queue *) q;
      qn = (Queue_Node *) malloc(sizeof(Queue_Node));
      if (qn == NULL) { perror("malloc"); exit(1); }
      qn->link = NULL;
      qn->val = jv;
      if (tq->size == 0) {
        tq->front = qn;
        tq->rear = qn;
      } else {
        tq->rear->link = qn;
        tq->rear = qn;
      }
      tq->size++;
      return;
    }
    

    Here is a pictoral example:

    To dequeue a node, you remove the front node of the queue, and set front to the next node on the queue:


    Jval queue_dequeue(Queue q)
    {
      True_Queue *tq;
      Jval v;
      Queue_Node *qn;
    
      tq = (True_Queue *) q;
      if (tq->size == 0) {
        fprintf(stderr, "Error: queue_dequeue called on an empty queue\n");
        exit(1);
      }
      v = tq->front->val;
      qn = tq->front;
      tq->front = qn->link;
      free(qn);
      tq->size--;
      return v;
    }
    

    Again, here is a pictoral example:

    Redrawn, it looks as follows: