THE SYLLABUS TASK
What you need to solve
Write a program to implement queue and its operations using an ADT.
Use the command table below; integer values are in −1,000,000 to 1,000,000. End with 0. Linked nodes have no fixed item capacity; allocation failure is reported.
UNDERSTAND THE IDEA
Explanation
Queue is a FIFO Abstract Data Type. Its interface is initQueue, enqueue, dequeue, peek, display and destroyQueue; linked nodes are this solution’s chosen implementation.
front points to the earliest remaining item and rear to the last. Enqueue attaches a node directly through rear. Dequeue advances front and frees the old first node.
When dequeue removes the final node, rear must also become NULL. Otherwise a later enqueue could follow a dangling pointer. Allocation failure is checked, and exit frees all nodes.
PLAN BEFORE CODING
Algorithm
- Initialize the empty queue.
- Read one command and its required value.
- Check empty/full/allocation conditions before changing storage.
- Call the operation function; print its result.
- Display in LIFO order for stack or FIFO order for queue.
- Repeat until 0; clean up allocated nodes when present.
CONTROL THE PROGRAM
Commands and input
A command is an operation number followed by its arguments. Whitespace separates tokens; line breaks make your input easier to read. No interactive menu prompts are printed.
| Input | Meaning |
|---|---|
1 value | Enqueue at the rear. |
2 | Dequeue and print the front value. |
3 | Peek at the front without removing it. |
4 | Display from front to rear. |
0 | Exit; release nodes in the ADT version. |
SEE THE CONTROL FLOW
Flowcharts
Follow arrows from Start. Diamonds are decisions; Yes/No labels select a branch. Returning arrows repeat a loop. Function steps group the pointer/index updates explained in the code. Expand the operation charts for more detail. On a phone, scroll sideways within a diagram.
Program command loop
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
/* Read a complete integer token; reject bad or oversized input safely. */
int readInt(int *value) {
char token[64];
int ch;
size_t used = 0;
while ((ch = getchar()) != EOF && isspace((unsigned char)ch)) { }
if (ch == EOF) return 0;
do {
if (used < sizeof token - 1) token[used++] = (char)ch;
else {
while ((ch = getchar()) != EOF && !isspace((unsigned char)ch)) { }
return 0;
}
ch = getchar();
} while (ch != EOF && !isspace((unsigned char)ch));
token[used] = '\0';
char *end;
errno = 0;
long result = strtol(token, &end, 10);
if (errno || *end != '\0' || result < -1000000 || result > 1000000) return 0;
*value = (int)result;
return 1;
}
/* Queue ADT interface implemented with front/rear linked-list pointers. */
typedef struct Node { int data; struct Node *next; } Node;
typedef struct { Node *front, *rear; } Queue;
void initQueue(Queue *q) { q->front = q->rear = NULL; }
int enqueue(Queue *q, int value) {
Node *node = malloc(sizeof *node);
if (!node) return 0;
node->data = value; node->next = NULL;
if (q->rear) q->rear->next = node;
else q->front = node;
q->rear = node; return 1;
}
int dequeue(Queue *q, int *value) {
if (!q->front) return 0;
Node *node = q->front; *value = node->data;
q->front = node->next;
if (!q->front) q->rear = NULL;
free(node); return 1;
}
int peek(const Queue *q, int *value) {
if (!q->front) return 0;
*value = q->front->data; return 1;
}
void display(const Queue *q) {
printf("Queue (front to rear):");
for (const Node *p = q->front; p; p = p->next) printf(" %d", p->data);
putchar('\n');
}
void destroyQueue(Queue *q) { int ignored; while (dequeue(q, &ignored)) { } }
/* 1 value: enqueue; 2: dequeue; 3: peek; 4: display; 0: exit. */
int main(void) {
Queue q; initQueue(&q);
int command, value;
while (readInt(&command)) {
if (command == 0) { destroyQueue(&q); return 0; }
if (command == 1) {
if (!readInt(&value)) break;
puts(enqueue(&q, value) ? "Enqueued." : "Allocation failed; queue unchanged.");
} else if (command == 2) {
if (dequeue(&q, &value)) printf("Dequeued: %d\n", value);
else puts("Underflow: queue is empty.");
} else if (command == 3) {
if (peek(&q, &value)) printf("Front: %d\n", value);
else puts("Queue is empty.");
} else if (command == 4) display(&q);
else break;
}
puts("Invalid or incomplete input. End with command 0.");
destroyQueue(&q); return 1;
}
Code loads into your existing compiler. Enter the sample input there; sign-in and execution rules stay the same.
Compile and run locally
gcc -std=c17 queue-adt.c -o lab
./labOn Windows, run .\lab.exe after compiling with GCC.
UNDERSTAND THE CODE
Line-by-line explanation
These steps follow the exact source above, including its input helper, operations, command handling and cleanup. Line numbers count blank lines; related statements are grouped. Start with main to follow execution, then open the functions it calls.
Start with main → · Data structure and operations →
Line 1Include terminal input and output
#include <stdio.h>stdio.h declares printf, puts, getchar and putchar. The input helper reads characters with getchar; the main program prints operation results using the output functions.
Line 2Include conversion and memory utilities
#include <stdlib.h>stdlib.h declares strtol for converting an integer token. It also declares malloc and free, which the linked structures and tree programs use to obtain and release node storage.
Line 3Include string and memory utilities
#include <string.h>string.h declares utilities such as memset. It is part of the shared header list; merely including a header does not call any function or change stored values.
Lines 4–5Include character tests and conversion error reporting
#include <ctype.h>
#include <errno.h>ctype.h supplies isspace for recognising whitespace. errno.h supplies errno so the program can detect a range error reported by strtol.
Lines 7–8Define a checked integer-input helper
/* Read a complete integer token; reject bad or oversized input safely. */
int readInt(int *value) {The comment states the purpose of readInt. Its int *value parameter receives the address of a caller variable. The helper returns 1 after storing a valid value there, or 0 on failure; these flags are separate from main’s process exit status.
Lines 9–11Reserve a token buffer and its reading state
char token[64];
int ch;
size_t used = 0;token holds up to 63 input characters and a null terminator. ch is int so it can distinguish character input from EOF. used starts at zero and counts the stored token characters.
Lines 12–13Skip leading whitespace and handle end-of-file
while ((ch = getchar()) != EOF && isspace((unsigned char)ch)) { }
if (ch == EOF) return 0;getchar reads one character at a time. The loop discards whitespace before the token. The unsigned-char cast supplies an allowed argument to isspace. If EOF is reached before a token starts, return 0 without changing the caller variable.
Lines 14–15Store token characters while space remains
do {
if (used < sizeof token - 1) token[used++] = (char)ch;The do-while body handles the first non-whitespace character already read. The size check leaves one buffer slot for a null terminator. used++ uses the old index for storage, then advances the count.
Lines 16–19Reject a token that is too long
else {
while ((ch = getchar()) != EOF && !isspace((unsigned char)ch)) { }
return 0;
}When the buffer has no character space left, consume the rest of that token through whitespace or EOF, then return failure. The program does not silently treat a truncated prefix as the full input number.
Lines 20–21Read the next character and decide whether the token continues
ch = getchar();
} while (ch != EOF && !isspace((unsigned char)ch));Keep collecting until whitespace or EOF. The terminating whitespace has been consumed by getchar; it is not stored as part of the token.
Lines 22–24Terminate the string and prepare a checked conversion
token[used] = '\0';
char *end;
errno = 0;Write the null terminator after the stored token. end will identify where conversion stopped. Reset errno before strtol so a previous library error cannot be mistaken for this conversion’s result.
Line 25Convert the complete token as a decimal integer
long result = strtol(token, &end, 10);strtol reads token in base 10 and returns a long. Passing &end lets it write the address of the first unconverted character. A sign such as - is allowed as part of a valid integer token.
Line 26Reject conversion errors, leftover characters and out-of-range values
if (errno || *end != '\0' || result < -1000000 || result > 1000000) return 0;A nonzero errno rejects a reported conversion error. *end must be the null terminator, so input such as 12x is rejected rather than accepted as 12. The allowed value range is -1,000,000 to 1,000,000.
Lines 27–29Store the accepted integer through its address
*value = (int)result;
return 1;
}After validation, cast result to int and assign it through *value. Return 1 for success. The brace ends readInt. The caller variable remains unchanged on the earlier failure returns.
Lines 31–33Define the queue interface and its linked state
/* Queue ADT interface implemented with front/rear linked-list pointers. */
typedef struct Node { int data; struct Node *next; } Node;
typedef struct { Node *front, *rear; } Queue;The comment describes the representation. A Node stores data and a next link. Queue keeps front, the oldest node, and rear, the newest. This supports enqueue and dequeue without traversing the whole queue.
Line 34Initialise both endpoints to NULL
void initQueue(Queue *q) { q->front = q->rear = NULL; }initQueue receives the caller’s state by address. In an empty linked queue both front and rear must be null.
Lines 35–38Allocate and prepare the new rear node
int enqueue(Queue *q, int value) {
Node *node = malloc(sizeof *node);
if (!node) return 0;
node->data = value; node->next = NULL;malloc reserves one Node. On failure return 0 without altering the queue. On success, store value and set next to NULL because the new item will be last.
Lines 39–42Append the node and update both endpoint cases
if (q->rear) q->rear->next = node;
else q->front = node;
q->rear = node; return 1;
}If rear already exists, connect its next to node. If the queue was empty, front must also become node. In both cases set rear to node and return success. The old front is unchanged for a nonempty queue.
Lines 43–44Check whether a dequeue can return an item
int dequeue(Queue *q, int *value) {
if (!q->front) return 0;A null front indicates underflow. Return failure before reading a node or modifying the caller’s output variable.
Lines 45–46Save the old front and move to its successor
Node *node = q->front; *value = node->data;
q->front = node->next;Copy the integer into *value while the old node is alive, then advance q->front to node->next. node still identifies the storage that must be released.
Lines 47–49Clear rear when the last node is removed
if (!q->front) q->rear = NULL;
free(node); return 1;
}If the new front is NULL, the queue became empty, so rear must also become NULL. Otherwise rear would keep a dangling pointer. Free the old front node and return success.
Lines 50–53Peek without removing the front node
int peek(const Queue *q, int *value) {
if (!q->front) return 0;
*value = q->front->data; return 1;
}Return 0 for an empty queue. Otherwise copy the front integer through the output pointer and return 1. No link changes or free calls occur.
Lines 54–58Display from oldest to newest
void display(const Queue *q) {
printf("Queue (front to rear):");
for (const Node *p = q->front; p; p = p->next) printf(" %d", p->data);
putchar('\n');
}Follow next links beginning at front and print each integer. Stop at NULL, then end the line. The const access and separate traversal pointer leave the queue state unchanged.
Line 59Destroy the queue using its dequeue operation
void destroyQueue(Queue *q) { int ignored; while (dequeue(q, &ignored)) { } }Repeated dequeue calls free each front node. ignored receives the integers that are no longer needed. The final successful removal resets both endpoints.
Lines 60–61Document the command protocol and start main
/* 1 value: enqueue; 2: dequeue; 3: peek; 4: display; 0: exit. */
int main(void) {Commands request enqueue, dequeue, peek, display or normal exit. The following driver uses these operations rather than directly editing links.
Lines 62–63Initialise the queue and command variables
Queue q; initQueue(&q);
int command, value;initQueue makes q explicitly empty before any operation. command and value hold checked input and successful output values.
Lines 64–65Read commands and clean up on normal exit
while (readInt(&command)) {
if (command == 0) { destroyQueue(&q); return 0; }Command 0 destroys all remaining nodes and returns success. If command reading fails instead, execution reaches the error cleanup after the loop.
Lines 66–68Read an integer for enqueue
if (command == 1) {
if (!readInt(&value)) break;
puts(enqueue(&q, value) ? "Enqueued." : "Allocation failed; queue unchanged.");Command 1 needs one following value. A failed read breaks to cleanup. Otherwise enqueue reports success or allocation failure without exposing internal node addresses.
Lines 69–71Dequeue with an output parameter
} else if (command == 2) {
if (dequeue(&q, &value)) printf("Dequeued: %d\n", value);
else puts("Underflow: queue is empty.");The caller passes &value. Print it only when dequeue succeeds; underflow leaves no returned queue item to display.
Lines 72–74Peek at the front without removing it
} else if (command == 3) {
if (peek(&q, &value)) printf("Front: %d\n", value);
else puts("Queue is empty.");Command 3 prints the front value on success or an empty-queue message on failure. The next dequeue will still return that same front item.
Lines 75–77Display or reject another command
} else if (command == 4) display(&q);
else break;
}Command 4 traverses the queue. Any remaining command breaks to the shared error path. The brace closes the reading loop.
Lines 78–80Clean up after malformed or incomplete input
puts("Invalid or incomplete input. End with command 0.");
destroyQueue(&q); return 1;
}Print the error message, call destroyQueue to free owned nodes, and return failure. This complements the normal command-0 cleanup.
FOLLOW THE STATE
Dry run
| Step | Operation / state | Result |
|---|---|---|
| Enqueue 10 | insert at rear | Front → 10 ← Rear |
| Enqueue 20 | advance/link rear | Front → 10 → 20 ← Rear |
| Dequeue | remove 10 | Front → 20 ← Rear |
CHECK THE BEHAVIOUR
Sample input & output
These outputs were produced by compiling and running this exact source. Inputs are provided in the stated order; the programs do not print repeated menu prompts.
Sample 1
1 10
1 20
3
4
2
4
0
Enqueued.
Enqueued.
Front: 10
Queue (front to rear): 10 20
Dequeued: 10
Queue (front to rear): 20
Sample 2
2
3
4
1 -5
2
2
0
Underflow: queue is empty.
Queue is empty.
Queue (front to rear):
Enqueued.
Dequeued: -5
Underflow: queue is empty.
Sample 3
1 1
1 2
1 3
1 4
1 5
1 6
2
2
1 7
1 8
4
0
Enqueued.
Enqueued.
Enqueued.
Enqueued.
Enqueued.
Enqueued.
Dequeued: 1
Dequeued: 2
Enqueued.
Enqueued.
Queue (front to rear): 3 4 5 6 7 8
Common mistakes
- Check emptiness before removal or peek.
- Do not confuse stack LIFO with queue FIFO.
- Clear rear after removing the final queue node.
WHY THIS GROWTH RATE?
Time and space complexity
Individual insertion/removal/peek O(1); display O(n). Node storage and cleanup O(n).
Let n be the number of stored items. Enqueue, dequeue and peek use a fixed number of index/pointer updates, with no traversal through the queue. Each operation is O(1), assuming fixed-cost integer work and allocation/free.
Display visits all n items, so it is O(n). Linked-node cleanup also visits and frees n nodes, giving O(n) exit time. The nodes use O(n) storage.
The interface is not itself an algorithm with one complexity: each operation has its own cost. The whole command session depends on how many pushes/enqueues, removals and display requests you issue. Temporary counters and pointers use O(1) extra working space.
Big-O describes growth in the stated input quantity. Fixed factors and lower-order terms are omitted. Supported input bounds keep the educational examples practical; operation costs depend on the actual representation and functions shown.
PREPARE FOR YOUR LAB VIVA
Related viva questions & answers
Try answering aloud, then expand the answer to check your reasoning. These questions focus on the concepts used in this program.
Why does the linked queue maintain both front and rear?
Front identifies the next node to remove, while rear identifies where a new node is appended. Keeping both pointers avoids traversing the entire queue for each enqueue.
What special action is required when enqueuing into an empty linked queue?
The new node becomes both front and rear. There is no old rear to link from, so assigning front is necessary before rear is updated.
Why must rear be cleared when dequeue removes the final node?
Front becomes NULL, so the queue is empty. Leaving rear unchanged would keep a pointer to freed memory, which a later enqueue might incorrectly treat as a valid node.
Why is a newly enqueued node's next set to NULL?
It becomes the final node in this non-circular chain. A null next link marks its end and prevents an uninitialized pointer from being followed during display or later operations.
Why does dequeue copy the node's data before calling free?
The returned value must be read while the node still exists. Reading its data after free would access released storage; the output integer remains usable after the node is released.
Why does this linked queue have no CAPACITY-based full condition?
It allocates a node for each enqueue rather than reserving a fixed array. Growth is limited by available memory and accepted input, and allocation failure is reported without changing the existing queue.