0% found this document useful (0 votes)
14 views

Data Structure Notes

Uploaded by

IT sacwc
Copyright
© © All Rights Reserved
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
14 views

Data Structure Notes

Uploaded by

IT sacwc
Copyright
© © All Rights Reserved
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
You are on page 1/ 98

Unit II

Stacks and Queues


Data Structure and Types
What are Data Structures?

Data structure is a storage that is used to store and organize data. It is a way of arranging data
on a computer so that it can be accessed and updated efficiently.
Depending on your requirement and project, it is important to choose the right data structure
for your project. For example, if you want to store data sequentially in the memory, then you
can go for the Array data structure.

Array data Structure Representation

Types of Data Structure

Basically, data structures are divided into two categories:


 Linear data structure
 Non-linear data structure

Linear data structures


In linear data structures, the elements are arranged in sequence one after the other. Since
elements are arranged in particular order, they are easy to implement.
However, when the complexity of the program increases, the linear data structures might not
be the best choice because of operational complexities.
Popular linear data structures are:

1. Array Data Structure

In an array, elements in memory are arranged in continuous memory. All the elements of an
array are of the same type. And, the type of elements that can be stored in the form of arrays
is determined by the programming language.

An array with each element represented by an index

2. Stack Data Structure

In stack data structure, elements are stored in the LIFO principle. That is, the last element
stored in a stack will be removed first.
It works just like a pile of plates where the last plate kept on the pile will be removed first.

In a stack, operations can be perform only from one end (top here).
3. Queue Data Structure
Unlike stack, the queue data structure works in the FIFO principle where first element stored
in the queue will be removed first.
It works just like a queue of people in the ticket counter where first person on the queue will
get the ticket first.

In a queue, addition and removal are performed from separate ends.

4. Linked List Data Structure


In linked list data structure, data elements are connected through a series of nodes. And, each
node contains the data items and address to the next node.

A linked list

Non-linear data structures


Unlike linear data structures, elements in non-linear data structures are not in any sequence.
Instead they are arranged in a hierarchical manner where one element will be connected to
one or more elements.

Non-linear data structures are further divided into graph and tree based data structures.
1. Graph Data Structure
In graph data structure, each node is called vertex and each vertex is connected to other
vertices through edges.
Graph data structure example
Popular Graph Based Data Structures:
 Spanning Tree and Minimum Spanning Tree
 Strongly Connected Components
 Adjacency Matrix
 Adjacency List

2. Trees Data Structure


Similar to a graph, a tree is also a collection of vertices and edges. However, in tree data
structure, there can only be one edge between two vertices.

Tree data structure example


Popular Tree based Data Structure
 Binary Tree
 Binary Search Tree
 AVL Tree
 B-Tree
 B+ Tree
 Red-Black Tree

Linear Vs Non-linear Data Structures


Now that we know about linear and non-linear data structures, let's see the major differences
between them.

Linear Data Structures Non Linear Data Structures

The data items are arranged in sequential The data items are arranged in non-
order, one after the other. sequential order (hierarchical manner).

All the items are present on the single The data items are present at different
layer. layers.

It can be traversed on a single run. That is, It requires multiple runs. That is, if we
if we start from the first element, we can start from the first element it might not be
traverse all the elements sequentially in a possible to traverse all the elements in a
single pass. single pass.

Different structures utilize memory in


The memory utilization is not efficient. different efficient ways depending on the
need.

The time complexity increase with the data


Time complexity remains the same.
size.

Example: Arrays, Stack, Queue Example: Tree, Graph, Map

Array
Definition
o Arrays are defined as the collection of similar type of data items stored at contiguous memory
locations.
o Arrays are the derived data type in C programming language which can store the primitive
type of data such as int, char, double, float, etc.
o Array is the simplest data structure where each data element can be randomly accessed by
using its index number.
o For example, if we want to store the marks of a student in 6 subjects, then we don't need to
define different variable for the marks in different subject. Instead of that, we can define an
array which can store the marks in each subject at the contiguous memory locations.
The array marks [10] defines the marks of the student in 10 different subjects where each
subject marks are located at a particular subscript in the array i.e. marks [0] denotes the
marks in first subject, marks [1] denotes the marks in 2nd subject and so on.

Properties of the Array

1. Each element is of same data type and carries a same size i.e. int = 4 bytes.
2. Elements of the array are stored at contiguous memory locations where the first element is
stored at the smallest memory location.
3. Elements of the array can be randomly accessed since we can calculate the address of each
element of the array with the given base address and the size of data element.
For example, in C language, the syntax of declaring an array is like following:
int arr[10]; char arr[10]; float arr[5];

Need of using Array


In computer programming, the most of the cases requires to store the large number of data of
similar type. To store such amount of data, we need to define a large number of variables. It
would be very difficult to remember names of all the variables while writing the programs.
Instead of naming all the variables with a different name, it is better to define an array and
store all the elements into it.

In the following example, we have marks of a student in six different subjects. The problem
intends to calculate the average of all the marks of the student.
In order to illustrate the importance of array, we have created two programs, one is without
using array and other involves the use of array to store marks.

Program without array:


1. #include <stdio.h>
2. void main ()
3. {
4. int marks_1 = 56, marks_2 = 78, marks_3 = 88, marks_4 = 76, marks_5 = 56, marks_6 = 8
9;
5. float avg = (marks_1 + marks_2 + marks_3 + marks_4 + marks_5 +marks_6) / 6 ;
6. printf(avg);
7. }

Program by using array:


1. #include <stdio.h>
2. void main ()
3. {
4. int marks[6] = {56,78,88,76,56,89);
5. int i;
6. float avg;
7. for (i=0; i<6; i++ )
8. {
9. avg = avg + marks[i];
10. }
11. printf(avg);
12. }

Complexity of Array operations

Time and space complexity of various array operations are described in the following table.
Time Complexity

Algorithm Average Case Worst Case


Access O(1) O(1)

Search O(n) O(n)

Insertion O(n) O(n)

Deletion O(n) O(n)

Space Complexity
In array, space complexity for worst case is O (n).

Advantages of Array
o Array provides the single name for the group of variables of the same type therefore, it is
easy to remember the name of all the elements of an array.
o Traversing an array is a very simple process, we just need to increment the base address of
the array in order to visit each element one by one.
o Any element in the array can be directly accessed by using the index.

Memory Allocation of the array


As we have mentioned, all the data elements of an array are stored at contiguous locations in
the main memory. The name of the array represents the base address or the address of first
element in the main memory. Each element of the array is represented by a proper indexing.

The indexing of the array can be defined in three ways.

1. 0 (zero - based indexing) : The first element of the array will be arr[0].
2. 1 (one - based indexing) : The first element of the array will be arr[1].
3. n (n - based indexing) : The first element of the array can reside at any random index number.
In the following image, we have shown the memory allocation of an array arr of size 5. The
array follows 0-based indexing approach. The base address of the array is 100th byte. This
will be the address of arr[0]. Here, the size of int is 4 bytes therefore each element will take 4
bytes in the memory.
In 0 based indexing, If the size of an array is n then the maximum index number, an element
can have is n-1. However, it will be n if we use 1 based indexing.

Accessing Elements of an array


To access any random element of an array we need the following information:
1. Base Address of the array.
2. Size of an element in bytes.
3. Which type of indexing, array follows?
Address of any element of a 1D array can be calculated by using the following formula:
Byte address of element A[i] = base address + size * ( i - first index)

Example:
1. In an array, A[-10 ..... +2 ], Base address (BA) = 999, size of an element = 2 bytes,
2. find the location of A[-1].
3. L(A[-1]) = 999 + [(-1) - (-10)] x 2
4. = 999 + 18
5. = 1017

Passing array to the function:


As we have mentioned earlier that, the name of the array represents the starting address or the
address of the first element of the array. All the elements of the array can be traversed by
using the base address.

The following example illustrate, how the array can be passed to a function.

Example:
1. #include <stdio.h>
2. int summation(int[]);
3. void main ()
4. {
5. int arr[5] = {0,1,2,3,4};
6. int sum = summation(arr);
7. printf("%d",sum);
8. }
9. int summation (int arr[])
10. {
11. int sum=0,i;
12. for (i = 0; i<5; i++)
13. {
14. sum = sum + arr[i];
15. }
16. return sum;
17. }

The above program defines a function named as summation which accepts an array as an
argument. The function calculates the sum of all the elements of the array and returns it.

2D Array

2D array can be defined as an array of arrays. The 2D array is organized as matrices which
can be represented as the collection of rows and columns.
However, 2D arrays are created to implement a relational database look alike data structure.
It provides ease of holding bulk of data at once which can be passed to any number of
functions wherever required.

How to declare 2D Array


The syntax of declaring two dimensional array is very much similar to that of a one
dimensional array, given as follows.
int arr[max_rows][max_columns];
However, it produces the data structure which looks like following.

Above image shows the two dimensional array, the elements are organized in the form of
rows and columns. First element of the first row is represented by a[0][0] where the number
shown in the first index is the number of that row while the number shown in the second
index is the number of the column.

How do we access data in a 2D ARRAY?

Due to the fact that the elements of 2D arrays can be random accessed. Similar to one
dimensional arrays, we can access the individual cells in a 2D array by using the indices of
the cells. There are two indices attached to a particular cell, one is its row number while the
other is its column number.
However, we can store the value stored in any particular cell of a 2D array to some variable x
by using the following syntax.

1. int x = a[i][j];
Where i and j is the row and column number of the cell respectively.
We can assign each cell of a 2D array to 0 by using the following code:
1. for ( int i=0; i<n ;i++)
2. {
3. for (int j=0; j<n; j++)
4. {
5. a[i][j] = 0;
6. }
7. }

Initializing 2D Arrays

We know that, when we declare and initialize one dimensional array in C programming
simultaneously, we don't need to specify the size of the array. However this will not work
with 2D arrays. We will have to define at least the second dimension of the array.

The syntax to declare and initialize the 2D array is given as follows.


int arr[2][2] = {0,1,2,3};

The number of elements that can be present in a 2D array will always be equal to (number of
rows * number of columns).

Example : Storing User's data into a 2D array and printing it.

C Example :
1. #include <stdio.h>
2. void main ()
3. {
4. int arr[3][3],i,j;
5. for (i=0;i<3;i++)
6. {
7. for (j=0;j<3;j++)
8. {
9. printf("Enter a[%d][%d]: ",i,j);
10. scanf("%d",&arr[i][j]);
11. }
12. }
13. printf("\n printing the elements ....\n");
14. for(i=0;i<3;i++)
15. {
16. printf("\n");
17. for (j=0;j<3;j++)
18. {
19. printf("%d\t",arr[i][j]);
20. }
21. }
22. }

Mapping 2D array to 1D array

When it comes to map a 2 dimensional array, most of us might think that why this mapping is
required. However, 2 D arrays exists from the user point of view. 2D arrays are created to
implement a relational database table lookalike data structure, in computer memory, the
storage technique for 2D array is similar to that of an one dimensional array.

The size of a two dimensional array is equal to the multiplication of number of rows and the
number of columns present in the array. We do need to map two dimensional array to the one
dimensional array in order to store them in the memory.

A 3 X 3 two dimensional array is shown in the following image. However, this array needs to
be mapped to a one dimensional array in order to store it into the memory.
There are two main techniques of storing 2D array elements into memory

1. Row Major ordering


In row major ordering, all the rows of the 2D array are stored into the memory contiguously.
Considering the array shown in the above image, its memory allocation according to row
major order is shown as follows.

first, the 1st row of the array is stored into the memory completely, then the 2nd row of the
array is stored into the memory completely and so on till the last row.
2. Column major ordering
According to the column major ordering, all the columns of the 2D array are stored into the
memory contiguously. The memory allocation of the array which is shown in in the above
image is given as follows.

first, the 1st column of the array is stored into the memory completely, then the 2nd row of the
array is stored into the memory completely and so on till the last column of the array.

Calculating the Address of the random element of a 2D array


Due to the fact that, there are two different techniques of storing the two dimensional array
into the memory, there are two different formulas to calculate the address of a random
element of the 2D array.

By Row Major Order


If array is declared by a[m][n] where m is the number of rows while n is the number of
columns, then address of an element a[i][j] of the array stored in row major order is
calculated as,

Address (a[i][j]) = B. A. + (i * n + j) * size


Where, B. A. is the base address or the address of the first element of the array a[0][0] .
Example:
1. a[10...30, 55...75], base address of the array (BA) = 0, size of an element = 4 bytes .
2. Find the location of a[15][68].
3. Address(a[15][68]) = 0 +
4. ((15 - 10) x (68 - 55 + 1) + (68 - 55)) x 4
5. = (5 x 14 + 13) x 4
6. = 83 x 4
7. = 332 answer

By Column major order


If array is declared by a[m][n] where m is the number of rows while n is the number of
columns, then address of an element a[i][j] of the array stored in row major order is
calculated as,

Address(a[i][j]) = ((j*m)+i)*Size + BA
where BA is the base address of the array.

Example:
1. A [5 ... +20][20 ... 70], BA = 1020, Size of element = 8 bytes.
2. Find the location of a[0][30].
3. Address [A[0][30]) = ((30-20) x 24 + 5) x 8 + 1020 = 245 x 8 + 1020 = 2980 bytes

Stack

A stack is an Abstract Data Type (ADT), commonly used in most programming languages. It
is named stack as it behaves like a real-world stack, for example – a deck of cards or a pile of
plates, etc.
A real-world stack allows operations at one end only. For example, we can place or remove a
card or plate from the top of the stack only. Likewise, Stack ADT allows all data operations
at one end only. At any given time, we can only access the top element of a stack.

This feature makes it LIFO data structure. LIFO stands for Last-in-first-out. Here, the
element which is placed (inserted or added) last, is accessed first. In stack terminology,
insertion operation is called PUSH operation and removal operation is called POP operation.

Stack Representation
The following diagram depicts a stack and its operations −

A stack can be implemented by means of Array, Structure, Pointer, and Linked List. Stack
can either be a fixed size one or it may have a sense of dynamic resizing. Here, we are going
to implement stack using arrays, which makes it a fixed size stack implementation.

Basic Operations

Stack operations may involve initializing the stack, using it and then de-initializing it. Apart
from these basic stuffs, a stack is used for the following two primary operations −
 push() − Pushing (storing) an element on the stack.
 pop() − Removing (accessing) an element from the stack.
When data is Pushed onto stack.

To use a stack efficiently, we need to check the status of stack as well. For the same purpose,
the following functionality is added to stacks –

 peek() − get the top data element of the stack, without removing it.
 isFull() − check if stack is full.
 isEmpty() − check if stack is empty.

At all times, we maintain a pointer to the last PUSHed data on the stack. As this pointer
always represents the top of the stack, hence named top. The top pointer provides top value
of the stack without actually removing it.

First we should learn about procedures to support stack functions −


peek ()

Algorithm of peek() function −


begin procedure peek
return stack[top]
end procedure

Implementation of peek () function in C programming language −


Example
int peek() {
return stack[top];
}

isfull()
Algorithm of isfull() function –

begin procedure isfull

if top equals to MAXSIZE


return true
else
return false
endif

end procedure

Implementation of isfull() function in C programming language −


Example

bool isfull() {
if(top == MAXSIZE)
return true;
else
return false;
}

isempty()
Algorithm of isempty() function –

begin procedure isempty

if top less than 1


return true
else
return false
endif

end procedure
Implementation of isempty() function in C programming language is slightly different. We
initialize top at -1, as the index in array starts from 0. So we check if the top is below zero or
-1 to determine if the stack is empty.

Here's the code −


Example
bool isempty() {
if(top == -1)
return true;
else
return false;
}

Push Operation

The process of putting a new data element onto stack is known as a Push Operation. Push
operation involves a series of steps −
 Step 1 − Checks if the stack is full.
 Step 2 − If the stack is full, produces an error and exit.
 Step 3 − If the stack is not full, increments top to point next empty space.
 Step 4 − Adds data element to the stack location, where top is pointing.
 Step 5 − Returns success.

If the linked list is used to implement the stack, then in step 3, we need to allocate space
dynamically.
Algorithm for PUSH Operation
A simple algorithm for Push operation can be derived as follows –

begin procedure push: stack, data

if stack is full
return null
endif

top ← top + 1
stack[top] ← data

end procedure

Implementation of this algorithm in C, is very easy. See the following code −


Example

void push(int data) {


if(!isFull()) {
top = top + 1;
stack[top] = data;
} else {
printf("Could not insert data, Stack is full.\n");
}
}

Pop Operation
Accessing the content while removing it from the stack, is known as a Pop Operation. In an
array implementation of pop() operation, the data element is not actually removed,
instead top is decremented to a lower position in the stack to point to the next value. But in
linked-list implementation, pop() actually removes data element and deallocates memory
space.
A Pop operation may involve the following steps −
 Step 1 − Checks if the stack is empty.
 Step 2 − If the stack is empty, produces an error and exit.
 Step 3 − If the stack is not empty, accesses the data element at which top is pointing.
 Step 4 − Decreases the value of top by 1.
 Step 5 − Returns success.

Algorithm for Pop Operation


A simple algorithm for Pop operation can be derived as follows −
begin procedure pop: stack

if stack is empty
return null
endif

data ← stack[top]
top ← top - 1
return data

end procedure

Implementation of this algorithm in C, is as follows −


Example
int pop(int data) {

if(!isempty()) {
data = stack[top];
top = top - 1;
return data;
} else {
printf("Could not retrieve data, Stack is empty.\n");
}
}

Queue
Queue is an abstract data structure, somewhat similar to Stacks. Unlike stacks, a queue is
open at both its ends. One end is always used to insert data (enqueue) and the other is used to
remove data (dequeue). Queue follows First-In-First-Out methodology, i.e., the data item
stored first will be accessed first.

A real-world example of queue can be a single-lane one-way road, where the vehicle enters
first, exits first. More real-world examples can be seen as queues at the ticket windows and
bus-stops.

Queue Representation
As we now understand that in queue, we access both ends for different reasons. The
following diagram given below tries to explain queue representation as data structure −
As in stacks, a queue can also be implemented using Arrays, Linked-lists, Pointers and
Structures. For the sake of simplicity, we shall implement queues using one-dimensional
array.

Basic Operations
Queue operations may involve initializing or defining the queue, utilizing it, and then
completely erasing it from the memory. Here we shall try to understand the basic operations
associated with queues –

 enqueue() − add (store) an item to the queue.


 dequeue() − remove (access) an item from the queue.

Few more functions are required to make the above-mentioned queue operation efficient.
These are −
 peek() − Gets the element at the front of the queue without removing it.
 isfull() − Checks if the queue is full.
 isempty() − Checks if the queue is empty.

In queue, we always dequeue (or access) data, pointed by front pointer and while enqueing
(or storing) data in the queue we take help of rear pointer.

Let's first learn about supportive functions of a queue −


peek()
This function helps to see the data at the front of the queue. The algorithm of peek() function
is as follows −

Algorithm
begin procedure peek
return queue[front]
end procedure

Implementation of peek() function in C programming language −

Example
int peek() {
return queue[front];
}

isfull()

As we are using single dimension array to implement queue, we just check for the rear
pointer to reach at MAXSIZE to determine that the queue is full. In case we maintain the
queue in a circular linked-list, the algorithm will differ.
Algorithm of isfull() function −
Algorithm
begin procedure isfull

if rear equals to MAXSIZE


return true
else
return false
endif

end procedure

Implementation of isfull() function in C programming language −


Example
bool isfull() {
if(rear == MAXSIZE - 1)
return true;
else
return false;
}

isempty()
Algorithm of isempty() function –

Algorithm
begin procedure isempty

if front is less than MIN OR front is greater than rear


return true
else
return false
endif

end procedure

If the value of front is less than MIN or 0, it tells that the queue is not yet initialized, hence
empty.

Here's the C programming code −


Example
bool isempty() {
if(front < 0 || front > rear)
return true;
else
return false;
}

Enqueue Operation

Queues maintain two data pointers, front and rear. Therefore, its operations are
comparatively difficult to implement than that of stacks.
The following steps should be taken to enqueue (insert) data into a queue −
 Step 1 − Check if the queue is full.
 Step 2 − If the queue is full, produce overflow error and exit.
 Step 3 − If the queue is not full, increment rear pointer to point the next empty space.
 Step 4 − Add data element to the queue location, where the rear is pointing.
 Step 5 − return success.

Sometimes, we also check to see if a queue is initialized or not, to handle any unforeseen
situations.
Algorithm for enqueue operation
procedure enqueue(data)

if queue is full
return overflow
endif

rear ← rear + 1
queue[rear] ← data
return true

end procedure

Implementation of enqueue() in C programming language −


Example
int enqueue(int data)
if(isfull())
return 0;

rear = rear + 1;
queue[rear] = data;

return 1;
end procedure

Dequeue Operation
Accessing data from the queue is a process of two tasks − access the data where front is
pointing and remove the data after access. The following steps are taken to
perform dequeue operation −
 Step 1 − Check if the queue is empty.
 Step 2 − If the queue is empty, produce underflow error and exit.
 Step 3 − If the queue is not empty, access the data where front is pointing.
 Step 4 − Increment front pointer to point to the next available data element.
 Step 5 − Return success.

Algorithm for dequeue operation


procedure dequeue
if queue is empty
return underflow
end if

data = queue[front]
front ← front + 1
return true

end procedure

Implementation of dequeue() in C programming language −


Example
int dequeue() {
if(isempty())
return 0;

int data = queue[front];


front = front + 1;

return data;
}

Array implementation of Stack


In array implementation, the stack is formed by using the array. All the operations regarding
the stack are performed using arrays. Lets see how each operation can be implemented on the
stack using array data structure.

Adding an element onto the stack (push operation)


Adding an element into the top of the stack is referred to as push operation. Push operation
involves following two steps.
1. Increment the variable Top so that it can now refere to the next memory location.
2. Add element at the position of incremented top. This is referred to as adding new element at
the top of the stack.

Stack is overflown when we try to insert an element into a completely filled stack therefore,
our main function must always avoid stack overflow condition.
Algorithm:
1. begin
2. if top = n then stack full
3. top = top + 1
4. stack (top) : = item;
5. end

Time Complexity: o (1)

Implementation of push algorithm in C language


1. void push (int val,int n) //n is size of the stack
2. {
3. if (top == n )
4. printf("\n Overflow");
5. else
6. {
7. top = top +1;
8. stack[top] = val;
9. }
10. }

Deletion of an element from a stack (Pop operation)

Deletion of an element from the top of the stack is called pop operation. The value of the
variable top will be incremented by 1 whenever an item is deleted from the stack. The top
most element of the stack is stored in a variable and then the top is decremented by 1. The
operation returns the deleted value that was stored in another variable as the result.
The underflow condition occurs when we try to delete an element from an already empty
stack.
Algorithm :
1. begin
2. if top = 0 then stack empty;
3. item := stack(top);
4. top = top - 1;
5. end;
Time Complexity : o(1)
Implementation of POP algorithm using C language
1. int pop ()
2. {
3. if(top == -1)
4. {
5. printf("Underflow");
6. return 0;
7. }
8. else
9. {
10. return stack[top - - ];
11. }
12. }

Visiting each element of the stack (Peek operation)


Peek operation involves returning the element which is present at the top of the stack without
deleting it. Underflow condition can occur if we try to return the top element in an already
empty stack.

Algorithm:
PEEK (STACK, TOP)
1. Begin
2. if top = -1 then stack empty
3. item = stack[top]
4. return item
5. End
Time complexity: o (n)

Implementation of Peek algorithm in C language


1. int peek()
2. {
3. if (top == -1)
4. {
5. printf("Underflow");
6. return 0;
7. }
8. else
9. {
10. return stack [top];
11. }
12. }

C program
1. #include <stdio.h>
2. int stack[100],i,j,choice=0,n,top=-1;
3. void push();
4. void pop();
5. void show();
6. void main ()
7. {
8. printf("Enter the number of elements in the stack ");
9. scanf("%d",&n);
10. printf("*********Stack operations using array*********");
11. printf("\n----------------------------------------------\n");
12. while(choice != 4)
13. {
14. printf("Chose one from the below options...\n");
15. printf("\n1.Push\n2.Pop\n3.Show\n4.Exit");
16. printf("\n Enter your choice \n");
17. scanf("%d",&choice);
18. switch(choice)
19. {
20. case 1:
21. {
22. push();
23. break;
24. }
25. case 2:
26. {
27. pop();
28. break;
29. }
30. case 3:
31. {
32. show();
33. break;
34. }
35. case 4:
36. {
37. printf("Exiting....");
38. break;
39. }
40. default:
41. {
42. printf("Please Enter valid choice ");
43. }
44. };
45. }
46. }
47.
48. void push ()
49. {
50. int val;
51. if (top == n )
52. printf("\n Overflow");
53. else
54. {
55. printf("Enter the value?");
56. scanf("%d",&val);
57. top = top +1;
58. stack[top] = val;
59. }
60. }
61. void pop ()
62. {
63. if(top == -1)
64. printf("Underflow");
65. else
66. top = top -1;
67. }
68. void show()
69. {
70. for (i=top;i>=0;i--)
71. {
72. printf("%d\n",stack[i]);
73. }
74. if(top == -1)
75. {
76. printf("Stack is empty");
77. }
78. }

Linked list implementation of stack

Instead of using array, we can also use linked list to implement stack. Linked list allocates the
memory dynamically. However, time complexity in both the scenario is same for all the
operations i.e. push, pop and peek.
In linked list implementation of stack, the nodes are maintained non-contiguously in the
memory. Each node contains a pointer to its immediate successor node in the stack. Stack is
said to be overflown if the space left in the memory heap is not enough to create a node.

The top most node in the stack always contains null in its address field. Let’s discuss the way
in which, each operation is performed in linked list implementation of stack.

Adding a node to the stack (Push operation)

Adding a node to the stack is referred to as push operation. Pushing an element to a stack in
linked list implementation is different from that of an array implementation. In order to push
an element onto the stack, the following steps are involved.

1. Create a node first and allocate memory to it.


2. If the list is empty then the item is to be pushed as the start node of the list. This includes
assigning value to the data part of the node and assign null to the address part of the node.
3. If there are some nodes in the list already, then we have to add the new element in the
beginning of the list (to not violate the property of the stack). For this purpose, assign the
address of the starting element to the address field of the new node and make the new node,
the starting node of the list.
Time Complexity: o (1)

C implementation:
1. void push ()
2. {
3. int val;
4. struct node *ptr =(struct node*)malloc(sizeof(struct node));
5. if(ptr == NULL)
6. {
7. printf("not able to push the element");
8. }
9. else
10. {
11. printf("Enter the value");
12. scanf("%d",&val);
13. if(head==NULL)
14. {
15. ptr->val = val;
16. ptr -> next = NULL;
17. head=ptr;
18. }
19. else
20. {
21. ptr->val = val;
22. ptr->next = head;
23. head=ptr;
24. }
25. printf("Item pushed");
26. }
27. }

Deleting a node from the stack (POP operation)


Deleting a node from the top of stack is referred to as pop operation. Deleting a node from
the linked list implementation of stack is different from that in the array implementation. In
order to pop an element from the stack, we need to follow the following steps:

1. Check for the underflow condition: The underflow condition occurs when
we try to pop from an already empty stack. The stack will be empty if the head pointer of the
list points to null.
2. Adjust the head pointer accordingly: In stack, the elements are popped only from one end,
therefore, the value stored in the head pointer must be deleted and the node must be freed.
The next node of the head node now becomes the head node.

Time Complexity: o (n)

C implementation
1. void pop()
2. {
3. int item;
4. struct node *ptr;
5. if (head == NULL)
6. {
7. printf("Underflow");
8. }
9. else
10. {
11. item = head->val;
12. ptr = head;
13. head = head->next;
14. free(ptr);
15. printf("Item popped");
16. }
17. }

Display the nodes (Traversing)


Displaying all the nodes of a stack needs traversing all the nodes of the linked list organized
in the form of stack. For this purpose, we need to follow the following steps.
1. Copy the head pointer into a temporary pointer.
2. Move the temporary pointer through all the nodes of the list and print the value field attached
to every node.

Time Complexity: o (n)

C Implementation
1. void display()
2. {
3. int i;
4. struct node *ptr;
5. ptr=head;
6. if(ptr == NULL)
7. {
8. printf("Stack is empty\n");
9. }
10. else
11. {
12. printf("Printing Stack elements \n");
13. while(ptr!=NULL)
14. {
15. printf("%d\n",ptr->val);
16. ptr = ptr->next;
17. }
18. }
19. }

Queue
1. A queue can be defined as an ordered list which enables insert operations to be performed
at one end called REAR and delete operations to be performed at another end
called FRONT.
2. Queue is referred to be as First In First Out list.
3. For example, people waiting in line for a rail ticket form a queue.

Applications of Queue
1. Queues are widely used as waiting lists for a single shared resource like printer, disk, CPU.
2. Queues are used in asynchronous transfer of data (where data is not being transferred at the
same rate between two processes) for eg. Pipes, file IO, sockets.
3. Queues are used as buffers in most of the applications like MP3 media player, CD player, etc.
4. Queue are used to maintain the play list in media players in order to add and remove the
songs from the play-list.
5. Queues are used in operating systems for handling interrupts.
Complexity
Data Time Complexity Space
Struct Comple
ure ity

Average Worst Worst

Acce Sear Inserti Deleti Acce Sear Inserti Deleti


ss ch on on ss ch on on

Queue θ(n) θ(n) θ(1) θ(1) O(n) O(n) O(1) O(1) O(n)

Types of Queues
What is the Queue?
A queue in the data structure can be considered similar to the queue in the real-world. A
queue is a data structure in which whatever comes first will go out first. It follows the FIFO
(First-In-First-Out) policy.

In Queue, the insertion is done from one end known as the rear end or the tail of the queue,
whereas the deletion is done from another end known as the front end or the head of the
queue. In other words, it can be defined as a list or a collection with a constraint that the
insertion can be performed at one end called as the rear end or tail of the queue and deletion
is performed on another end called as the front end or the head of the queue.
Operations on Queue
There are two fundamental operations performed on a Queue:

o Enqueue: The enqueue operation is used to insert the element at the rear end of the queue. It
returns void.
o Dequeue: The dequeue operation performs the deletion from the front-end of the queue. It
also returns the element which has been removed from the front-end. It returns an integer
value. The dequeue operation can also be designed to void.
o Peek: This is the third operation that returns the element, which is pointed by the front
pointer in the queue but does not delete it.
o Queue overflow (isfull): When the Queue is completely full, then it shows the overflow
condition.
o Queue underflow (isempty): When the Queue is empty, i.e., no elements are in the Queue
then it throws the underflow condition.

Implementation of Queue
There are two ways of implementing the Queue:
o Sequential allocation: The sequential allocation in a Queue can be implemented using an
array.
For more details, click on the below link: https://www.javatpoint.com/array-representation-
of-queue
o Linked list allocation: The linked list allocation in a Queue can be implemented using a
linked list.
Types of Queue
There are four types of Queues:

o Linear Queue
In Linear Queue, an insertion takes place from one end while the deletion occurs from
another end. The end at which the insertion takes place is known as the rear end, and the end
at which the deletion takes place is known as front end. It strictly follows the FIFO rule. The
linear Queue can be represented, as shown in the below figure:

The above figure shows that the elements are inserted from the rear end, and if we insert
more elements in a Queue, then the rear value gets incremented on every insertion. If we
want to show the deletion, then it can be represented as:

In the above figure, we can observe that the front pointer points to the next element, and the
element which was previously pointed by the front pointer was deleted.

The major drawback of using a linear Queue is that insertion is done only from the rear end.
If the first three elements are deleted from the Queue, we cannot insert more elements even
though the space is available in a Linear Queue. In this case, the linear Queue shows
the overflow condition as the rear is pointing to the last element of the Queue.

o Circular Queue
In Circular Queue, all the nodes are represented as circular. It is similar to the linear Queue
except that the last element of the queue is connected to the first element. It is also known
as Ring Buffer as all the ends are connected to another end. The circular queue can be
represented as:

The drawback that occurs in a linear queue is overcome by using the circular queue. If the
empty space is available in a circular queue, the new element can be added in an empty space
by simply incrementing the value of rear.

o Priority Queue
A priority queue is another special type of Queue data structure in which each element has
some priority associated with it. Based on the priority of the element, the elements are
arranged in a priority queue. If the elements occur with the same priority, then they are served
according to the FIFO principle.

In priority Queue, the insertion takes place based on the arrival while the deletion occurs
based on the priority. The priority Queue can be shown as:
The above figure shows that the highest priority element comes first and the elements of the
same priority are arranged based on FIFO structure.

o Deque
Both the Linear Queue and Deque are different as the linear queue follows the FIFO principle
whereas, deque does not follow the FIFO principle. In Deque, the insertion and deletion can
occur from both ends.

Array representation of Queue


We can easily represent queue by using linear arrays. There are two variables i.e. front and
rear that are implemented in the case of every queue. Front and rear variables point to the
position from where insertions and deletions are performed in a queue. Initially, the value of
front and queue is -1 which represents an empty queue. Array representation of a queue
containing 5 elements along with the respective values of front and rear, is shown in the
following figure.

The above figure shows the queue of characters forming the English word "HELLO". Since,
No deletion is performed in the queue till now, therefore the value of front remains -1 .
However, the value of rear increases by one every time an insertion is performed in the
queue. After inserting an element into the queue shown in the above figure, the queue will
look something like following. The value of rear will become 5 while the value of front
remains same.
After deleting an element, the value of front will increase from -1 to 0. however, the queue
will look something like following.

Algorithm to insert any element in a queue


Check if the queue is already full by comparing rear to max - 1. if so, then return an overflow
error.

If the item is to be inserted as the first element in the list, in that case set the value of front
and rear to 0 and insert the element at the rear end.
Otherwise keep increasing the value of rear and insert each element one by one having rear as
the index.

Algorithm
o Step 1: IF REAR = MAX - 1
Write OVERFLOW
Go to step
[END OF IF]
o Step 2: IF FRONT = -1 and REAR = -1
SET FRONT = REAR = 0
ELSE
SET REAR = REAR + 1
[END OF IF]
o Step 3: Set QUEUE[REAR] = NUM
o Step 4: EXIT

C Function
1. void insert (int queue[], int max, int front, int rear, int item)
2. {
3. if (rear + 1 == max)
4. {
5. printf("overflow");
6. }
7. else
8. {
9. if(front == -1 && rear == -1)
10. {
11. front = 0;
12. rear = 0;
13. }
14. else
15. {
16. rear = rear + 1;
17. }
18. queue[rear]=item;
19. }
20. }

Algorithm to delete an element from the queue


If, the value of front is -1 or value of front is greater than rear, write an underflow message
and exit.

Otherwise, keep increasing the value of front and return the item stored at the front end of the
queue at each time.

Algorithm
o Step 1: IF FRONT = -1 or FRONT > REAR
Write UNDERFLOW
ELSE
SET VAL = QUEUE[FRONT]
SET FRONT = FRONT + 1
[END OF IF]
o Step 2: EXIT

C Function
1. int delete (int queue[], int max, int front, int rear)
2. {
3. int y;
4. if (front == -1 || front > rear)
5. {
6. printf("underflow");
7. }
8. else
9. {
10. y = queue[front];
11. if(front == rear)
12. {
13. front = rear = -1;
14. else
15. front = front + 1;
16. }
17. return y;
18. }
19. }

Drawback of array implementation


Although, the technique of creating a queue is easy, but there are some drawbacks of using
this technique to implement a queue.

o Memory wastage: The space of the array, which is used to store queue elements, can never
be reused to store the elements of that queue because the elements can only be inserted at
front end and the value of front might be so high so that, all the space before that, can never
be filled.

The above figure shows how the memory space is wasted in the array representation of
queue. In the above figure, a queue of size 10 having 3 elements, is shown. The value of the
front variable is 5, therefore, we cannot reinsert the values in the place of already deleted
element before the position of front. That much space of the array is wasted and cannot be
used in the future (for this queue).

o Deciding the array size


One of the most common problem with array implementation is the size of the array which
requires to be declared in advance. Due to the fact that, the queue can be extended at runtime
depending upon the problem, the extension in the array size is a time taking process and
almost impossible to be performed at runtime since a lot of reallocations take place. Due to
this reason, we can declare the array large enough so that we can store queue elements as
enough as possible but the main problem with this declaration is that, most of the array slots
(nearly half) can never be reused. It will again lead to memory wastage.

Linked List implementation of Queue


Due to the drawbacks discussed in the previous section of this tutorial, the array
implementation cannot be used for the large scale applications where the queues are
implemented. One of the alternative of array implementation is linked list implementation of
queue.

The storage requirement of linked representation of a queue with n elements is o(n) while the
time requirement for operations is o(1).

In a linked queue, each node of the queue consists of two parts i.e. data part and the link part.

Each element of the queue points to its immediate next element in the memory.

In the linked queue, there are two pointers maintained in the memory i.e. front pointer and
rear pointer. The front pointer contains the address of the starting element of the queue while
the rear pointer contains the address of the last element of the queue.

Insertion and deletions are performed at rear and front end respectively. If front and rear both
are NULL, it indicates that the queue is empty.
The linked representation of queue is shown in the following figure.

Operation on Linked Queue


There are two basic operations which can be implemented on the linked queues. The
operations are Insertion and Deletion.
Insert operation
The insert operation append the queue by adding an element to the end of the queue. The new
element will be the last element of the queue.

Firstly, allocate the memory for the new node ptr by using the following statement.

Ptr = (struct node *) malloc (sizeof(struct node));

There can be the two scenario of inserting this new node ptr into the linked queue.
In the first scenario, we insert element into an empty queue. In this case, the condition front
= NULL becomes true. Now, the new element will be added as the only element of the queue
and the next pointer of front and rear pointer both, will point to NULL.

1. ptr -> data = item;


2. if(front == NULL)
3. {
4. front = ptr;
5. rear = ptr;
6. front -> next = NULL;
7. rear -> next = NULL;
8. }

In the second case, the queue contains more than one element. The condition front = NULL
becomes false. In this scenario, we need to update the end pointer rear so that the next pointer
of rear will point to the new node ptr. Since, this is a linked queue, hence we also need to
make the rear pointer point to the newly added node ptr. We also need to make the next
pointer of rear point to NULL.

1. rear -> next = ptr;


2. rear = ptr;
3. rear->next = NULL;
In this way, the element is inserted into the queue. The algorithm and the C implementation is
given as follows.

Algorithm
o Step 1: Allocate the space for the new node PTR
o Step 2: SET PTR -> DATA = VAL
o Step 3: IF FRONT = NULL
SET FRONT = REAR = PTR
SET FRONT -> NEXT = REAR -> NEXT = NULL
ELSE
SET REAR -> NEXT = PTR
SET REAR = PTR
SET REAR -> NEXT = NULL
[END OF IF]
o Step 4: END

C Function
1. void insert(struct node *ptr, int item; )
2. {
3. ptr = (struct node *) malloc (sizeof(struct node));
4. if(ptr == NULL)
5. {
6. printf("\nOVERFLOW\n");
7. return;
8. }
9. else
10. {
11. ptr -> data = item;
12. if(front == NULL)
13. {
14. front = ptr;
15. rear = ptr;
16. front -> next = NULL;
17. rear -> next = NULL;
18. }
19. else
20. {
21. rear -> next = ptr;
22. rear = ptr;
23. rear->next = NULL;
24. }
25. }
26. }

Deletion
Deletion operation removes the element that is first inserted among all the queue elements.
Firstly, we need to check either the list is empty or not. The condition front == NULL
becomes true if the list is empty, in this case , we simply write underflow on the console and
make exit.

Otherwise, we will delete the element that is pointed by the pointer front. For this purpose,
copy the node pointed by the front pointer into the pointer ptr. Now, shift the front pointer,
point to its next node and free the node pointed by the node ptr. This is done by using the
following statements.

1. ptr = front;
2. front = front -> next;
3. free(ptr);

The algorithm and C function is given as follows.


Algorithm

o Step 1: IF FRONT = NULL


Write " Underflow "
Go to Step 5
[END OF IF]
o Step 2: SET PTR = FRONT
o Step 3: SET FRONT = FRONT -> NEXT
o Step 4: FREE PTR
o Step 5: END

C Function
1. void delete (struct node *ptr)
2. {
3. if(front == NULL)
4. {
5. printf("\nUNDERFLOW\n");
6. return;
7. }
8. else
9. {
10. ptr = front;
11. front = front -> next;
12. free(ptr);
13. }
14. }

Circular Queue
Why was the concept of the circular queue introduced?
There was one limitation in the array implementation of Queue. If the rear reaches to the end
position of the Queue then there might be possibility that some vacant spaces are left in the
beginning which cannot be utilized. So, to overcome such limitations, the concept of the
circular queue was introduced.
As we can see in the above image, the rear is at the last position of the Queue and front is
pointing somewhere rather than the 0th position. In the above array, there are only two
elements and other three positions are empty. The rear is at the last position of the Queue; if
we try to insert the element then it will show that there are no empty spaces in the Queue.

There is one solution to avoid such wastage of memory space by shifting both the elements at
the left and adjust the front and rear end accordingly. It is not a practically good approach
because shifting all the elements will consume lots of time. The efficient approach to avoid
the wastage of the memory is to use the circular queue data structure.

What is a Circular Queue?


A circular queue is similar to a linear queue as it is also based on the FIFO (First In First Out)
principle except that the last position is connected to the first position in a circular queue that
forms a circle. It is also known as a Ring Buffer.

Operations on Circular Queue

The following are the operations that can be performed on a circular queue:
o Front: It is used to get the front element from the Queue.
o Rear: It is used to get the rear element from the Queue.
o enQueue(value): This function is used to insert the new value in the Queue. The new
element is always inserted from the rear end.
o deQueue(): This function deletes an element from the Queue. The deletion in a Queue
always takes place from the front end.

Applications of Circular Queue


The circular Queue can be used in the following scenarios:

o Memory management: The circular queue provides memory management. As we have


already seen that in linear queue, the memory is not managed very efficiently. But in case of
a circular queue, the memory is managed efficiently by placing the elements in a location
which is unused.
o CPU Scheduling: The operating system also uses the circular queue to insert the processes
and then execute them.
o Traffic system: In a computer-control traffic system, traffic light is one of the best examples
of the circular queue. Each light of traffic light gets ON one by one after every jinterval of
time. Like red light gets ON for one minute then yellow light for one minute and then green
light. After green light, the red light gets ON.

Enqueue operation
The steps of enqueue operation are given below:

o First, we will check whether the Queue is full or not.


o Initially the front and rear are set to -1. When we insert the first element in a Queue, front and
rear both are set to 0.
o When we insert a new element, the rear gets incremented, i.e., rear=rear+1.
Scenarios for inserting an element

There are two scenarios in which queue is not full:


o If rear != max - 1, then rear will be incremented to mod(maxsize) and the new value will be
inserted at the rear end of the queue.
o If front != 0 and rear = max - 1, it means that queue is not full, then set the value of rear to
0 and insert the new element there.
There are two cases in which the element cannot be inserted:
o When front ==0 && rear = max-1, which means that front is at the first position of the
Queue and rear is at the last position of the Queue.
o front== rear + 1;

Algorithm to insert an element in a circular queue


Step 1: IF (REAR+1)%MAX = FRONT
Write " OVERFLOW "
Goto step 4
[End OF IF]
Step 2: IF FRONT = -1 and REAR = -1
SET FRONT = REAR = 0
ELSE IF REAR = MAX - 1 and FRONT ! = 0
SET REAR = 0
ELSE
SET REAR = (REAR + 1) % MAX
[END OF IF]
Step 3: SET QUEUE[REAR] = VAL
Step 4: EXIT

Dequeue Operation
The steps of dequeue operation are given below:
o First, we check whether the Queue is empty or not. If the queue is empty, we cannot perform
the dequeue operation.
o When the element is deleted, the value of front gets decremented by 1.
o If there is only one element left which is to be deleted, then the front and rear are reset to -1.

Algorithm to delete an element from the circular queue


Step 1: IF FRONT = -1
Write " UNDERFLOW "
Goto Step 4
[END of IF]
Step 2: SET VAL = QUEUE[FRONT]
Step 3: IF FRONT = REAR
SET FRONT = REAR = -1
ELSE
IF FRONT = MAX -1
SET FRONT = 0
ELSE
SET FRONT = FRONT + 1
[END of IF]
[END OF IF]
Step 4: EXIT

Let's understand the enqueue and dequeue operation through the diagrammatic
representation.
Implementation of circular queue using Array
1. #include <stdio.h>
2. # define max 6
3. int queue[max]; // array declaration
4. int front=-1;
5. int rear=-1;
6. // function to insert an element in a circular queue
7. void enqueue(int element)
8. {
9. if(front==-1 && rear==-1) // condition to check queue is empty
10. {
11. front=0;
12. rear=0;
13. queue[rear]=element;
14. }
15. else if((rear+1)%max==front) // condition to check queue is full
16. {
17. printf("Queue is overflow..");
18. }
19. else
20. {
21. rear=(rear+1)%max; // rear is incremented
22. queue[rear]=element; // assigning a value to the queue at the rear position.
23. }
24. }
25. // function to delete the element from the queue
26. int dequeue()
27. {
28. if((front==-1) && (rear==-1)) // condition to check queue is empty
29. {
30. printf("\nQueue is underflow..");
31. }
32. else if(front==rear)
33. {
34. printf("\nThe dequeued element is %d", queue[front]);
35. front=-1;
36. rear=-1;
37. }
38. else
39. {
40. printf("\nThe dequeued element is %d", queue[front]);
41. front=(front+1)%max;
42. }
43. }
44. // function to display the elements of a queue
45. void display()
46. {
47. int i=front;
48. if(front==-1 && rear==-1)
49. {
50. printf("\n Queue is empty..");
51. }
52. else
53. {
54. printf("\nElements in a Queue are :");
55. while(i<=rear)
56. {
57. printf("%d,", queue[i]);
58. i=(i+1)%max;
59. }
60. }
61. }
62. int main()
63. {
64. int choice=1,x; // variables declaration
65. while(choice<4 && choice!=0) // while loop
66. {
67. printf("\n Press 1: Insert an element");
68. printf("\nPress 2: Delete an element");
69. printf("\nPress 3: Display the element");
70. printf("\nEnter your choice");
71. scanf("%d", &choice);
72. switch(choice)
73. {
74. case 1:
75. printf("Enter the element which is to be inserted");
76. scanf("%d", &x);
77. enqueue(x);
78. break;
79. case 2:
80. dequeue();
81. break;
82. case 3:
83. display();
84. }}
85. return 0;
86. }

Implementation of circular queue using linked list

As we know that linked list is a linear data structure that stores two parts, i.e., data part and
the address part where address part contains the address of the next node. Here, linked list is
used to implement the circular queue; therefore, the linked list follows the properties of the
Queue. When we are implementing the circular queue using linked list then both the enqueue
and dequeue operations take O(1) time.
1. #include <stdio.h>
2. // Declaration of struct type node
3. struct node
4. {
5. int data;
6. struct node *next;
7. };
8. struct node *front=-1;
9. struct node *rear=-1;
10. // function to insert the element in the Queue
11. void enqueue(int x)
12. {
13. struct node *newnode; // declaration of pointer of struct node type.
14. newnode=(struct node *)malloc(sizeof(struct node)); // allocating the memory to the new
node
15. newnode->data=x;
16. newnode->next=0;
17. if(rear==-1) // checking whether the Queue is empty or not.
18. {
19. front=rear=newnode;
20. rear->next=front;
21. }
22. else
23. {
24. rear->next=newnode;
25. rear=newnode;
26. rear->next=front;
27. }
28. }
29. // function to delete the element from the queue
30. void dequeue()
31. {
32. struct node *temp; // declaration of pointer of node type
33. temp=front;
34. if((front==-1)&&(rear==-1)) // checking whether the queue is empty or not
35. {
36. printf("\nQueue is empty");
37. }
38. else if(front==rear) // checking whether the single element is left in the queue
39. {
40. front=rear=-1;
41. free(temp);
42. }
43. else
44. {
45. front=front->next;
46. rear->next=front;
47. free(temp);
48. }
49. }
50. // function to get the front of the queue
51. int peek()
52. {
53. if((front==-1) &&(rear==-1))
54. {
55. printf("\nQueue is empty");
56. }
57. else
58. {
59. printf("\nThe front element is %d", front->data);
60. }
61. }
62. // function to display all the elements of the queue
63. void display()
64. {
65. struct node *temp;
66. temp=front;
67. printf("\n The elements in a Queue are : ");
68. if((front==-1) && (rear==-1))
69. {
70. printf("Queue is empty");
71. }
72. else
73. {
74. while(temp->next!=front)
75. {
76. printf("%d,", temp->data);
77. temp=temp->next;
78. }
79. printf("%d", temp->data);
80. }
81. }
82. void main()
83. {
84. enqueue(34);
85. enqueue(10);
86. enqueue(23);
87. display();
88. dequeue();
89. peek();
90. }

Dequeue

The dequeue stands for Double Ended Queue. In the queue, the insertion takes place from
one end while the deletion takes place from another end. The end at which the insertion
occurs is known as the rear end whereas the end at which the deletion occurs is known
as front end.

Deque is a linear data structure in which the insertion and deletion operations are performed
from both ends. We can say that deque is a generalized version of the queue.

Let's look at some properties of deque.


o Deque can be used both as stack and queue as it allows the insertion and deletion operations
on both ends.

In deque, the insertion and deletion operation can be performed from one side. The stack
follows the LIFO rule in which both the insertion and deletion can be performed only from
one end; therefore, we conclude that deque can be considered as a stack.
In deque, the insertion can be performed on one end, and the deletion can be done on another
end. The queue follows the FIFO rule in which the element is inserted on one end and deleted
from another end. Therefore, we conclude that the deque can also be considered as the queue.

There are two types of Queues, Input-restricted queue, and output-restricted queue.

1. Input-restricted queue: The input-restricted queue means that some restrictions are applied
to the insertion. In input-restricted queue, the insertion is applied to one end while the
deletion is applied from both the ends.

2. Output-restricted queue: The output-restricted queue means that some restrictions are
applied to the deletion operation. In an output-restricted queue, the deletion can be applied
only from one end, whereas the insertion is possible from both ends.

Operations on Dequeue

The following are the operations applied on deque:


o Insert at front
o Delete from end
o insert at rear
o delete from rear
Other than insertion and deletion, we can also perform peek operation in deque.
Through peek operation, we can get the front and the rear element of the dequeue.

We can perform two more operations on dequeue:


o isFull(): This function returns a true value if the stack is full; otherwise, it returns a false
value.
o isEmpty(): This function returns a true value if the stack is empty; otherwise it returns a false
value.

Memory Representation
The deque can be implemented using two data structures, i.e., circular array, and doubly
linked list. To implement the deque using circular array, we first should know what circular
array is.

What is a circular array?


An array is said to be circular if the last element of the array is connected to the first element
of the array. Suppose the size of the array is 4, and the array is full but the first location of the
array is empty. If we want to insert the array element, it will not show any overflow condition
as the last element is connected to the first element. The value which we want to insert will
be added in the first location of the array.

Applications of Dequeue

o The deque can be used as a stack and queue; therefore, it can perform both redo and undo
operations.
o It can be used as a palindrome checker means that if we read the string from both ends, then
the string would be the same.
o It can be used for multiprocessor scheduling. Suppose we have two processors, and each
processor has one process to execute. Each processor is assigned with a process or a job, and
each process contains multiple threads. Each processor maintains a deque that contains
threads that are ready to execute. The processor executes a process, and if a process creates a
child process then that process will be inserted at the front of the deque of the parent process.
o Suppose the processor P2 has completed the execution of all its threads then it steals the
thread from the rear end of the processor P1 and adds to the front end of the processor P2. The
processor P2 will take the thread from the front end; therefore, the deletion takes from both
the ends, i.e., front and rear end. This is known as the A-steal algorithm for scheduling.

Implementation of Deque using a circular array


The following are the steps to perform the operations on the Deque:
Enqueue operation
1. Initially, we are considering that the deque is empty, so both front and rear are set to -1, i.e., f
= -1 and r = -1.
2. As the deque is empty, so inserting an element either from the front or rear end would be the
same thing. Suppose we have inserted element 1, then front is equal to 0, and the rear is
also equal to 0.

3. Suppose we want to insert the next element from the rear. To insert the element from the rear
end, we first need to increment the rear, i.e., rear=rear+1. Now, the rear is pointing to the
second element, and the front is pointing to the first element.
4. Suppose we are again inserting the element from the rear end. To insert the element, we will
first increment the rear, and now rear points to the third element.

5. If we want to insert the element from the front end, and insert an element from the front, we
have to decrement the value of front by 1. If we decrement the front by 1, then the front
points to -1 location, which is not any valid location in an array. So, we set the front as (n -
1), which is equal to 4 as n is 5. Once the front is set, we will insert the value as shown in the
below figure:

Dequeue Operation
1. If the front is pointing to the last element of the array, and we want to perform the delete
operation from the front. To delete any element from the front, we need to set front=front+1.
Currently, the value of the front is equal to 4, and if we increment the value of front, it
becomes 5 which is not a valid index. Therefore, we conclude that if front points to the last
element, then front is set to 0 in case of delete operation.

2. If we want to delete the element from rear end then we need to decrement the rear value by 1,
i.e., rear=rear-1 as shown in the below figure:
3. If the rear is pointing to the first element, and we want to delete the element from the rear end
then we have to set rear=n-1 where n is the size of the array as shown in the below figure:

Let's create a program of deque.


The following are the six functions that we have used in the below program:
o enqueue_front(): It is used to insert the element from the front end.
o enqueue_rear(): It is used to insert the element from the rear end.
o dequeue_front(): It is used to delete the element from the front end.
o dequeue_rear(): It is used to delete the element from the rear end.
o getfront(): It is used to return the front element of the deque.
o getrear(): It is used to return the rear element of the deque.

1. #define size 5
2. #include <stdio.h>
3. int deque[size];
4. int f=-1, r=-1;
5. // enqueue_front function will insert the value from the front
6. void enqueue_front(int x)
7. {
8. if((f==0 && r==size-1) || (f==r+1))
9. {
10. printf("deque is full");
11. }
12. else if((f==-1) && (r==-1))
13. {
14. f=r=0;
15. deque[f]=x;
16. }
17. else if(f==0)
18. {
19. f=size-1;
20. deque[f]=x;
21. }
22. else
23. {
24. f=f-1;
25. deque[f]=x;
26. }
27. }
28. // enqueue_rear function will insert the value from the rear
29. void enqueue_rear(int x)
30. {
31. if((f==0 && r==size-1) || (f==r+1))
32. {
33. printf("deque is full");
34. }
35. else if((f==-1) && (r==-1))
36. {
37. r=0;
38. deque[r]=x;
39. }
40. else if(r==size-1)
41. {
42. r=0;
43. deque[r]=x;
44. }
45. else
46. {
47. r++;
48. deque[r]=x;
49. }
50. }
51. // display function prints all the value of deque.
52. void display()
53. {
54. int i=f;
55. printf("\n Elements in a deque : ");
56. while(i!=r)
57. {
58. printf("%d ",deque[i]);
59. i=(i+1)%size;
60. }
61. printf("%d",deque[r]);
62. }
63. // getfront function retrieves the first value of the deque.
64. void getfront()
65. {
66. if((f==-1) && (r==-1))
67. {
68. printf("Deque is empty");
69. }
70. else
71. {
72. printf("\nThe value of the front is: %d", deque[f]);
73. }
74. }
75. // getrear function retrieves the last value of the deque.
76. void getrear()
77. {
78. if((f==-1) && (r==-1))
79. {
80. printf("Deque is empty");
81. }
82. else
83. {
84. printf("\nThe value of the rear is: %d", deque[r]);
85. }
86. }
87. // dequeue_front() function deletes the element from the front
88. void dequeue_front()
89. {
90. if((f==-1) && (r==-1))
91. {
92. printf("Deque is empty");
93. }
94. else if(f==r)
95. {
96. printf("\nThe deleted element is %d", deque[f]);
97. f=-1;
98. r=-1;
99. }
100. else if(f==(size-1))
101. {
102. printf("\nThe deleted element is %d", deque[f]);
103. f=0;
104. }
105. else
106. {
107. printf("\nThe deleted element is %d", deque[f]);
108. f=f+1;
109. }
110. }
111. // dequeue_rear() function deletes the element from the rear
112. void dequeue_rear()
113. {
114. if((f==-1) && (r==-1))
115. {
116. printf("Deque is empty");
117. }
118. else if(f==r)
119. {
120. printf("\nThe deleted element is %d", deque[r]);
121. f=-1;
122. r=-1;
123. }
124. else if(r==0)
125. {
126. printf("\nThe deleted element is %d", deque[r]);
127. r=size-1;
128. }
129. else
130. {
131. printf("\nThe deleted element is %d", deque[r]);
132. r=r-1;
133. }
134. }
135. int main()
136. {
137. // inserting a value from the front.
138. enqueue_front(2);
139. // inserting a value from the front.
140. enqueue_front(1);
141. // inserting a value from the rear.
142. enqueue_rear(3);
143. // inserting a value from the rear.
144. enqueue_rear(5);
145. // inserting a value from the rear.
146. enqueue_rear(8);
147. // Calling the display function to retrieve the values of deque
148. display();
149. // Retrieve the front value
150. getfront();
151. // Retrieve the rear value.
152. getrear();
153. // deleting a value from the front
154. dequeue_front();
155. //deleting a value from the rear
156. dequeue_rear();
157. // Calling the display function to retrieve the values of deque
158. display();
159. return 0;
160. }

What is a priority queue?


A priority queue is an abstract data type that behaves similarly to the normal queue except
that each element has some priority, i.e., the element with the highest priority would come
first in a priority queue. The priority of the elements in a priority queue will determine the
order in which elements are removed from the priority queue.

The priority queue supports only comparable elements, which means that the elements are
either arranged in an ascending or descending order.
For example, suppose we have some values like 1, 3, 4, 8, 14, 22 inserted in a priority queue
with an ordering imposed on the values is from least to the greatest. Therefore, the 1 number
would be having the highest priority while 22 will be having the lowest priority.

Characteristics of a Priority queue


A priority queue is an extension of a queue that contains the following characteristics:
o Every element in a priority queue has some priority associated with it.
o An element with the higher priority will be deleted before the deletion of the lesser priority.
o If two elements in a priority queue have the same priority, they will be arranged using the
FIFO principle.

Let's understand the priority queue through an example.


We have a priority queue that contains the following values:
1, 3, 4, 8, 14, 22

All the values are arranged in ascending order. Now, we will observe how the priority queue
will look after performing the following operations:

o poll(): This function will remove the highest priority element from the priority queue. In the
above priority queue, the '1' element has the highest priority, so it will be removed from the
priority queue.
o add(2): This function will insert '2' element in a priority queue. As 2 is the smallest element
among all the numbers so it will obtain the highest priority.
o poll(): It will remove '2' element from the priority queue as it has the highest priority queue.
o add(5): It will insert 5 element after 4 as 5 is larger than 4 and lesser than 8, so it will obtain
the third highest priority in a priority queue.
Types of Priority Queue

There are two types of priority queue:


o Ascending order priority queue: In ascending order priority queue, a lower priority number
is given as a higher priority in a priority. For example, we take the numbers from 1 to 5
arranged in an ascending order like 1, 2, 3, 4, 5; therefore, the smallest number, i.e., 1 is
given as the highest priority in a priority queue.

o Descending order priority queue: In descending order priority queue, a higher priority
number is given as a higher priority in a priority. For example, we take the numbers from 1 to
5 arranged in descending order like 5, 4, 3, 2, 1; therefore, the largest number, i.e., 5 is given
as the highest priority in a priority queue.

Representation of priority queue


Now, we will see how to represent the priority queue through a one-way list.

We will create the priority queue by using the list given below in which INFO list contains
the data elements, PRN list contains the priority numbers of each data element available in
the INFO list, and LINK basically contains the address of the next node.
Let's create the priority queue step by step.
In the case of priority queue, lower priority number is considered the higher priority,

i.e., lower priority number = higher priority.

Step 1: In the list, lower priority number is 1, whose data value is 333, so it will be inserted
in the list as shown in the below diagram:
Step 2: After inserting 333, priority number 2 is having a higher priority, and data values
associated with this priority are 222 and 111. So, this data will be inserted based on the FIFO
principle; therefore 222 will be added first and then 111.
Step 3: After inserting the elements of priority 2, the next higher priority number is 4 and
data elements associated with 4 priority numbers are 444, 555, 777. In this case, elements
would be inserted based on the FIFO principle; therefore, 444 will be added first, then 555,
and then 777.
Step 4: After inserting the elements of priority 4, the next higher priority number is 5, and the
value associated with priority 5 is 666, so it will be inserted at the end of the queue.
Implementation of Priority Queue
The priority queue can be implemented in four ways that include arrays, linked list, heap data
structure and binary search tree. The heap data structure is the most efficient way of
implementing the priority queue, so we will implement the priority queue using a heap data
structure in this topic. Now, first we understand the reason why heap is the most efficient way
among all the other data structures.

Analysis of complexities using different implementations

Implementation add Remove peek

Linked list O(1) O(n) O(n)

Binary heap O(logn) O(logn) O(1)

Binary search tree O(logn) O(logn) O(1)

What is Heap?
A heap is a tree-based data structure that forms a complete binary tree, and satisfies the heap
property. If A is a parent node of B, then A is ordered with respect to the node B for all nodes
A and B in a heap. It means that the value of the parent node could be more than or equal to
the value of the child node, or the value of the parent node could be less than or equal to the
value of the child node. Therefore, we can say that there are two types of heaps:
o Max heap: The max heap is a heap in which the value of the parent node is greater than the
value of the child nodes.

o Min heap: The min heap is a heap in which the value of the parent node is less than the value
of the child nodes.

Both the heaps are the binary heap, as each has exactly two child nodes.
Priority Queue Operations
The common operations that we can perform on a priority queue are insertion, deletion and
peek. Let's see how we can maintain the heap data structure.

o Inserting the element in a priority queue (max heap)


If we insert an element in a priority queue, it will move to the empty slot by looking from top
to bottom and left to right.

If the element is not in a correct place then it is compared with the parent node; if it is found
out of order, elements are swapped. This process continues until the element is placed in a
correct position.
o Removing the minimum element from the priority queue
As we know that in a max heap, the maximum element is the root node. When we remove the
root node, it creates an empty slot. The last inserted element will be added in this empty slot.
Then, this element is compared with the child nodes, i.e., left-child and right child, and swap
with the smaller of the two. It keeps moving down the tree until the heap property is restored.

Applications of Priority queue


The following are the applications of the priority queue:
o It is used in the Dijkstra's shortest path algorithm.
o It is used in prim's algorithm
o It is used in data compression techniques like Huffman code.
o It is used in heap sort.
o It is also used in operating system like priority scheduling, load balancing and interrupt
handling.

Evaluation of Expression
There are 3 different ways to write an algebraic expressions:
 Infix form
 Prefix form
 Postfix form

Infix form
Is exactly the fully parenthesized notation we have just introduced. Let me remind you once
again the Recursive definition
infix-expression := (infix-expression operand infix-expression)
infix-expression := atom

Examples

(3 * 7)
((1 + 3) * 2)
((1 + 3) * ( 2 - 3))

Main Feature:
the binary operator is between the two operands.
Question: what if we do not put all the parentheses? Then there are ambiguities on how to
interpret an expression: is 1+2*3 the same as (1+2)*3 or the same as 1+(2*3)? The
precedence of operators solves this problem.

Prefix form
Main Feature:
the operator preceeds the two operands.
Recursive definition of fully parenthesized version:
prefix-expression := (operand prefix-expression prefix-expression)
prefix-expression := atom

Recursive definition of classic version, without parentheses (we do not need them, because
there is no longer any ambiguity on how to match the operands to the operators):
prefix-expression := operand prefix-expression prefix-expression
prefix-expression := atom

Examples

(* 3 7) or simply * 3 7
(* ( + 1 3) 2) or simply * + 1 3 2
( * ( + 1 3) ( - 2 3)) or simply * + 1 3 - 2 3

Postfix form
Main Feature:
the operator is after the two operands. Recursive definition
postfix-expression := (operand postfix-expression postfix-expression)
postfix-expression := atom

Recursive definition of classic version, without parentheses (we do not need them, because
there is no longer any ambiguity on how to match the operands to the operators):
postfix-expression := operand postfix-expression postfix-expression
postfix-expression := atom

Examples

(3 7 *) or simply 3 7 *
((1 3 + ) 2 *) or simply 1 3 + 2 *
((1 3 +) ( 2 3 -) * ) or simply 1 3 + 2 3 - *

Associativity
Associativity describes the rule where operators with the same precedence appear in an
expression. For example, in the expression a + b − c, both + and – have the same precedence,
then which part of the expression will be evaluated first, is determined by associativity of
those operators. Here, both + and − are left associative, so the expression will be evaluated
as (a + b) − c.

Precedence and associativity determine the order of evaluation of an expression. Following is


an operator precedence and associativity table (highest to lowest) −

Sr.No. Operator Precedence Associativity

1 Exponentiation ^ Highest Right Associative

2 Multiplication ( ∗ ) & Division ( / ) Second Highest Left Associative

3 Addition ( + ) & Subtraction ( − ) Lowest Left Associative

The above table shows the default behavior of operators. At any point of time in expression
evaluation, the order can be altered by using parenthesis.

For example −
In a + b*c, the expression part b*c will be evaluated first, with multiplication as precedence
over addition. We here use parenthesis for a + b to be evaluated first, like (a + b)*c.

Examples of Infix, Prefix, Postfix


• Infix A + B, 3*x – y
• Prefix +AB, -*3xy
• Postfix AB+, 3x*y-

Converting to Infix to Postfix


A+B*C
=> A + [B*C]
=> A+[BC*]
=> A [BC*] +
=> ABC * +

Converting to Infix to Prefix


A+B*C
=> A + [B*C]
=> A+[*BC]
=> + A [*BC]
=> + A * BC

Why?
Why use PREFIX and POSTFIX notations when we have simple INFIX notation? INFIX
notations are not as simple as they seem especially while evaluating them. To evaluate an
infix expression we need to consider Operators’ Priority and Associative property

• E.g. expression 3+5*4 evaluate to 32 i.e. (3+5)*4 or to 23 i.e. 3+(5*4).

To solve this problem Precedence or Priority of the operators was defined. Operator
precedence governs the evaluation order. An operator with higher precedence is applied
before an operator with lower precedence.
The following table briefly tries to show the difference in all three notations −

Sr.No. Infix Notation Prefix Notation Postfix Notation

1 a+b +ab ab+

2 (a + b) ∗ c ∗+abc ab+c∗

3 a ∗ (b + c) ∗a+bc abc+∗

4 a/b+c/d +/ab/cd ab/cd/+

5 (a + b) ∗ (c + d) ∗+ab+cd ab+cd+∗
6 ((a + b) ∗ c) - d -∗+abcd ab+c∗d-

Postfix Expression Evaluation


A postfix expression is a collection of operators and operands in which the operator is placed
after the operands. That means, in a postfix expression the operator follows the operands.

Postfix Expression has following general structure...


Operand1 Operand2 Operator
Example

Postfix Expression Evaluation using Stack Data Structure


A postfix expression can be evaluated using the Stack data structure. To evaluate a postfix
expression using Stack data structure we can use the following steps...

1. Read all the symbols one by one from left to right in the given Postfix Expression
2. If the reading symbol is operand, then push it on to the Stack.
3. If the reading symbol is operator (+ , - , * , / etc.,), then perform TWO pop operations
and store the two popped oparands in two different variables (operand1 and operand2).
Then perform reading symbol operation using operand1 and operand2 and push result
back on to the Stack.
4. Finally! perform a pop operation and display the popped value as final result.
Example

Consider the following Expression...


Infix to Postfix
Conversion
Any expression
can be
represented using three
types of expressions (Infix,
Postfix, and
Prefix). We can also
convert one type of
expression to
another type of
expression like Infix to
Postfix, Infix to
Prefix, Postfix to Prefix
and vice versa.

To convert any
Infix expression into Postfix
or Prefix expression we can
use the
following procedure...
1. Find all the operators in the
given Infix
Expression.
2. Find the order of
operators
evaluated according to
their Operator
precedence.
3. Convert each operator into required type of expression (Postfix or Prefix) in the same order.

Example
Consider the following Infix Expression to be converted into Postfix Expression...
D=A+B*C
 Step 1 - The Operators in the given Infix Expression : = , + , *
 Step 2 - The Order of Operators according to their preference : * , + , =
 Step 3 - Now, convert the first operator * ----- D = A + B C *
 Step 4 - Convert the next operator + ----- D = A BC* +
 Step 5 - Convert the next operator = ----- D ABC*+ =

Finally, given Infix Expression is converted into Postfix Expression as follows...


DABC*+=

Infix to Postfix Conversion using Stack Data Structure


To convert Infix Expression into Postfix Expression using a stack data structure, We can use
the following steps...

1. Read all the symbols one by one from left to right in the given Infix Expression.
2. If the reading symbol is operand, then directly print it to the result (Output).
3. If the reading symbol is left parenthesis '(', then Push it on to the Stack.
4. If the reading symbol is right parenthesis ')', then Pop all the contents of stack until
respective left parenthesis is poped and print each poped symbol to the result.
5. If the reading symbol is operator (+ , - , * , / etc.,), then Push it on to the Stack.
However, first pop the operators which are already on the stack that have higher or
equal precedence than current operator and print them to the result.

Example
Consider the following Infix Expression...
(A+B)*(C-D)
The given infix expression can be converted into postfix expression using Stack data
Structure as follows...
The
final
Postfix
Expression is as
follows...
AB+
CD-*

Procedure for
Postfix

Conversion
1. Scan the Infix string from left to right.
2. Initialize an empty stack.
3. If the scanned character is an operand, add it to the Postfix string.
4. If the scanned character is an operator and if the stack is empty push the character to stack.
If the scanned character is an Operator and the stack is not empty, compare the precedence
5.
of the character with the element on top of the stack.
If top Stack has higher precedence over the scanned character pop the stack else push the
6. scanned character to stack. Repeat this step until the stack is not empty and top Stack has
precedence over the character.
7. Repeat 4 and 5 steps till all the characters are scanned.
After all characters are scanned, we have to add any character that the stack may have to
8.
the Postfix string.
9. If stack is not empty add top Stack to Postfix string and Pop the stack.
10
Repeat this step as long as stack is not empty.
.

Algorithm for Postfix Conversion


S:stack
while(more tokens)
x<=next token
if(x == operand)
print x
else
while(precedence(x)<=precedence(top(s)))
print(pop(s))
push(s,x)
while(! empty (s))
print(pop(s))

Conversion To Postfix
EXAMPLE:
A+(B*C-(D/E-F)*G)*H
Stack Input Output

Empty A+(B*C-(D/E-F)*G)*H -

Empty +(B*C-(D/E-F)*G)*H A

+ (B*C-(D/E-F)*G)*H A

+( B*C-(D/E-F)*G)*H A

+( *C-(D/E-F)*G)*H AB

+(* C-(D/E-F)*G)*H AB

+(* -(D/E-F)*G)*H ABC

+(- (D/E-F)*G)*H ABC*

+(-( D/E-F)*G)*H ABC*

+(-( /E-F)*G)*H ABC*D

+(-(/ E-F)*G)*H ABC*D

+(-(/ -F)*G)*H ABC*DE

+(-(- F)*G)*H ABC*DE/

+(-(- F)*G)*H ABC*DE/

+(-(- )*G)*H ABC*DE/F

+(- *G)*H ABC*DE/F-

+(-* G)*H ABC*DE/F-

+(-* )*H ABC*DE/F-G

+ *H ABC*DE/F-G*-

+* H ABC*DE/F-G*-
+* End ABC*DE/F-G*-H

Empty End ABC*DE/F-G*-H*+

Infix to prefix conversion


Algorithm of Infix to Prefix
Step 1. Push “)” onto STACK, and add “(“ to end of the A
Step 2. Scan A from right to left and repeat step 3 to 6 for each element of A until the
STACK is empty
Step 3. If an operand is encountered add it to B
Step 4. If a right parenthesis is encountered push it onto STACK
Step 5. If an operator is encountered then:
a. Repeatedly pop from STACK and add to B each operator (on the top of STACK) which
has same or higher precedence than the operator.
b. Add operator to STACK
Step 6. If left parenthesis is encontered then
a. Repeatedly pop from the STACK and add to B (each operator on top of stack until a left
parenthesis is encounterd)
b. Remove the left parenthesis
Step 7. Exit

Infix to prefix conversion

Expression = (A+B^C)*D+E^5

Step 1. Reverse the infix expression.

5^E+D*)C^B+A(

Step 2. Make Every '(' as ')' and every ')' as '('

5^E+D*(C^B+A)

Step 3. Convert expression to postfix form.


A+(B*C-(D/E-F)*G)*H

Expression Stack Output Comment

5^E+D*(C^B+A) Empty - Initial

^E+D*(C^B+A) Empty 5 Print

E+D*(C^B+A) ^ 5 Push

+D*(C^B+A) ^ 5E Push

D*(C^B+A) + 5E^ Pop And Push

*(C^B+A) + 5E^D Print

(C^B+A) +* 5E^D Push

C^B+A) +*( 5E^D Push

^B+A) +*( 5E^DC Print

B+A) +*(^ 5E^DC Push

+A) +*(^ 5E^DCB Print

A) +*(+ 5E^DCB^ Pop And Push

) +*(+ 5E^DCB^A Print

End +* 5E^DCB^A+ Pop Until '('

End Empty 5E^DCB^A+*+ Pop Every element

Step 4. Reverse the expression.

+*+A^BCD^E5
Result
+*+A^BCD^E5

You might also like