C Programming Language Program implements queue using linked list.
Queue - Linked List |
#include<stdio.h>
#include<stdlib.h>
//THIS IS TO CREATE THE NODES
struct Node {
int data;
struct Node* next;
};
// GLOBAL VARIABLES TO SAVE THE FRONT OF THE QUEUE AND THE REAR END
struct Node* front = NULL;
struct Node* rear = NULL; //
//TO INSERT AN INTEGER TO THE QUEUE
void Enqueue(int x) {
struct Node* temp =
(struct Node*)malloc(sizeof(struct Node));
temp->data =x;
temp->next = NULL;
if(front == NULL && rear == NULL){
front = rear = temp;
return;
}
rear->next = temp;
rear = temp;
}
//TO REMOVE THE FIRST IN QUEUE
void Dequeue() {
struct Node* temp = front;
if(front == NULL) {
printf("Queue is Empty\n");
return;
}
if(front == rear) {
front = rear = NULL;
}
else {
front = front->next;
}
free(temp);
}
// TO SEE THE FIRST INTEGER IN THE QUEUE
int Front() {
if(front == NULL) {
printf("Queue is empty\n");
return;
}
return front->data;
}
//TO PRINT ALL OF THE QUEUE
void Print() {
struct Node* temp = front;
while(temp != NULL) {
printf("%d ",temp->data);
temp = temp->next;
}
printf("\n");
}
//CHECK IF QUEUE IS EMPTY - > return 1 of empty , 0 if not empty
int isEmpty(){
struct Node* temp= front;
if (front == NULL ) return 1;
else return 0;
}
int main(){
struct myQueue* mQ = NULL;
//start adding to queue 3,4,6,7
Enqueue(3);
Enqueue(4);
Enqueue(6);
Enqueue(7);
// print the queue
Print();
//remove first integer from the queue so we will have 4,6,7 only
Dequeue();
//print the updated queue
Print();
}
Here, besides discussing the queue data structure, we will demonstrate how to better hide the details of a data structure using ADTs/CDTs and generic type definitions.
- Abstract idea of a queue:The queue is another data structure. A physical analogy for a queue is a line at a bank. When you go to the bank, customers go to the rear (end) of the line and customers come off of the line (i.e., are serviced) from the front of the line.
Aside: In fact, other English-speaking countries use this term for a line, e.g., they might say "Queue up!" rather than "Get in a line!"
Like a stack, a queue usually holds things of the same type. We usually draw queues horizontally. Here's a queue of characters with 3 elements:
queue ------------- | a | b | c | ------------- ^ ^ | | front rear
Here are the minimal set of operations we'd need for an abstract queue:
Enter
(orInsert
)Places an object at the rear of the queue.
Delete
(orRemove
)Removes an object from the front of the queue and produces that object.
IsEmpty
Reports whether the queue is empty or not.
- Order produced by a queue:
Queues are useful because they produce a certain order in which the contents of the queue are used. Let's see what order that is by looking at a queue of characters. Now, what would a particular sequence ofEnter
andDelete
s do to this queue:
queue ------------- | a | b | c | ------------- ^ ^ | | front rear
Enter(queue, 'd')
...
queue ----------------- | a | b | c | d | ----------------- ^ ^ | | front rear
ch = Delete(queue)
...
queue ch ------------- ----- | b | c | d | | a | ------------- ----- ^ ^ | | front rear
Answer: Queues produce FIFO order. Remember that stacks produce LIFO order.
- Implementing a queue:Since a queue usually holds a bunch of items with the same type, we could implement a queue with an array. However, here we'll use a linked listimplementation.
Remember that a linked list can:
- hold as many objects as memory can hold (i.e., it's not a fixed size).
- grow easily without having to copy over the old contents.
Note: Copying over is the problem incurred with a dynamic array that has to be made bigger.
Recall that linked lists are made up of things called nodes:
A Node ----------------- | | | | Data | Link -+--> | | | -----------------
A node contains 2 main parts:
- some data and,
- a link to the node after it (for a singly-linked list).
list | v --------- --------- --------- | a | --+---> | b | --+---> | c | 0 | --------- --------- ---------
A logical choice would be to make the beginning of the linked list the front of the queue and the end of the linked list the rear of the queue.
Given this representation, we'll rename the link to the beginning of the list to front:
front | v --------- --------- --------- | a | --+---> | b | --+---> | c | 0 | --------- --------- ---------
Answer: Yes, however, we see one inefficiency in that, unlike the stack, things aren't added and removed from the same end. To add something to a queue, that thing must go on the rear. Currently, the only way to get to the end is to traverse to the end of the list. We can be more efficient if we keep track of therear of the queue with a direct link.
Our augmented representation looks as follows:
front rear-----------------+ | | v v --------- --------- --------- | a | --+---> | b | --+---> | c | 0 | --------- --------- ---------
Empty Queue
Last, we'll need a representation of an empty queue. We'll make at least the front, but also maybe rear, link to nothing when the queue is empty. In C, this means they will containNULL
pointers. - C code:Now, let's think about how to actually code this queue of characters in C...
We'll put this data structure in its own module, thus, we'll want to create filesqueue.h
andqueue.c
.
There are 2 main parts to a C data structure: the data types needed to keep track of a queue and the functions needed to implement queue operations.
- Organization of data types for a queue:Our goal is to have a type for a queue, so that people using our queue can define queue variables, and pass them to queue functions. For example, they'll do something like the following:
#include "queue.h" ... type-of-a-queue q1, q2; char ch; /* Setup queues. */ QueueEnter(ref-to-q1, 'a'); QueueEnter(ref-to-q2, 'b'); ch = QueueDelete(ref-to-q1); /* Cleanup queues. */
However, we'll also want to abstract the type of an element. In other words, we want to make it very easy to change the type of an element by isolating it in one place, in a type definition.
Let's consider the organization of types between our queue module files:
queue.h queue.c ------- ------- #include "queue.h" type-of-an-element type-of-a-queue
.h
) for the queue will need to have a type for the queue (for people to define queue variables) and the type of an element (for functional prototypes)...
However, while people using the queue will need to know the type-of-a-queue, and thus, it will need to be in the header file, we want to hide any of theinternals of the type-of-a-queue in the.c
file. Thus, the type-of-a-queue should be broken up into an abstract and concrete part.
- The concrete part is a structure with all the pieces of data needed for a queue, and which is hidden in the implementation. (Remember that we isolate this type from users of the queue, i.e., they do not need to know that the queue is implemented as a linked list. Furthermore, isolating the type will prevent them from being able to mess up the queue.)
- The abstract part is a pointer to such a structure, which is publicly available to users of a queue.
queue.h queue.c ------- ------- #include "queue.h" type-of-an-element abstract-type-of-a-queue concrete-type-of-a-queue
queue.h queue.c ------- ------- #include "queue.h" type-of-an-element type-of-a-node abstract-type-of-a-queue concrete-type-of-a-queue
queue.h
inqueue.c
since we always include the header for a module in the implementation (.c
) part of the module.
- ADTs and CDTs:
Let's start filling in the types for a queue. First, we want to write a queue that is very generic. The fact that this queue holds a character is only particular to this exercise. It should be easy to change the type of things held in the queue.
Let's introduce a type name for the type of things held in the queue.
typedef char queueElementT;
Next, elements of the queue are being stored in a linked list. Recall that linked lists are made up of nodes that contain both an element and a pointer to thenext node.
Thus, we can define a node by combining these 2 parts into one type with astruct
(and place it in the implementation file):
typedef struct queueNodeTag { queueElementT element; struct queueNodeTag *next; } queueNodeT;
Since these pointers have to do with the implementation of the queue, we put them in the concrete-type-of-a-queue,struct queueCDT
(which also goes in the implementation file):
typedef struct queueCDT { queueNodeT *front, *rear; } queueCDT;
typedef struct queueCDT *queueADT;
NOTE: C will allow us to use a pointer to a type that has not been defined yet only if that type is a structure that we name with a tag, as instruct queueCDT
above. That is why we must use a tag name with the CDT when we define it inqueue.c
. In other words, the struct mentioned inqueue.h
,struct queueCDT
, must be named using the form "struct tag-name
" and have the same tag name as the CDT defined inqueue.c
.
Finally, we have the following:
queue.h queue.c ------- ------- #include "queue.h" typedef char queueElementT typedef struct queueNodeTag { queueElementT element; struct queueNodeTag *next; } queueNodeT; typedef struct queueCDT typedef struct queueCDT { *queueADT; queueNodeT *front, *rear; } queueCDT;
In other words, things that need to be part of the interface go inqueue.h
and things that are hidden as part of the implementation go inqueue.c
.