A queue has three main operations:
These operations are defined in queue.h, which is in the directory /home/cs140/spring-2004/include. To use them, you must link your code with /home/cs140/spring-2004/objs/queue.o. Since queue.h makes use of Jval's, you'll have to link with jval.o 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.
Here's the code (in queuehead.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; if (argc != 2) { fprintf(stderr, "usage: queuehead n\n"); exit(1); } if ((sscanf(argv[1], "%d", &n) !=1) || (n < 0)) { fprintf(stderr, "n (%s) must be an integer that is >= 0\n", argv[1]); exit(1); } q = new_queue(); is = new_inputstruct(NULL); i = 0; while (i < n && get_line(is) >= 0) { queue_enqueue(q, new_jval_s(strdup(is->text1))); i++; } while (!queue_empty(q)) { printf("%s", jval_s(queue_dequeue(q))); } }It works just fine. Of course, it's easier to write head without queues, but this is a nice illustration of using queues:
UNIX> cat input Give Him Six! UNIX> queuehead 2 < input Give Him UNIX> queuehead 100 < input Give Him Six! UNIX> queuehead 0 < input UNIX>
Like the stack implementation, the queue implementation will have two structs -- a header struct (TrueQueue) and a struct (QueueNode) for each node on the list. Here are the typedef's. You must use these typedef's in your implementation in Lab 6:
typedef struct queuenode { struct queuenode *link; Jval val; } QueueNode; typedef struct { QueueNode *head; QueueNode *tail; int size; } TrueQueue;Note the QueueNode is just like the StackNode -- it contains a value and a pointer to the next item in the queue. The header struct is slightly different. It contains two QueueNode pointers -- one to the item that is the head of the queue, and one to the item that is the tail of the queue. If the queue is empty, both of these are NULL. Otherwise, head points to the item that will be returned by queue_head() and queue_dequeue(), and tail points to the last item that was put onto the queue with queue_enqueue().
Here's the subtle part. The link field of each item on the queue points to the next item that was enqueued. If the item is the last one enqueued (i.e., the item is the one pointed to by the queue's tail pointer), then its link field is NULL.
It is the job of all the queue routines to work with this structure.
tq ----> |-------------| | head = NULL | | tail = NULL | | size = 0 | |-------------|Next comes the call queue_enqueue(q, new_jval_i(1)). This will create a new node for the queue, which is the only item on the queue. Therefore, both the head and tail pointers point to this new node, and the node's link field is NULL:
tq ----> |-------------| | head = -------/---->|-------------| | tail = ------/ | link = NULL | | size = 1 | | val.i = 1 | |-------------| |-------------|Next comes the call queue_enqueue(q, new_jval_i(2)). This will create the second node on the queue, which becomes the tail. The head remains the same, but its link field now points to the second node:
tq ----> |-------------| | head = ------------>|-------------| | tail = -------\ | link = -------+->|-------------| | size = 2 | | | val.i = 1 | | | link = NULL | |-------------| | |-------------| | | val.i = 2 | | | |-------------| \---------------------/You'll note that both tq->tail and tq->head->link point to the new node.
Next comes the call queue_enqueue(q, new_jval_i(3)). This will create the third node on the queue, which becomes the tail. The head still remains the same. The old tail (the second node) now has a link field pointing to the new tail: Here is the picture:
tq-->|-------------| | head = ------------>|------------| | tail = -------\ | link = ------->|-------------| | size = 3 | | | val.i = 1 | | link = -------+->|-------------| |-------------| | |------------| | val.i = 2 | | | link = NULL | | |-------------| | | val.i = 3 | | | |-------------| \--------------------------------------/You should study this picture and the above, and think about how to get from the above to this picture. That will be the heart of your implementation of queue_enqueue().
Now, we call i = jval_i(queue_dequeue(q)). The queue_dequeue() call will return a Jval whose value is the integer 1, and the queue will become:
tq ----> |-------------| | head = ------------>|-------------| | tail = -------\ | link = -------+->|-------------| | size = 2 | | | val.i = 2 | | | link = NULL | |-------------| | |-------------| | | val.i = 3 | | | |-------------| \---------------------/Thus, the first node of the queue is deleted from the queue, freed, and the head pointer is moved to point to the next node. We call i = jval_i(queue_dequeue(q)) again, and this time the queue_dequeue() call returns a Jval whose value is the integer 2. The queue now becomes:
tq ----> |-------------| | head = -------/---->|-------------| | tail = ------/ | link = NULL | | size = 1 | | val.i = 3 | |-------------| |-------------|Next comes the call queue_enqueue(q, new_jval_i(4)). By now, you should be able to figure these things out -- here's the queue:
tq ----> |-------------| | head = ------------>|-------------| | tail = -------\ | link = -------+->|-------------| | size = 2 | | | val.i = 3 | | | link = NULL | |-------------| | |-------------| | | val.i = 4 | | | |-------------| \---------------------/Next is another queue_dequeue(q) which returns a Jval whose value is the integer 3, and the queue becomes:
tq ----> |-------------| | head = -------/---->|-------------| | tail = ------/ | link = NULL | | size = 1 | | val.i = 4 | |-------------| |-------------|Finally, the last call to queue_dequeue(q) is made, which returns a Jval whose value is the integer 4, and the queue becomes empty:
tq ----> |-------------| | head = NULL | | tail = NULL | | size = 0 | |-------------|
tq ----> |-------------| | head = ------------>|-------------| | tail = -------\ | link = -------+->|-------------| | size = 2 | | | val.i = 1 | | | link = NULL | |-------------| | |-------------| | | val.i = 2 | | | |-------------| \---------------------/I'm going to show pictorally what happens when you call queue_enqueue(q, new_jval_i(3)). I will assume that val is a parameter to queue_enqueue, and there is a local variable QueueNode *qn. When queue_enqueue(q, new_jval_i(3)) is called, the state first looks like:
val.i = 3 qn = ? tq ----> |-------------| | head = ------------>|-------------| | tail = -------\ | link = -------+->|-------------| | size = 2 | | | val.i = 1 | | | link = NULL | |-------------| | |-------------| | | val.i = 2 | | | |-------------| \---------------------/Now, we malloc a new QueueNode, and set its fields:
val.i = 3 qn ------------------------------------------------------------>|-------------| tq-->|-------------| | link = NULL | | head = ------------>|------------| | val.i = 3 | | tail = -------\ | link = ------+->|-------------| |-------------| | size = 3 | | | val.i = 1 | | | link = NULL | |-------------| | |------------| | | val.i = 2 | | | |-------------| \--------------------/We then link it into the list:
val.i = 3 qn -----------------------------------------------------------+->|-------------| tq-->|-------------| | | link = NULL | | head = ------------>|------------| | | val.i = 3 | | tail = -------\ | link = ------+->|-------------| | |-------------| | size = 3 | | | val.i = 1 | | | link = -------/ |-------------| | |------------| | | val.i = 2 | | | |-------------| \--------------------/And then reset tail to be this new node:
val.i = 3 qn -----------------------------------------------------------+->|-------------| tq-->|-------------| | | link = NULL | | head = ------------>|------------| | | val.i = 3 | | tail = -------\ | link = -------->|-------------| | |-------------| | size = 3 | | | val.i = 1 | | link = -------+ |-------------| | |------------| | val.i = 2 | | | |-------------| | | | \---------------------------------------/When we return, qn and val go away, and we're left with the picture we desire:
tq-->|-------------| | head = ------------>|------------| | tail = -------\ | link = ------->|-------------| | size = 3 | | | val.i = 1 | | link = -------+->|-------------| |-------------| | |------------| | val.i = 2 | | | link = NULL | | |-------------| | | val.i = 3 | | | |-------------| \--------------------------------------/
val = ? qn = ? tq-->|-------------| | head = ------------>|------------| | tail = -------\ | link = ------->|-------------| | size = 3 | | | val.i = 1 | | link = -------+->|-------------| |-------------| | |------------| | val.i = 2 | | | link = NULL | | |-------------| | | val.i = 3 | | | |-------------| \--------------------------------------/First, we set val to queue_head(q):
val.i = 1 qn = ? tq-->|-------------| | head = ------------>|------------| | tail = -------\ | link = ------->|-------------| | size = 3 | | | val.i = 1 | | link = -------+->|-------------| |-------------| | |------------| | val.i = 2 | | | link = NULL | | |-------------| | | val.i = 3 | | | |-------------| \--------------------------------------/And then we set qn to be the head of the queue:
val.i = 1 qn ----------------------\ tq-->|-------------| | | head = ----------+->|------------| | tail = -------\ | link = ------->|-------------| | size = 3 | | | val.i = 1 | | link = -------+->|-------------| |-------------| | |------------| | val.i = 2 | | | link = NULL | | |-------------| | | val.i = 3 | | | |-------------| \--------------------------------------/Next, we remove the head node from the queue, and have the queue's head pointer point to the next node in the queue.
val.i = 1 qn ----------------------\ tq-->|-------------| | | head = --------\ \->|------------| | tail = -------\ \ | link = ------+->|-------------| | size = 3 | | | | val.i = 1 | | | link = -------+->|-------------| |-------------| | | |------------| | | val.i = 2 | | | link = NULL | | | | |-------------| | | val.i = 3 | | \------------------/ | |-------------| \---------------------------------------/Then, we free qn and decrement the size:
val.i = 1 qn = ? tq-->|-------------| | head = ---------\ | tail = -------\ \-------------------->|-------------| | size = 2 | | | link = -------+->|-------------| |-------------| | | val.i = 2 | | | link = NULL | | |-------------| | | val.i = 3 | | | |-------------| \---------------------------------------/And we return val. When we're done, the queue looks like it should:
tq ----> |-------------| | head = ------------>|-------------| | tail = -------\ | link = -------+->|-------------| | size = 2 | | | val.i = 2 | | | link = NULL | |-------------| | |-------------| | | val.i = 3 | | | |-------------| \---------------------/with the above two examples, you should be able to figure out the hard parts of implementing queues: writing queue_enqueue() and queue_dequeue().