Queue
Queue
Queue
A Queue, also called a first-in-first-out (FIFO) structure, is a linear list in which deletions take
place only at one end of the list, the front of the list, and insertions take place only at the other
end of the list, the rear of the list. The features of a Queue are similar to the features of any queue
of customers at a counter, at a bus stop, at railway reservation counter etc. Another common
rear example are buffers for network communication that temporarily store packets of data arriving
front on a network port A queue can be implemented using arrays or linked lists.
Theoretically, a queue does not have a specific capacity. Regardless of how many elements are
already contained, a new element can always be added. For a data structure the executing
computer will eventually run out of memory, thus limiting the queue size. Queue overflow results
from trying to add an element onto a full queue and queue underflow happens when trying to
remove an element from an empty queue. Front always points to the oldest element (inserted first among all elements) in the queue and
rear points to the location where new element to be inserted. A queue is empty when rear and front point to the same location in the
queue.
In order to add new element (i.e. Insert) in the queue it is necessary to check, is there any free space in the array or not after rear, if not,
then this condition is known as queue overflow. In order to delete an element (i.e. delete) from the queue it is necessary to check, is
there an element in the queue or not, if not, then this condition is known as queue underflow. No element in the queue means the queue
is empty. First, let us study the simplest strategy to implement insert an element in a queue and delete an element from a queue.
Insertion operation: In order to add a new element in the queue it is necessary to check where rear is placed. Normally in the beginning,
the rear points to one end of an array and as elements are added to the queue it moves toward the other end of the array. When rear
reaches the other end of the array it indicates that the queue is full and no more insertion is possible.
In the beginning both rear and front points to the 0th location of an array. As elements are inserted rear moves upward and reaches to
the other end of an array. In order to add elements to the queue it is necessary to check queue overflow. Figure 2.10 shows insertion
operations performed on queue. Insertion operation is performed in constant time hence time complexity is O(1).
0 1 2 3 4 0 1 2 3 4 0 1 2 3 4 0 1 2 3 4
5 5 7 5 7 3
6. Insert 10 in Q -- generates 5 7 3 9 8 5 7 3 9
Queue-overflow condition
fron rear fron rear
t 5. Insert 8 in Q t 4. Insert 9 in Q
SIT,Nagpur
Notes On Algorithms and Data Structures
Deletion operation: Deletion of an element from a queue is only possible if it contains elements otherwise a queue-underflow condition
occurs. If the queue contains elements then elements inserted first will be deleted first. Implementation of insert suggests that the element
inserted first is pointed by front. In order to delete an element from a queue, check stack-underflow condition (front == rear) first, if it
is not then process (or delete) element at front and then increment front. Figure 2.11 shows deletion operations performed on queue.
Deletion operation is performed in constant time hence time complexity is O(1).
0 1 2 3 4 0 1 2 3 4 0 1 2 3 4 0 1 2 3 4
5 7 3 9 8 7 3 9 8 3 9 8 9 8
0 1 2 3 4 0 1 2 3 4
6. Delete from Q -- generates
8
Queue-underflow condition
Traversal operation: The best way to traverse in a queue is to start from the front end and reach up to the rear end. It is not possible to
traverse an empty queue. Following is a code for traversal operation.
void TraverseQ()
{
int i;
if(rear == front)
printf(" \n Queue is empty\n ");
else
{
printf("Traversing in Queue\n");
for(i=front; i< rear; i++)
printf("%d\n", Q[i]);
}
}
The array-based queue is somewhat tricky to implement efficiently. A simple implementation of the array-based queue is not efficient.
Let us consider the case when an array of capacity SIZE is initially empty and SIZE number of elements are inserted into it. These
insertions set the rear at SIZE and now remove all the elements from the queue. These deletions set the front at SIZE. Now one has an
array with no elements but both rear & front are set to SIZE. The condition for queue-full is rear == SIZE hence no more insertion is
possible even though the queue is empty.
Generates Queue-Empty
DelQ() condition
0 1 2 3 4 0 1 2 3 4
5 7 3 9 8
InsQ(10) Generates Queue-Full
rea rear condition
front
r fron
Initial Queue t
There are several possible solutions to solve this problem
1. If at the time of insertion of element in a queue when queue-full condition (rear == SIZE) occurs but queue is not
empty (rear != front) then shift all elements in queue at the beginning of a queue and set front at first element in
queue & rear after the last element of a queue
SIT,Nagpur
Notes On Algorithms and Data Structures
0 1 2 3 4 InsQ(10) 0 1 2 3 4 0 1 2 3 4
Case-1:
3 9 8 3 9 8 3 9 8 10
0 1 2 3 4 InsQ(10) 0 1 2 3 4 0 1 2 3 4
Case-2:
10
front rea
r
3. Assume that there are n elements in the queue. If analogy adopted is all elements of the queue are to be stored in the
last n positions of the array then the last element of the queue is at position SIZE-1 & rear is at SIZE. DelQ()
operation requires O(1) time because the front always points to the first element inserted in the queue. InsQ()
operation requires O(n) time, because all the n elements are shifted one position towards the beginning of the queue
(if queue is not full) to create place at the end of array for insertion of element
0 1 2 3 4
DelQ()
0 1 2 3 4 7 3
5 7 3
front rea
r
front rea InsQ(10) 0 1 2 3 4 0 1 2 3 4
r 5 7 3 5 7 3 10
Initial Queue
SIT,Nagpur
Notes On Algorithms and Data Structures
A more efficient queue representation is obtained by an array CQ[ 0 : n-1] as circular. When rear = n-
56 85 1 next element is entered at CQ[0] in case that spot is free using same convention as before front always
points one position counter clockwise from the first element in queue. Again front = rear iff queue is
2 77 91 empty.
Insertion operation: Initially for a circular queue rear=front=0 to indicate the queue is empty. In
55 45 n-
order to add a new element in the queue rear is advanced to the next position in clockwise direction
1 2
25 and if that location is free then an element is added to the queue.
n- rear After adding n-1 elements in the queue any attempt to insert nth element in the queue generates queue-
0
full condition. In order to insert nth element in the queue, rear is advanced to next position but front also
front 1
points to the same location hence now front=rear indicated queue-full condition. In this
implementation, location pointed by front does not store any element hence at the most the queue can accommodate only n-1 elements.
When queue-full occurs it is necessary to restore rear at the last element added in the queue. Figure 2.12 shows insertion operations
performed on a circular queue. Insertion operation is performed in constant time hence time complexity is O(1).
rear
2 2 2 77
Insert 55 Insert 77
n- 55 n- 55 n-
1 2 1 2 1 2
0 n- rear 0 n- 0 n-
rear front 1 front 1 front 1
t = rear;
rear = (rear + 1) % SIZE;
if (rear == front)
{
printf("\n Circular Queue is full -- Overflow \n");
rear = t;
}
else
CQ[rear] = data;
}
SIT,Nagpur
Notes On Algorithms and Data Structures
Deletion operation: Deletion of an element from a queue is only possible if it contains elements otherwise queue-empty condition
occurs. The condition for queue-empty is rear=front. If the queue contains elements then implementation of insertion states
that front always points one position before the first element added in the list in the counterclockwise direction. In order to delete
elements from a queue, advance front by one position in clockwise direction and delete elements at that location. Figure 2.13 shows
deletion operations performed on a circular queue. Deletion operation is performed in constant time hence time complexity is O(1).
56 85 56 85 front 56 85
2 77 2
91 77 91 2 91
55 45 n- Delete 45 n- Delete 45 n-
1 2 1 2 1 2
25 25 25
front
0 n- rear 0 n- rear 0 n- rear
front 1 1 1
Traversal operation: best way to traverse in circular queue is to start from the front and move in clock-wise direction till rear comes.
void TraverseCQ()
{
int i;
if(rear == front)
printf(" \n Queue is empty\n ");
else
{
printf("Traversing in Circular Queue\n");
i=front;
do
{
SIT,Nagpur
Notes On Algorithms and Data Structures
i=(i+1) % SIZE;
printf("%d\n", CQ[i]);
}while (i != rear);
}
}
In the circular queue implementation test for Queue-full in InsCQ and test for Queue- empty in DelCQ are the same, that is front = rear.
In implementation when the circular queue is full one memory space is free. If one wishes to insert an element in this free space then
will not be able to distinguish between cases full & empty queue. To avoid this, the implementation is permitting a maximum of n-
1 elements rather than n elements to be in queue at any time.
One way to use all n positions would be to use another variable as tag to distinguish between two situations i.e. tag = 0 if the queue is
empty. This would however slow down two procedures. Since insertion and deletion procedures will be used many-times in many
problems involving queues, loss of one position will be more than made up for reduction in computing time.
InsCQB(int data)
{
if (rear == front && tag == 1)
printf("\n Circular Queue is full -- Overflow \n");
else
{
rear = (rear + 1) % SIZE;
if (rear == front)
tag = 1;
CQ[rear] = data;
}
}
DelCQB()
{
if (rear == front && tag == 0)
printf(" \n Circular Queue is empty -- Underflow\n ");
else
{
front = (front +1) % SIZE;
if (front == rear)
tag = 0;
return (CQ[front]);
}
}
void TraverseCQB()
{
int i;
SIT,Nagpur
Notes On Algorithms and Data Structures
Exercise:
1. Suppose a stack implementation supports, in addition, to PUSH and POP, an operation REVERSE, which
reverses the order of the elements on the stack To implement a queue using the above stack implementation,
show how to implement INSERTQ using a single operation and DELETEQ using a sequence of operations.
2. Consider the following sequence of operations on an empty stack.
push(54); push(52); pop(); push(55); push(62); s = pop();
Consider the following sequence of operations on an empty queue.
insertQ(21); insertQ(24); deleteQ(); insert(28); insertQ(32); q = deleteQ();
The value of s + q is ______
3. A letter means insertQ and an asterisk means deleteQ in the following sequence. Give the sequence of values
returned by the deleteQ operations when this sequence of operations is performed on an initially empty queue.
E A S * Y * Q U E * * * S T * * * I O * N * * *
4. Consider the following sequence of stack operations:
insertQ(d), insertQ(h), deleteQ(), insertQ(f), insertQ(s), deleteQ(), deleteQ(), insertQ(m).
Assume the queue is initially empty, what is the sequence of values deleted from the queue, and what is the
final state of the queue?
5. Consider the queues Q1 containing four elements and Q2 containing none (shown as the Initial State in the
figure). The only operations allowed on these two queues are Enqueue (Q, element) and Dequeue (Q). The
minimum number of Enqueue operations on Q1 required to place the elements of Q1 in Q2 in reverse order
(shown as the Final State in the figure) without using any additional storage is ______________.
insert(Q, i);
}
}
What operation is performed by the above function f ?
8. Give a recursive implementation of function int queue_size(Q), that takes as input a queue Q and returns the
number of elements in it. Upon returning, the input queue should contain the same elements in the same order
as when it was called. Use the functions provided by the queue interface only.
9. Implement the function a queue_copy(Q); that does return a copy of its input queue.
10. Implement the function queue_reverse(Q); that destructively returns a queue with the same elements as its
input queue but in reverse order. The input queue shall be empty when the call returns.
11. Implement the function void queue_sort(Q), that sorts its input queue. The resulting queue should be sorted
in ascending order, with the largest item at the front and the smallest at the rear. For simplicity, you may
assume that the queue items have type int rather than string. Your code may use temporary queues but no
other data structures. Besides the functions provided by the queue interface. Hint: an effective way to solve
this exercise is carefully consider what loop invariants should hold at various points.
12. What does the following code fragment print when n is 10? In general, what does it do for other positive
integers n? Initially quue is empty.
insert(0);
insert(1);
for (i = 0; i < n; i++) {
a = deleteQ();
print(a);
b = peekQ(); // read the element at front
insertQ(a + b);
}
13.
For example, in an operating system the runnable processes might be stored in a priority queue, where certain system processes are
given a higher priority than user processes. Similarly, in a network router packets may be routed according to some assigned priorities.
In both of these examples, bounding the size of the queues helps to prevent so-called denial-of-service attacks where a system is
essentially disabled by flooding its task store. This can happen accidentally or on purpose by a malicious attacker
Some Implementations
The priority can be assigned to element implicitly or explicitly. Explicitly some integer value is assigned as priority to an element and
then always deletes the element with highest priority. Implicitly some properties of element (like its value) decide priority of element.
There are two types of priority queue possible based on the value of element (priority assigned implicitly)
• Ascending Priority Queue
• Descending Priority Queue
An ascending priority queue is a collection of items into which elements can be inserted arbitrarily and from which only the smallest
value element can be removed. A descending priority queue is similar but allows deletion of only the largest value element.
Considering different implementation choices and consider the complexity of various operations. The intrinsic ordering of the elements
does determine the results of the basic operations. There are several ways to implement ascending priority queue in fixed size (n) array
Unordered array: Insert element in queue from rear end that is a constant-time operation. However, finding the minimum will
take O(n), since it need scanning of whole portion of the array that’s in use. Consequently, deleting the minimal element also
takes O(n): first find the minimal element, then swap it with the last element in the array, and decrement rear. Figure 2.14
shows insertion & deletion operations performed on priority queue.
SIT,Nagpur
Notes On Algorithms and Data Structures
Initial Queue
0 1 2 3 4 InsQ(10) 0 1 2 3 4
5 3 7 5 3 7 10
Sorted array: To insert element find quickly (in O(log(n)) steps) the place i where insertion is to be done by using binary
search. Make room for insertion by shifting elements that takes O(n) copy operations. Finding the minimum is O(1) (since it
is stored at index 0 in the array). Deleting the minimum value takes O(1) by keeping the array sorted in descending order, or
by keeping two array indices: one for the smallest current element and one for the largest.
Exercise 2.6:
1. A linear list is being maintained circularly in an array CQ(0: n - 1) with front and rear set up as for circular queues.
a) Obtain a formula in terms of front, rear and n for the number of elements in the list.
b) Write a function to delete the k'th element in the list.
c) Write a function to insert an element Y immediately after the k'th element.
What is the time complexity for b) and c)?
2. A priority queue Q is used to implement a stack S that stores chars, PUSH (c) is implemented as INSERT ( x, c, k) where k is
an appropriate integer key chosen by the implementation POP is implemented as DELETE MIN(k). For a sequence of
operation, the keys chosen one is
(A) non-increasing order (B) non-decreasing order
(C) Strictly increasing order (D) Strictly decreasing order
front rear
SIT,Nagpur
Notes On Algorithms and Data Structures
Input-restricted DQ Input-restricted DQ
An output-restricted DQ one where insertion can be made at both ends, but deletion can be made from one end only
Output-restricted DQ Output-restricted DQ
#define SIZE 5
int DQ[SIZE], rear=SIZE/2, front=SIZE/2;
Insertion operation: As elements are added to DQ from either ends, rear and front are moving away from each other (i.e DQ is
expanding). When rear reaches to SIZE insertion from rear end is not possible and when front reaches to -1 insertion from front end is
not possible. In order to add element in DQ it is necessary to check queue overflow. Figure 2.15 shows insertion operations performed
on double ended queue.
0 1 2 3 4 0 1 2 3 4
Initial Queue 3
DqIns_front(int data)
{
if ( front == -1)
printf(" Insertion not possible from front\n");
else
{
if(rear == front+1)
front--;
DQ[front] = data;
front--;
}
DqIns_rear(int data)
{
if (rear == SIZE)
SIT,Nagpur
Notes On Algorithms and Data Structures
Deletion operation: Deletion of element from a double ended queue (DQ) is only possible if it contains elements otherwise queue-
underflow condition occurs. As elements are deleted from either ends of DQ, rear and front are moving towards each other (i.e DQ is
shrinking). DQ is empty if front and rear are equal. Figure 2.16 shows deletion operations performed on double ended queue.
0 1 2 3 4 0 1 2 3 4
Empty 5 3 9
Queue
rea fron rear
r
front t
Figure 2.16. Deletion operation on Double Ended Queue of SIZE=5
DqDel_front()
{
if (front == rear )
printf(" Double Ended Queue is empty\n");
else
{
front++;
if(rear == front+1)
rear--;
}
DqDel_rear()
{
if (rear == front )
printf(" Double Ended Queue is empty\n");
else
{
rear--;
if(rear == front+1)
front++;
}
SIT,Nagpur
Notes On Algorithms and Data Structures
# define SIZE 5
int Stack[SIZE], top1= -1, top2=SIZE;
Push: let top1 and top2 are pointers moving in stacks S1 and S2 respectively. Every insertion
of element in stack S1 increments top1 whereas every insertion of element in S2 decrements Push top2.
Hence after every insertion in stacks top1 and top2 are catching each other and as long as Stack S2 top2 they are
not overlapping or crossing (means top1 < top2) each other it is possible to add elements in both
the stacks. PushS1 is function to add element in stack S 1 and PushS2 is function to add m-1 element
in stack S2. ..
X ..
void PushS1( int data) 1
{
if (top1+1 == top2) 0
printf("\n Stack S1 is full -- Overflow \n");
Stack S1 top1
else
{
top1++;
Stack[top1]=data;
}
}
SIT,Nagpur
Notes On Algorithms and Data Structures
Pop: Deletion of element from either stack is only possible if stack contains elements
otherwise stack-underflow condition occurs. If stack contains elements then element inserted Pop last
will be deleted first. Implementation of PushS1 and PushS2 suggests that element inserted last Stack S2 top2 in
stack S1 and S2 is at location the top1and top2 respectively. Every deletion in either stacks moves
m-1
top1 and top2 away from each other. Pointers top1 and top2 are moving towards either ends of the
array. In order to delete element from either stack, check stack-underflow condition first. .. Stack-
underflow (or empty) condition for stack S1 is top1 == -1 and for stack S2 is top2 == SIZE. If X .. stack
is not empty then every deletion of element from stack S1 decrements top1 whereas every 1
deletion of element from S2 increments top2. PopS1 is function to delete element from stack S1 and
0
PopS2 is function to delete element from stack S2.
void PopS1() Stack S1 top1
{
if(top1 == -1)
printf(" \n stack S1 is empty -- Underflow\n ");
else
{
printf(" \n Element deleted from stack S1: %d\n", Stack[top1]);
top1--;
}
}
void PopS2()
{
if(top2 == SIZE)
printf(" \n stack S2 is empty -- Underflow\n ");
else
{
printf(" \n Element deleted from stack S2: %d\n", Stack[top2]);
top2++;
}
}
Traversal operation: Processing of each element in stack can be done by either start from the top and reach up to the bottom of the
stack or start from the bottom of the stack and reach to the top of the stack. TraverseS1 is function to traverse in stack S1 and TraverseS2
is function to traverse in stack S2.
void TraverseS1()
{
int i;
if(top1 == -1)
printf(" \n stack S1 is empty\n ");
else
{
printf("Traversing in Stack S1\n");
for(i= top1; i>-1; i--)
printf("%d\n", Stack[i]);
}
}
void TraverseS2()
{
int i;
if(top2 == SIZE)
printf(" \n stack S2 is empty\n ");
else
{
printf("Traversing in Stack S2\n");
SIT,Nagpur
Notes On Algorithms and Data Structures
For example, in order to represent 3 stacks (i.e. n=3) in an array of size 10 (i.e. m=10), the top and BM for each stacks are initialize as
follows
The ith stack grow in lower memory indexes than the (i+1)th stack. The ith stack can grow from BM[i]+1 up to BM[i+1] before it catches
up with the (i=1)th stack.
SIT,Nagpur
Notes On Algorithms and Data Structures
Pop: Deletion of element from stack is only possible if stack contains elements otherwise stack-empty condition occurs. Every deletion
of element from ith stack decrements ith top by 1. When top of ith stack is equal to BM of ith stack then no more deletion from ith stack is
possible and it is stack-empty condition.
void Pop(int i)
{
if(top[i] == BM[i])
printf(" \n stack %d is empty -- Underflow\n ",i);
else
{
printf(" \n Element deleted from stack %d: %d\n", i, Stack[top[i]]);
top[i]=top[i]-1;
}
}
Traversal operation: An ith stack can grow from BM[i]+1 up to BM[i+1] and top[i] always points to topmost element in i th stack. Hence
processing of elements in ith stack means visiting elements from top[i] to BM[i]+1.
void Traverse(int i)
{
int j;
if(top[i] == BM[i])
printf(" \n stack %d is empty\n ",i);
else
{
printf("Traversing in Stack %d\n",i);
for(j= top[i]; j>BM[i]; j--)
printf("%d\n", Stack[j]);
}
}
SIT,Nagpur