Why we need circular queue?
When we discussed queues, we decided to have two index variables f and r, which would maintain the two ends of the queue. If we follow the illustration below, we would see that our queue gets full when element 8 is pushed in the queue.
In other words, we can only enqueue in a queue until the queue isn't full.
Figure 1: Using two integer variables to maintain the ends of a queue
Now, by dequeuing first three elements we will observe that our queue is still full as the rear end i at the array's threshold. But it has space of three elements that we just removed.
In circular queues, we mainly focus on the point that we don't increment our indices linearly. Linearly increasing indicies cause the case of overflow when our index reaches the limit, which is size-1.
In linear increment, i becomes i+1.
But in circular increment : i becomes (i+1)%size. This gives an upper cap to the maximum value making the index repeat itself.
And this makes us start from the beginning once we reach the threshold of the array. Refer to the illustration below to visualize the movement of the cursor.
Conversion of a linear queue to a circular queue ↑
And this is the circular implementation of the same array we used to implement linearly. This allows the leftover spaces to be used again.
This wheel type array is called the circular queue.
struct circularQueue
{
int size;
int f;
int r;
int* arr;
};
In main, we had declared a struct circularQueue q, and initialized its instances. Here is a subtle change, we don’t initialize circular queues’ f and r with -1, rather 0. Since -1 is unreachable in circular incrementation. Leave everything as it is.
//In main
struct circularQueue q;
q.size = 4;
q.f = q.r = 0;
q.arr = (int*) malloc(q.size*sizeof(int));
if our f equals r, then there is no element in our queue, and this is the case of an empty queue.
int isEmpty(struct circularQueue *q){
if(q->r==q->f){
return 1;
}
return 0;
}
If our (r+1)%size equals f, then there is not space left in out queue, and this is the case of a full queue.
int isFull(struct circularQueue *q){
if((q->r+1)%q->size == q->f){
return 1;
}
return 0;
}
void enqueue(struct circularQueue *q, int val){
if(isFull(q)){
printf("This Queue is full");
}
else{
q->r = (q->r +1)%q->size;
q->arr[q->r] = val;
printf("Enqued element: %d\n", val);
}
}
int dequeue(struct circularQueue *q){
int a = -1;
if(isEmpty(q)){
printf("This Queue is empty");
}
else{
q->f = (q->f +1)%q->size;
a = q->arr[q->f];
}
return a;
}
#include<stdio.h>
#include<stdlib.h>
struct circularQueue
{
int size;
int f;
int r;
int* arr;
};
int isEmpty(struct circularQueue *q){
if(q->r==q->f){
return 1;
}
return 0;
}
int isFull(struct circularQueue *q){
if((q->r+1)%q->size == q->f){
return 1;
}
return 0;
}
void enqueue(struct circularQueue *q, int val){
if(isFull(q)){
printf("This Queue is full");
}
else{
q->r = (q->r +1)%q->size;
q->arr[q->r] = val;
printf("Enqued element: %d\n", val);
}
}
int dequeue(struct circularQueue *q){
int a = -1;
if(isEmpty(q)){
printf("This Queue is empty");
}
else{
q->f = (q->f +1)%q->size;
a = q->arr[q->f];
}
return a;
}
int main(){
struct circularQueue q;
q.size = 4;
q.f = q.r = 0;
q.arr = (int*) malloc(q.size*sizeof(int));
// Enqueue few elements
enqueue(&q, 12);
enqueue(&q, 15);
enqueue(&q, 1);
printf("Dequeuing element %d\n", dequeue(&q));
printf("Dequeuing element %d\n", dequeue(&q));
printf("Dequeuing element %d\n", dequeue(&q));
enqueue(&q, 45);
enqueue(&q, 45);
enqueue(&q, 45);
if(isEmpty(&q)){
printf("Queue is empty\n");
}
if(isFull(&q)){
printf("Queue is full\n");
}
return 0;
}