Download as pdf or txt
Download as pdf or txt
You are on page 1of 158

DATA AND FILE STRUCTURE

MODULE 2
ARRAY LINKEDLIST STRINGS
Array in Data Structure
An array is a collection of items stored at contiguous memory locations. The idea is to store
multiple items of the same type together. This makes it easier to calculate the position of each
element by simply adding an offset to a base value, i.e., the memory location of the first element
of the array (generally denoted by the name of the array).

Properties of array
There are some of the properties of an array that are listed as follows -

 Each element in an array is of the same data type and carries the same size that is 4 bytes.
 Elements in the array are stored at contiguous memory locations from which the first element is
stored at the smallest memory location.
 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 the data element.

Representation of an array
We can represent an array in various ways in different programming languages. As an
illustration, lets see the declaration of array in C language -
As per the above illustration, there are some of the following important points –
 Index starts with 0.
 The array’s length is 10, which means we can store 10 elements.
 Each element in the array can be accessed via its index.

Why are arrays required?
Arrays are useful because –
 Sorting and searching a value in an array is easier.
 Arrays are best to process multiple values quickly and easily.
 Arrays are good for storing multiple values in a single variable - In computer programming, most
cases require storing a large number of data of a similar type. To store such an amount of data, we
need to define a large number of variables. It would be very difficult to remember the 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.
Types Array

 One-dimensional Array: A simple linear array where elements are stored in a contiguous memory
location and accessed using a single index. It's the most basic form of an array.

 Multi-dimensional Array: Arrays with more than one dimension. The most common type is a two-
dimensional array (also known as a matrix), but arrays with three or more dimensions are also possible.

Basic operations
Now, let;s discuss the basic operations supported in the array –

 Traversal - This operation is used to print the elements of the array.


 Insertion - It is used to add an element at a particular index.
 Deletion - It is used to delete an element from a particular index.
 Search - It is used to search an element using the given index or by the value.
 Update - It updates an element at a particular index.
Traversal operation
This operation is performed to traverse through the array elements. It prints all array elements
one after another. We can understand it with the below program -
Code:-
#include <stdio.h>
int main() {
int array[] = {1, 2, 3, 4, 5};
int arraySize = sizeof(array) / sizeof(array[0]);
for (int i = 0; i < arraySize; i++) {
printf("%d ", array[i]);
}
printf("\n");
return 0;
}

Insertion operation
This operation is performed to insert one or more elements into the array. As per the requirements, an
element can be added at the beginning, end, or at any index of the array.
Now, lets see the implementation of inserting an element into the array.
Code:-
#include <stdio.h>
#define MAX_SIZE 100
void insertElement(int array[], int *size, int element, int position) {
if (*size >= MAX_SIZE || position < 0 || position > *size) {

printf("Insertion failed: Invalid position or array is full.\n");


return;
}
for (int i = *size; i > position; i--)
array[i] = array[i - 1];

array[position] = element;
(*size)++;
}
int main() {
int array[MAX_SIZE] = {1, 2, 3, 4, 5};
int size = 5;
insertElement(array, &size, 10, 2);
printf("Array after insertion:\n");
for (int i = 0; i < size; i++)
printf("%d ", array[i]);
printf("\n");

return 0;
}

Deletion operation
As the name implies, this operation removes an element from the array and then reorganizes
all of the array elements.
Code:-
#include <stdio.h>

#define MAX_SIZE 100

void deleteElement(int array[], int *size, int position) {

if (position < 0 || position >= *size) {


printf("Deletion failed: Invalid position.\n");
return;
}

for (int i = position; i < *size - 1; i++)


array[i] = array[i + 1];

(*size)--;
}

int main() {
int array[MAX_SIZE] = {1, 2, 3, 4, 5};
int size = 5;

deleteElement(array, &size, 2);

printf("Array after deletion:\n");


for (int i = 0; i < size; i++)
printf("%d ", array[i]);
printf("\n");

return 0;
}
Search operation
This operation is performed to search an element in the array based on the value or index.
Code:-
#include <stdio.h>
#define MAX_SIZE 100

intlinearSearch(int array[], int size, int key) {


for (int i = 0; i < size; i++)
if (array[i] == key) return i;
return -1;
}

int main() {
int array[MAX_SIZE] = {1, 3, 5, 7, 9, 2, 4, 6, 8, 10};
int size = 10, key = 6;

int index = linearSearch(array, size, key);


printf("Element %d %s.\n", key, (index != -1) ? ("found at index "): ("not found in the array"));

return 0;
}
Update operation
This operation is performed to update an existing array element located at the given index.
Code:-
#include <stdio.h>
#define MAX_SIZE 100
void updateElement(int array[], int size, int position, int newValue) {
if (position >= 0 && position < size)
array[position] = newValue;
else
printf("Update failed: Invalid position.\n");
}
int main() {
int array[MAX_SIZE] = {1, 2, 3, 4, 5};
int size = 5;
updateElement(array, size, 2, 10);

printf("Array after update:\n");


for (int i = 0; i < size; i++)
printf("%d ", array[i]);
printf("\n");
return 0;
}
Advantages of Array
 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.
 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.
 Any element in the array can be directly accessed by using the index.
Disadvantages of Array
 Array is homogenous. It means that the elements with similar data type can be stored in it.
 In array, there is static memory allocation that is size of an array cannot be altered.
 There will be wastage of memory if we store less number of elements than the declared size.

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.
1. 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.
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:
for ( int i=0; i&lt;n ;i++) {
for (int j=0; j&lt;n; j++) {
a[i][j] = 0;
} }

Initializing 2D Arrays
We know that, when we declare and initialize one dimensional array in C programming simultaneously,
we dont 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.
1. 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 :
#include &lt;stdio.h&gt;
void main () {
int arr[3][3],i,j;
for (i=0;i&lt;3;i++) {
for (j=0;j&lt;3;j++) {
printf(“Enter a[%d][%d]: “,i,j);
scanf(&quot;%d&quot;,&amp;arr[i][j]);
}
}
printf(&quot;\n printing the elements ....\n&quot;);
for(i=0;i&lt;3;i++) {
printf(&quot;\n&quot;);
for (j=0;j&lt;3;j++) {
printf(“%d\t”,arr[i][j]);
}
}
}

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 1 st row of the array is stored into the memory completely, then the 2 nd row of thearray 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 thememory
contiguously. The memory allocation of the array which is shown in in the aboveimage is given as
follows.

first, the 1 st column of the array is stored into the memory completely, then the 2 nd row of the array is
stored into the memory completely and so on till the last column of the array.
Multidimensional Arrays in C
A multi-dimensional array can be termed as an array of arrays that stores homogeneousdata in tabular
form. Data in multidimensional arrays is generally stored in row-major order in the memory.
The general form of declaring N-dimensional arrays is shown below.
Syntax:
data_type array_name[size1][size2]....[sizeN];
 data_type: Type of data to be stored in the array.
 array_name: Name of the array.
 size1, size2,…, sizeN: Size of each dimension.

Three-Dimensional Array in C
A Three Dimensional Array or 3D array in C is a collection of two-dimensional arrays. It
can be visualized as multiple 2D arrays stacked on top of each other.

Graphical Representation of Three-Dimensional Array of Size 3 x 3 x 3


Declaration of Three-Dimensional Array in C
We can declare a 3D array with x 2D arrays each having y rows and z columns using the
syntax shown below.
Syntax:
data_type array_name[x][y][z];

 data_type: Type of data to be stored in each element.

 array_name: name of the array

 x: Number of 2D arrays.

 y: Number of rows in each 2D array.

 z: Number of columns in each 2D array.


Example:
int array[3][3][3];
Initialization of Three-Dimensional Array in C. Initialization in a 3D array is the same as that of 2D
arrays. The difference is as the number of dimensions increases so the number of nested braces will also
increase.
A 3D array in C can be initialized by using:
1. Initializer List
2. Loops
Initialization of 3D Array using Initializer List
Method 1:
int x[2][3][4] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
11, 12, 13, 14, 15, 16, 17, 18, 19,
20, 21, 22, 23};
Method 2(Better):
int x[2][3][4] =
{
{ {0,1,2,3}, {4,5,6,7}, {8,9,10,11} },
{ {12,13,14,15}, {16,17,18,19}, {20,21,22,23} }
};
Initialization of 3D Array using Loops
It is also similar to that of 2D array with one more nested loop for accessing one moredimension.
int x[2][3][4];
for (int i=0; i&lt;2; i++) {
for (int j=0; j&lt;3; j++) {
for (int k=0; k&lt;4; k++) {
x[i][j][k] = (some_value);
}
}
}
Accessing elements in Three-Dimensional Array in C
Accessing elements in 3D Arrays is also similar to that of 3D Arrays. The difference is we
have to use three loops instead of two loops for one additional dimension in 3D Arrays.
Syntax:
array_name[x][y][z]
where,
 x: Index of 2D array.
 y: Index of that 2D array row.
 z: Index of that 2D array column.
// C program to print elements of Three-Dimensional Array
#include &lt;stdio.h&gt;
int main(void){
// initializing the 3-dimensional array
int x[2][3][2] = { { { 0, 1 }, { 2, 3 }, { 4, 5 } },{ { 6, 7 }, { 8, 9 }, { 10, 11 } } };
// output each element&#39;s value
for (int i = 0; i &lt; 2; ++i) {
for (int j = 0; j &lt; 3; ++j) {
for (int k = 0; k &lt; 2; ++k) {
printf(&quot;Element at x[%i][%i][%i] = %d\n&quot;, i,
j, k, x[i][j][k]);
}
}
}
return (0); }
Strings
A string is nothing but the collection of the individual array elements or characters.
String is enclosed within Double quotes.
“programming" is a example of String.
Each Character Occupy 1 byte of Memory.
Size of “programming“ = 11 bytes
String is always Terminated with NULL Character (‘\0′).
char word[20] = “‘p’ , ‘r’ , ‘o’ , ‘g’ , ‘r’ , ‘a’ , ‘m’ , ‘m’ , ‘I’ , ‘n’ , ‘g’ , ‘\0’”

NULL Character is also known as string terminating character.


It is represented by “\0”.
NULL Character is having ASCII value 0
NULL terminates a string, but isn’t part of it
important for strlen() – length doesn’t include the NULL

Strings in C
No explicit type, instead strings are maintained as arrays of characters
Representing strings in C
stored in arrays of characters
array can be of any length
end of string is indicated by a delimiter, the zero character ‘\0’
String Literals
String literal values are represented by sequences of characters between double quotes (“)
Examples

 “” - empty string
 “hello”
 “a” versus ‘a’
 ‘a’ is a single character value (stored in 1 byte) as the ASCII value for a
 “a” is an array with two characters, the first is a, the second is the character value \0

Declaration of a string
Since we cannot declare string using String Data Type, instead ofwhich we use array of type “char” to create String.
Syntax :
char String_Variable_name [ SIZE ] ;
Examples :
 char city[30];
 char name[20];
 char message[50];

Rules for declaring a string

 String / Character Array Variable name should be legal C Identifier.


 String Variable must have Size specified.
 Statement will cause compile time error. -> char city[];
 Do not use String as data type because String data type is included in later languages such as C++ / Java. C
does not support String data type
 When you are using string for other purpose than accepting and printing data then you must include
following header file in your code– #include<string.h>

Initializing String (Character Array)


Process of Assigning some legal default data to String is Called
Initialization of String.
A string can be initialized in different ways. We will explain this with the help of an example.
Below is an example to declare a string with name as str and initialize it with “GeeksforGeeks”.

 char str[] = "GeeksforGeeks";


 char str[50] = "GeeksforGeeks";
 char str[] = {'G','e','e','k','s','f','o','r','G','e','e','k','s','\0'};
 char str[14] = {'G','e','e','k','s','f','o','r','G','e','e','k','s','\0'};
String Variables
 Allocate an array of a size large enough to hold the string (plus 1 extravalue for the delimiter)
 Examples (with initialization):
char str1[6] = “Hello”;
char str2[] = “Hello”;
char *str3 = “Hello”;
char str4[6] = {‘H’,’e’,’l’,’l’,’o’,’\0’};

 Note, each variable is considered a constant in that the space it isconnected to cannot be changed
str1 = str2; /* not allowable, but we can copy the contents of str2 to str1 (more later) */

Reading strings: %s format


void main(){
char name[25];
scanf("%s", name);
printf("Name = %s \n", name);
}

Sample Example:-
#include <stdio.h>
int main () {
char greeting[6] = {'H', 'e', 'l', 'l', 'o', '\0'};
printf("Greeting message: %s\n", greeting );
return 0;
}
When the above code is compiled and executed, it producesthe following result −
Greeting message: Hello
String functions we will see
 strlen : finds the length of a string
 strcat : concatenates one string at the end of another
 strcmp : compares two strings lexicographically
 strcpy : copies one string to another.

1. String Copy (strcpy)


strcpy( ) function copies contents of one string into another string.
Syntax : strcpy (destination_string , source_string );

Example:-strcpy ( str1, str2) – It copies contents of str2 into str1.


strcpy ( str2, str1) – It copies contents of str1 into str2.
If destination string length is less than source string, entire source stringvalue won’t be copied into destination
string.
For example, considerdestination string length is 20 and source string length is 30. Then, only 20 characters from
source string will be copied into destination string and remaining 10 characters won’t be copied and will be
truncated.

2. String Concat (strcat)


strncat( ) function in C language concatenates (appends) portion of one string at the end of another string.
Syntax : strncat ( destination_string , source_string, size);
Example:-strncat ( str2, str1, 3 ); – First 3 characters of str1 is concatenated at the end of str2.
As you know, each string in C is ended up with null character (‘\0’).
In strncat( ) operation, null character of destination string is overwritten by source string’s first character and null
character is added at the end of new destination string which is created after strncat( ) operation.

3. String Compare (strcmp)


strcmp( ) function in C compares two given strings and returns zero if theyare same.
If length of string1 < string2, it returns < 0 value that is -1.
If length of string1 > string2, it returns > 0 value that is 1
If length of string1 = string2 it returns 0.
Syntax : strcmp (str1 , str2 );strcmp( ) function is case sensitive. i.e, “A” and “a” are treated as different characters.
4. String Length (strlen)
strlen( ) function in C gives the length of the givenstring.
Syntax : strlen(str);
strlen( ) function counts the number of characters in a givenstring and returns the integer value.
It stops counting the character when null character is found. Because, null character indicates the end of the string in
C.

C gets() function
The gets() function enables the user to enter some characters followed by the enter key. All the characters entered by
the user get stored in a character array. The null character is added to the array to make it a string. The gets() allows
the user to enter the space-separated strings. It returns the string entered by the user.
C puts() function
The puts() function is very much similar to printf() function. The puts() function is used to print the string on the
console which is previously read by using gets() or scanf() function. The puts() function returns an integer value
representing the number of characters being printed on the console. Since, it prints an additional newline character
with the string, which moves the cursor to the new line on the console, the integer value returned by puts() will
always be equal to the number of characters present in the string plus 1.

Pointers with strings


We have used pointers with the array, functions, and primitive data types so far. However, pointers can be used to
point to the strings. There are various advantages of using pointers to
#include<stdio.h>;
void main () {
char s[11] = “javatpoint”;
char *p = s; // pointer p is pointing to string s.
printf(“%s”;,p); // the string javatpoint is printed if we print p.
}

Pattern matching
Pattern matching in strings involves finding the occurrences of a specific pattern (substring) within a
larger string. There are various algorithms and approaches for pattern matching, with the most common
being the brute-force method, Knuth-Morris-Pratt (KMP) algorithm, and Boyer-Moore algorithm. Here's a
simple example of pattern matching in C using the brute-force method:
#include <stdio.h>
#include <string.h>
// Function to perform pattern matching using the brute-force method
void bruteForcePatternMatching(char text[], char pattern[]) {
int textLength = strlen(text);
int patternLength = strlen(pattern);
for (int i = 0; i <= textLength - patternLength; i++) {
int j;
for (j = 0; j < patternLength; j++) {
if (text[i + j] != pattern[j])
break; // Mismatch found, break inner loop
}
if (j == patternLength) {
printf("Pattern found at index %d\n", i);
}
}
}

int main() {
char text[] = "This is a simple example text for pattern matching.";
char pattern[] = "example";
bruteForcePatternMatching(text, pattern);
return 0;
}
Linked List
A linked list is a linear data structure, in which the elements are not stored at contiguous memory
locations. The elements in a linked list are linked using pointers as shown in the below image: In simple
words, a linked list consists of nodes where each node contains a data field and a reference(link) to the
next node in the list.

 Linked List can be defined as collection of objects called nodes that are randomly stored in the
memory.
 A node contains two fields i.e. data stored at that particular address and the pointer which contains
the address of the next node in the memory.
 The last node of the list contains pointer to the null.

Uses of Linked List


 The list is not required to be contiguously present in the memory. The node can reside any where
in the memory and linked together to make a list. This achieves optimized utilization of space.
 list size is limited to the memory size and doesn&#39;t need to be declared in advance.
 Empty node can not be present in the linked list.
 We can store values of primitive types or objects in the singly linked list.
Till now, we were using array data structure to organize the group of elements that are to be stored
individually in the memory. However, Array has several advantages and disadvantages which must be
known in order to decide the data structure which will be used throughout the program.
Array contains following limitations:
1. The size of array must be known in advance before using it in the program.
2. Increasing size of the array is a time taking process. It is almost impossible to expand the size of the
array at run time.
3. All the elements in the array need to be contiguously stored in the memory. Inserting any element in the
array needs shifting of all its predecessors.
Linked list is the data structure which can overcome all the limitations of an array. Using linked list is
useful because,
1. It allocates the memory dynamically. All the nodes of linked list are non-contiguously stored in the
memory and linked together with the help of pointers.
2. Sizing is no longer a problem since we do not need to define its size at the time of declaration. List
grows as per the program’s demand and limited to the available memory space.
Types of Linked Lists
1.Singly Linked List:Each node in a single Linked List contains a single pointer that points to the node
after it inthe list.
2.Doubly Linked List:- Each node in a doubly linked list has two pointers, one pointing to the node before
it and theother pointing to the node after it.
3.Circular Linked List:- A circular structure is produced in a circular linked list when the last node returns
to theinitial node.
What Are The Operations Supported by Linked List?
The operations supported by the linked list data structure are as follows:
 Insertion-This operation is beneficial for adding a new element to the linked list, which can be
done at any position in the list, including the tail or the head.
 Deletion-This operation is beneficial for removing an element from a linked list data structure.
This can also be done at any position on the list.
 Display-With this operation, one can visit each element in the linked list in a specific order from
head to tail.
 Search-This operation allows one to search for a particular element in the linked list data structure.
This can be done by crossing the list and comparing every element to the target.
Applications of Linked List
The linked list data structure is used to work on various computer science and programming projects.
- Implementation of Data Structures: Hash tables, stacks in data structures, and queues are just a few
of the significant data structures that may be implemented using the linked list data structures.
- Memory Management: To efficiently allocate and reallocate memory, Linked Lists data structures
can be utilised in memory management systems.
- File Systems: File systems can be represented using linked lists data structures. A node represents
each file or directory; the links signify the parent-child relationships between the files and
directories.
- Graphs and Charts: Graphs can be represented by Linked Lists data structure, where each node is a
vertex, and the links are the edges that connect them.
- Making music playlists: Linked List data structures are frequently used to build music playlists. A
node represents each song, and the connections between the nodes indicate the order in which the
songs are played.
- Picture Processing Method: Picture processing methods can be implemented using linked
lists, where a node represents each pixel.
Look here to know more about the Real-time application of data structures.
Uses of Linked List in Data Structure
The main use of a linked list data structure revolves around the storage and organisation of data flexibly
and dynamically. It offers effective insertion and deletion operations that can be done constantly, either at
the tail or head of the list and also at the linear time in the midst of the list.
Additionally, linked list data structures are essential for implementing other data structures like queues,
hash tables, and stacks, among many more. Additionally, linked lists also prove beneficial for representing
the hierarchical structure of data, like graphs and trees. If you want to know about Sorting in Data
Structures, look here.
Advantages &amp; Disadvantages of Linked List in Data Structure:
The advantages offered by linked list data structure are as follows:

 Dynamic size: At runtime, linked lists can change their size.


 Effective insertion and deletion: Inserting or removing elements from a list’s centre may be done
quickly and efficiently with linked lists.
 Memory efficiency: linked lists don&#39;t require contiguous memory allocation.
 Flexibility: Linked lists offer a lot of versatility.
Along with the advantages, some disadvantages linked list data structure offers:
 Sequential access: Linked lists have poor cache locality and significant memory overhead.
 Absence of random access: As linked lists cannot use random access, accessing entries directly
from an index is impossible.
 Complexity: The implementation and upkeep of linked lists can be more difficult tha those of
arrays.
Properties of Linked List
The main properties of a linked list data structure are mentioned below:
 Dynamic size: Instead of fixed-size arrays, Linked lists can expand or contract as needed while a
program is running.
 Effective element insertion and deletion: By simply updating the pointers, linked lists can insert or
delete elements from anywhere in the list. There is no constant-time random access to elements
in linked lists.
 Sequential access: Linked lists can be used for various activities since they can besequentially
accessed in both the forward and backward directions.
 You can gain extensive information about this by opting for a Business Analytics and Data
Science course.

 Singly linked list or One way chain


Singly linked list can be defined as the collection of ordered set of elements. The number ofelements may
vary according to need of the program. A node in the singly linked list consistof two parts: data part and
link part. Data part of the node stores actual information that is tobe represented by the node while the link
part of the node stores the address of its immediatesuccessor.
One way chain or singly linked list can be traversed only in one direction. In other words, wecan say that
each node contains only next pointer, therefore we can not traverse the list in thereverse direction.
Consider an example where the marks obtained by the student in three subjects are stored in alinked list as
shown in the figure.

In the above figure, the arrow represents the links. The data part of every node contains themarks obtained
by the student in the different subject. The last node in the list is identified bythe null pointer which is
present in the address part of the last node. We can have as manyelements we require, in the data part of
the list.
Representation of Singly Linked Lists:
A linked list is represented by a pointer to the first node of the linked list. The first node iscalled the head
of the linked list. If the linked list is empty, then the value of the headpoints to NULL.
Each node in a list consists of at least two parts:

- A Data Item (we can store integers, strings, or any type of data).
- Pointer (Or Reference) to the next node (connects one node to another) or An address ofanother
node
In C, we can represent a node using structures. Below is an example of a linked list node with integer
data.
In Java or C#, LinkedList can be represented as a class and a Node as a separate class. The LinkedList
class contains a reference of Node class type.
// A linked list node
struct Node {
int data;
struct Node* next;
};
Operations on Singly Linked List
There are various operations which can be performed on singly linked list. A list of all such operations is
given below.
Node Creation
struct node
{
int data;
struct node *next;
};
struct node *head, *ptr;
ptr = (struct node *)malloc(sizeof(struct node *));

 Circular Singly Linked List


In a circular Singly linked list, the last node of the list contains a pointer to the first node of the list. We
can have circular singly linked list as well as circular doubly linked list.
We traverse a circular singly linked list until we reach the same node where we started. The circular singly
liked list has no beginning and no ending. There is no null value present in the next part of any of the
nodes.
The following image shows a circular singly linked list.

Circular linked list are mostly used in task maintenance in operating systems. There are many examples
where circular linked list are being used in computer science including browser surfing where a record of
pages visited in the past by the user, ismaintained in the form of circular linked lists and can be accessed
again on clicking the previous button.
Memory Representation of circular linked list:
In the following image, memory representation of a circular linked list containing marks of a student in 4
subjects. However, the image shows a glimpse of how the circular list is being stored in the memory. The
start or head of the list is pointing to the element with the index 1 and containing 13 marks in the data part
and 4 in the next part. Which means that it is linked with the node that is being stored at 4th index of the
list.
However, due to the fact that we are considering circular linked list in the memory therefore the last node
of the list contains the address of the first node of the list.

We can also have more than one number of linked list in the memory with the different start pointers
pointing to the different start nodes in the list. The last node is identified by its next part which contains
the address of the start node of the list. We must be able to identify the last node of any linked list so that
we can find out the number of iterations which need to be performed while traversing the list.
Traversing in Circular Singly linked list
Traversing in circular singly linked list can be done through a loop. Initialize thetemporary pointer
variable temp to head pointer and run the while loop until the next pointer of temp becomes head.
while(ptr -&gt; next != head) {
printf(&quot;%d\n&quot;, ptr -&gt; data);
ptr = ptr -&gt; next;
}
Insertion into circular singly linked list at the beginning

temp = head;
while(temp-&gt;next != head)
temp = temp-&gt;next;
ptr-&gt;next = head;
temp -&gt; next = ptr;
head = ptr;

Insertion into circular singly linked list at the end

temp = head;
while(temp -&gt; next != head) {
temp = temp -&gt; next;
}
temp -&gt; next = ptr;
ptr -&gt; next = head;
Deletion in circular singly linked list at beginning

ptr = head;
while(ptr -&gt; next != head)
ptr = ptr -&gt; next;
ptr-&gt;next = head-&gt;next;
free(head);
head = ptr-&gt;next;
printf(“\nNode Deleted\n”);
Deletion in Circular singly linked list at the end

ptr = head;
while(ptr -&gt;next != head) {
preptr=ptr;
ptr = ptr-&gt;next;
}
preptr-&gt;next = ptr -&gt; next;
free(ptr);
printf(&quot;\nNode Deleted\n&quot;);
 Doubly Linked List:-
Inserting a new node in a doubly linked list is very similar to inserting new node in linked list. There is a
little extra work required to maintain the link of the previous node. A node can be inserted in a Doubly
Linked List in four ways:
 At the front of the DLL.
 In between two nodes
 After a given node.
 Before a given node.
 At the end of the DLL.
Insertion at the Beginning in Doubly Linked List:

To insert a new node at the beginning of the doubly list, we can use the following steps:
 Allocate memory for a new node (say new_node) and assign the provided value to its data field.
 Set the previous pointer of the new_node to nullptr.
 If the list is empty:
o Set the next pointer of the new_node to nullptr.
o Update the head pointer to point to the new_node.
 If the list is not empty:
o Set the next pointer of the new_node to the current head.
o Update the previous pointer of the current head to point to the new_node.
o Update the head pointer to point to the new_node.

Insertion in between two nodes in Doubly Linked List:

1. Add a node after a given node in a Doubly Linked List:


We are given a pointer to a node as prev_node, and the new node is inserted after the given node. This
can be done using the following steps:
 Firstly create a new node (say new_node).
 Now insert the data in the new node.
 Point the next of new_node to the next of prev_node.
 Point the next of prev_node to new_node.
 Point the previous of new_node to prev_node.
 Point the previous of next of new_node to new_node.

2. Add a node before a given node in a Doubly Linked List:


Let the pointer to this given node be next_node. This can be done using the following steps.
 Allocate memory for the new node, let it be called new_node.
 Put the data in new_node.
 Set the previous pointer of this new_node as the previous node of the next_node.
 Set the previous pointer of the next_node as the new_node.
 Set the next pointer of this new_node as the next_node.
 Set the next pointer of the previous of new_node to new_node.

Insertion at the End in Doubly Linked List:

The new node is always added after the last node of the given Linked List. This can be done using the
following steps:
 Create a new node (say new_node).
 Put the value in the new node.
 Make the next pointer of new_node as null.
 If the list is empty, make new_node as the head.
 Otherwise, travel to the end of the linked list.
 Now make the next pointer of last node point to new_node.
 Change the previous pointer of new_node to the last node of the list.
MODULE 3
STACK AND QUEUE
Stack
Stack is a linear data structure that follows a particular order in which the operations are performed. The
order may be LIFO(Last In First Out) or FILO(First In Last Out). LIFO implies that the element that is
inserted last, comes out first and FILO implies that the element that is inserted first, comes out last.

A Stack is a linear data structure that follows the LIFO (Last-In-First-Out) principle. Stack has one end,
whereas the Queue has two ends (front and rear). It contains only one pointer top pointer pointing to the
topmost element of the stack. Whenever an element is added in the stack, it is added on the top of the stack,
and the element can be deleted only from the stack. In other words, a stack can be defined as a container
in which insertion and deletion can be done from the one end known as the top of the stack.

Some key points related to stack

o It is called as stack because it behaves like a real-world stack, piles of books, etc.
o A Stack is an abstract data type with a pre-defined capacity, which means that it can store the
elements of a limited size.
o It is a data structure that follows some order to insert and delete the elements, and that order can be
LIFO or FILO.

Working of Stack

Stack works on the LIFO pattern. As we can observe in the below figure there are five memory blocks in
the stack; therefore, the size of the stack is 5.

Suppose we want to store the elements in a stack and let's assume that stack is empty. We have taken the
stack of size 5 as shown below in which we are pushing the elements one by one until the stack becomes
full.
Since our stack is full as the size of the stack is 5. In the above cases, we can observe that it goes from the
top to the bottom when we were entering the new element in the stack. The stack gets filled up from the
bottom to the top.

When we perform the delete operation on the stack, there is only one way for entry and exit as the other end
is closed. It follows the LIFO pattern, which means that the value entered first will be removed last. In the
above case, the value 5 is entered first, so it will be removed only after the deletion of all the other elements.

Standard Stack Operations

The following are some common operations implemented on the stack:

o push(): When we insert an element in a stack then the operation is known as a push. If the stack is
full then the overflow condition occurs.
o pop(): When we delete an element from the stack, the operation is known as a pop. If the stack is
empty means that no element exists in the stack, this state is known as an underflow state.
o isEmpty(): It determines whether the stack is empty or not.
o isFull(): It determines whether the stack is full or not.'
o peek(): It returns the element at the given position.
o count(): It returns the total number of elements available in a stack.
o change(): It changes the element at the given position.
o display(): It prints all the elements available in the stack.

PUSH operation

The steps involved in the PUSH operation is given below:

 Before inserting an element in a stack, we check whether the stack is full.


 If we try to insert the element in a stack, and the stack is full, then the overflow condition occurs.
 When we initialize a stack, we set the value of top as -1 to check that the stack is empty.
 When the new element is pushed in a stack, first, the value of the top gets incremented,
i.e., top=top+1, and the element will be placed at the new position of the top.
 The elements will be inserted until we reach the max size of the stack.

POP operation

The steps involved in the POP operation is given below:

 Before deleting the element from the stack, we check whether the stack is empty.
 If we try to delete the element from the empty stack, then the underflow condition occurs.
 If the stack is not empty, we first access the element which is pointed by the top
 Once the pop operation is performed, the top is decremented by 1, i.e., top=top-1.
Basic Operations on Stack
In order to make manipulations in a stack, there are certain operations provided to us.
 push() to insert an element into the stack
 pop() to remove an element from the stack
 top() Returns the top element of the stack.
 isEmpty() returns true if stack is empty else false.
 size() returns the size of stack.

Push:
Adds an item to the stack. If the stack is full, then it is said to be an Overflow condition.
Algorithm for push:
begin
if stack is full
return
endif
else
increment top
stack[top] assign value
end else
end procedure
Pop:
Removes an item from the stack. The items are popped in the reversed order in which they are pushed. If
the stack is empty, then it is said to be an Underflow condition.
Algorithm for pop:
begin
if stack is empty
return
endif
else
store value of stack[top]
decrement top
return value
end else
end procedure

Top:
Returns the top element of the stack.
Algorithm for Top:
begin
return stack[top]
end procedure
isEmpty:
Returns true if the stack is empty, else false.
Algorithm for isEmpty:
begin
if top < 1
return true
else
return false
end procedure
Applications of Stack

The following are the applications of the stack:

o Balancing of symbols: Stack is used for balancing a symbol. For example, we have the following
program:
o String reversal: Stack is also used for reversing a string. For example, we want to reverse a
"javaTpoint" string, so we can achieve this with the help of a stack.
First, we push all the characters of the string in a stack until we reach the null character.
After pushing all the characters, we start taking out the character one by one until we reach the
bottom of the stack.
o UNDO/REDO: It can also be used for performing UNDO/REDO operations. For example, we have
an editor in which we write 'a', then 'b', and then 'c'; therefore, the text written in an editor is abc. So,
there are three states, a, ab, and abc, which are stored in a stack. There would be two stacks in which
one stack shows UNDO state, and the other shows REDO state.
If we want to perform UNDO operation, and want to achieve 'ab' state, then we implement pop
operation.
o Recursion: The recursion means that the function is calling itself again. To maintain the previous
states, the compiler creates a system stack in which all the previous records of the function are
maintained.
o DFS(Depth First Search): This search is implemented on a Graph, and Graph uses the stack data
structure.
o Backtracking: Suppose we have to create a path to solve a maze problem. If we are moving in a
particular path, and we realize that we come on the wrong way. In order to come at the beginning of
the path to create a new path, we have to use the stack data structure.
o Expression conversion: Stack can also be used for expression conversion. This is one of the most
important applications of stack. The list of the expression conversion is given below:
o Infix to prefix
o Infix to postfix
o Prefix to infix
o Prefix to postfix
o Postfix to infix
o Memory management: The stack manages the memory. The memory is assigned in the contiguous
memory blocks. The memory is known as stack memory as all the variables are assigned in a function
call stack memory. The memory size assigned to the program is known to the compiler. When the
function is created, all its variables are assigned in the stack memory. When the function completed
its execution, all the variables assigned in the stack are released.
Implementing Stack using Arrays
#include<stdio.h>
int stack[100],choice,n,top,x,i;
void push(void);
void pop(void);
void display(void);
int main(){
top=-1;
printf("\n Enter the size of STACK[MAX=100]:");
scanf("%d",&n);
printf("\n\t STACK OPERATIONS USING ARRAY");
printf("\n\t--------------------------------");
printf("\n\t 1.PUSH\n\t 2.POP\n\t 3.DISPLAY\n\t 4.EXIT");
do{
printf("\n Enter the Choice:");
scanf("%d",&choice);
switch(choice){
case 1:{
push();
break;
}
case 2:{
pop();
break;
}
case 3:{
display();
break;
}
case 4:{
printf("\n\t EXIT POINT ");
break;
}
default:{
printf ("\n\t Please Enter a Valid Choice(1/2/3/4)");
}

}
}
while(choice!=4);
return 0;
}
void push(){
if(top>=n-1){
printf("\n\tSTACK is over flow");
}
else{
printf(" Enter a value to be pushed:");
scanf("%d",&x);
top++;
stack[top]=x;
}
}
void pop(){
if(top<=-1){
printf("\n\t Stack is under flow");
}
else{
printf("\n\t The popped elements is %d",stack[top]);
top--;
}
}
void display(){
if(top>=0){
printf("\n The elements in STACK \n");
for(i=top; i>=0; i--)
printf("\n%d",stack[i]);
printf("\n Press Next Choice");
}
else{
printf("\n The STACK is empty");
}
}

Implement Stack using Linked List


// C program for array implementation of stack
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>

// A structure to represent a stack


struct Stack {
int top;
unsigned capacity;
int* array;
};

// function to create a stack of given capacity. It initializes size of


// stack as 0
struct Stack* createStack(unsigned capacity){
struct Stack* stack = (struct Stack*)malloc(sizeof(struct Stack));
stack->capacity = capacity;
stack->top = -1;
stack->array = (int*)malloc(stack->capacity * sizeof(int));
return stack;
}

// Stack is full when top is equal to the last index


int isFull(struct Stack* stack){
return stack->top == stack->capacity - 1;
}

// Stack is empty when top is equal to -1


int isEmpty(struct Stack* stack){
return stack->top == -1;
}

// Function to add an item to stack. It increases top by 1


void push(struct Stack* stack, int item){
if (isFull(stack))
return;
stack->array[++stack->top] = item;
printf("%d pushed to stack\n", item);
}

// Function to remove an item from stack. It decreases top by 1


int pop(struct Stack* stack){
if (isEmpty(stack))
return INT_MIN;
return stack->array[stack->top--];
}
// Function to return the top from stack without removing it
int peek(struct Stack* stack){
if (isEmpty(stack))
return INT_MIN;
return stack->array[stack->top];
}

// Driver program to test above functions


int main(){
struct Stack* stack = createStack(100);
push(stack, 10);
push(stack, 20);
push(stack, 30);
printf("%d popped from stack\n", pop(stack));
return 0;
}

infix to postfix expression, evaluation of postfix expression Program


Queue
A Queue is defined as a linear data structure that is open at both ends and the operations are performed in
First In First Out (FIFO) order.
We define a queue to be a list in which all additions to the list are made at one end, and all deletions from
the list are made at the other end. The element which is first pushed into the order, the operation is first
performed on that.

FIFO Principle of Queue:


 A Queue is like a line waiting to purchase tickets, where the first person in line is the first person
served. (i.e. First come first serve).
 Position of the entry in a queue ready to be served, that is, the first entry that will be removed from
the queue, is called the front of the queue(sometimes, head of the queue), similarly, the position of
the last entry in the queue, that is, the one most recently added, is called the rear (or the tail) of the
queue. See the below figure.

Characteristics of Queue:
 Queue can handle multiple data.
 We can access both ends.
 They are fast and flexible.

Queue Representation:
Like stacks, Queues can also be represented in an array: In this representation, the Queue is implemented
using the array. Variables used in this case are
 Queue: the name of the array storing queue elements.
 Front: the index where the first element is stored in the array representing the queue.
 Rear: the index where the last element is stored in an array representing the 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
Due to the fact that queue performs actions on first in first out basis which is quite fair for the ordering of
actions. There are various applications of queues discussed as below.
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.

Types of Queue

There are four different types of queue that are listed as follows -

 Simple Queue or Linear Queue


 Circular Queue
 Priority Queue
 Double Ended Queue (or Deque)

Let's discuss each of the type of queue.


Simple Queue or 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 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.

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 representation of circular queue is shown in the below image -

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. The main advantage of using the circular queue is better memory utilization.

Priority Queue

It is a special type of queue in which the elements are arranged based on the priority. It is a special type of
queue data structure in which every element has a priority associated with it. Suppose some elements occur
with the same priority, they will be arranged according to the FIFO principle. The representation of priority
queue is shown in the below image -

Insertion in priority queue takes place based on the arrival, while deletion in the priority queue occurs based
on the priority. Priority queue is mainly used to implement the CPU scheduling algorithms.
There are two types of priority queue that are discussed as follows -
 Ascending priority queue - In ascending priority queue, elements can be inserted in arbitrary order,
but only smallest can be deleted first. Suppose an array with elements 7, 5, and 3 in the same order,
so, insertion can be done with the same sequence, but the order of deleting the elements is 3, 5, 7.
 Descending priority queue - In descending priority queue, elements can be inserted in arbitrary
order, but only the largest element can be deleted first. Suppose an array with elements 7, 3, and 5
in the same order, so, insertion can be done with the same sequence, but the order of deleting the
elements is 7, 5, 3.

To learn more about the priority queue, you can click the link - https://www.javatpoint.com/ds-priority-
queue

Deque (or, Double Ended Queue)

In Deque or Double Ended Queue, insertion and deletion can be done from both ends of the queue either
from the front or rear. It means that we can insert and delete elements from both front and rear ends of the
queue. Deque can be used as a palindrome checker means that if we read the string from both ends, then the
string would be the same.
Deque can be used both as stack and queue as it allows the insertion and deletion operations on both ends.
Deque can be considered as stack because stack follows the LIFO (Last In First Out) principle in which
insertion and deletion both can be performed only from one end. And in deque, it is possible to perform both
insertion and deletion from one end, and Deque does not follow the FIFO principle.
The representation of the deque is shown in the below image -

To know more about the deque, you can click the link - https://www.javatpoint.com/ds-deque
There are two types of deque that are discussed as follows -
 Input restricted deque - As the name implies, in input restricted queue, insertion operation can be
performed at only one end, while deletion can be performed from both ends.
 Output restricted deque - As the name implies, in output restricted queue, deletion operation can be
performed at only one end, while insertion can be performed from both ends.

Now, let's see the operations performed on the queue.


Operations performed on queue
The fundamental operations that can be performed on queue are listed as follows -
 Enqueue: The Enqueue operation is used to insert the element at the rear end of the queue. It returns
void.
 Dequeue: It 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.
 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.
 Queue overflow (isfull): It shows the overflow condition when the queue is completely full.
 Queue underflow (isempty): It shows the underflow condition when the Queue is empty, i.e., no
elements are in the Queue.

Now, let's see the ways to implement the queue.


Ways to implement the queue
There are two ways of implementing the Queue:
 Implementation using array: The sequential allocation in a Queue can be implemented using an
array.
 Implementation using Linked list: The linked list allocation in a Queue can be implemented using
a linked list. For more details, click on the be

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
 Step 1: IF REAR = MAX - 1
Write OVERFLOW
Go to step
[END OF IF]
 Step 2: IF FRONT = -1 and REAR = -1
SET FRONT = REAR = 0
ELSE
SET REAR = REAR + 1
[END OF IF]
 Step 3: Set QUEUE[REAR] = NUM
 Step 4: EXIT

C Function
void insert (int queue[], int max, int front, int rear, int item) {
if (rear + 1 == max) {
printf("overflow");
}
else {
if(front == -1 && rear == -1) {
front = 0;
rear = 0;
}
else {
rear = rear + 1;
}
queue[rear]=item;
}
}
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
 Step 1: IF FRONT = -1 or FRONT > REAR
Write UNDERFLOW
ELSE
SET VAL = QUEUE[FRONT]
SET FRONT = FRONT + 1
[END OF IF]
 Step 2: EXIT
C Function
int delete (int queue[], int max, int front, int rear) {
int y;
if (front == -1 || front > rear) {
printf("underflow");
}
else {
y = queue[front];
if(front == rear) {
front = rear = -1;
else
front = front + 1;
}
return y;
}
}

Linked List implementation of Queue


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.
1. 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.
ptr -> data = item;
if(front == NULL) {
front = ptr;
rear = ptr;
front -> next = NULL;
rear -> next = NULL;
}
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.
rear -> next = ptr;
rear = ptr;
rear->next = NULL;
In this way, the element is inserted into the queue. The algorithm and the C implementation is given as
follows.
Algorithm
- Step 1: Allocate the space for the new node PTR
- Step 2: SET PTR -> DATA = VAL
- 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]
- Step 4: END

C Function
void insert(struct node *ptr, int item; ) {
ptr = (struct node *) malloc (sizeof(struct node));
if(ptr == NULL) {
printf("\nOVERFLOW\n");
return;
}
else {
ptr -> data = item;
if(front == NULL) {
front = ptr;
rear = ptr;
front -> next = NULL;
rear -> next = NULL;
}
else {
rear -> next = ptr;
rear = ptr;
rear->next = NULL;
} } }
 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.
ptr = front;
front = front -> next;
free(ptr);
The algorithm and C function is given as follows.
Algorithm
- Step 1: IF FRONT = NULL
Write " Underflow "
Go to Step 5
[END OF IF]
- Step 2: SET PTR = FRONT
- Step 3: SET FRONT = FRONT -> NEXT
- Step 4: FREE PTR
- Step 5: END

C Function
void delete (struct node *ptr) {
if(front == NULL) {
printf("\nUNDERFLOW\n");
return;
}
else {
ptr = front;
front = front -> next;
free(ptr);
}
}

// A C program to demonstrate linked list based


// implementation of queue
#include <stdio.h>
#include <stdlib.h>
// A linked list (LL) node to store a queue entry
struct QNode {
int key;
struct QNode* next;
};
// The queue, front stores the front node of LL and rear
// stores the last node of LL
struct Queue {
struct QNode *front, *rear;
};
// A utility function to create a new linked list node.
struct QNode* newNode(int k){
struct QNode* temp = (struct QNode*)malloc(sizeof(struct QNode));
temp->key = k;
temp->next = NULL;
return temp;
}
// A utility function to create an empty queue
struct Queue* createQueue()
{
struct Queue* q
= (struct Queue*)malloc(sizeof(struct Queue));
q->front = q->rear = NULL;
return q;
}
// The function to add a key k to q
void enQueue(struct Queue* q, int k) {
// Create a new LL node
struct QNode* temp = newNode(k);

// If queue is empty, then new node is front and rear


// both
if (q->rear == NULL) {
q->front = q->rear = temp;
return;
}
// Add the new node at the end of queue and change rear
q->rear->next = temp;
q->rear = temp;
}
// Function to remove a key from given queue q
void deQueue(struct Queue* q){
// If queue is empty, return NULL.
if (q->front == NULL)
return;
// Store previous front and move front one node ahead
struct QNode* temp = q->front;
q->front = q->front->next;
// If front becomes NULL, then change rear also as NULL
if (q->front == NULL)
q->rear = NULL;
free(temp);
}

// Driver code
int main(){
struct Queue* q = createQueue();
enQueue(q, 10);
enQueue(q, 20);
deQueue(q);
deQueue(q);
enQueue(q, 30);
enQueue(q, 40);
enQueue(q, 50);
deQueue(q);
printf("Queue Front : %d \n", ((q->front != NULL) ? (q->front)->key : -1));
printf("Queue Rear : %d", ((q->rear != NULL) ? (q->rear)->key : -1));
return 0; }
Circular Queue
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:
 Front: It is used to get the front element from the Queue.
 Rear: It is used to get the rear element from the Queue.
 enQueue(value): This function is used to insert the new value in the Queue. The new element is
always inserted from the rear end.
 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:
 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.
 CPU Scheduling: The operating system also uses the circular queue to insert the processes and then
execute them.
 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:


 First, we will check whether the Queue is full or not.
 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.
 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:


 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.
 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:


 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.
 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:


 First, we check whether the Queue is empty or not. If the queue is empty, we cannot perform the
dequeue operation.
 When the element is deleted, the value of front gets decremented by 1.
 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

#include <stdio.h>

# define max 6
int queue[max]; // array declaration
int front=-1;
int rear=-1;
// function to insert an element in a circular queue
void enqueue(int element)
{
if(front==-1 && rear==-1) // condition to check queue is empty
{
front=0;
rear=0;
queue[rear]=element;
}
else if((rear+1)%max==front) // condition to check queue is full
{
printf("Queue is overflow..");
}
else
{
rear=(rear+1)%max; // rear is incremented
queue[rear]=element; // assigning a value to the queue at the rear position.
}
}

// function to delete the element from the queue


int dequeue()
{
if((front==-1) && (rear==-1)) // condition to check queue is empty
{
printf("\nQueue is underflow..");
}
else if(front==rear)
{
printf("\nThe dequeued element is %d", queue[front]);
front=-1;
rear=-1;
}
else
{
printf("\nThe dequeued element is %d", queue[front]);
front=(front+1)%max;
}
}
// function to display the elements of a queue
void display()
{
int i=front;
if(front==-1 && rear==-1)
{
printf("\n Queue is empty..");
}
else
{
printf("\nElements in a Queue are :");
while(i<=rear)
{
printf("%d,", queue[i]);
i=(i+1)%max;
}
}
}
int main()
{
int choice=1,x; // variables declaration

while(choice<4 && choice!=0) // while loop


{
printf("\n Press 1: Insert an element");
printf("\nPress 2: Delete an element");
printf("\nPress 3: Display the element");
printf("\nEnter your choice");
scanf("%d", &choice);

switch(choice)
{

case 1:

printf("Enter the element which is to be inserted");


scanf("%d", &x);
enqueue(x);
break;
case 2:
dequeue();
break;
case 3:
display();

}}
return 0;
}
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.
#include <stdio.h>
// Declaration of struct type node
struct node
{
int data;
struct node *next;
};
struct node *front=-1;
struct node *rear=-1;
// function to insert the element in the Queue
void enqueue(int x)
{
struct node *newnode; // declaration of pointer of struct node type.
newnode=(struct node *)malloc(sizeof(struct node)); // allocating the memory to the newnode
newnode->data=x;
newnode->next=0;
if(rear==-1) // checking whether the Queue is empty or not.
{
front=rear=newnode;
rear->next=front;
}
else
{
rear->next=newnode;
rear=newnode;
rear->next=front;
}
}

// function to delete the element from the queue


void dequeue()
{
struct node *temp; // declaration of pointer of node type
temp=front;
if((front==-1)&&(rear==-1)) // checking whether the queue is empty or not
{
printf("\nQueue is empty");
}
else if(front==rear) // checking whether the single element is left in the queue
{
front=rear=-1;
free(temp);
}
else
{
front=front->next;
rear->next=front;
free(temp);
}
}

// function to get the front of the queue


int peek()
{
if((front==-1) &&(rear==-1))
{
printf("\nQueue is empty");
}
else
{
printf("\nThe front element is %d", front->data);
}
}

// function to display all the elements of the queue


void display()
{
struct node *temp;
temp=front;
printf("\n The elements in a Queue are : ");
if((front==-1) && (rear==-1))
{
printf("Queue is empty");
}

else
{
while(temp->next!=front)
{
printf("%d,", temp->data);
temp=temp->next;
}
printf("%d", temp->data);
}
}

void main()
{
enqueue(34);
enqueue(10);
enqueue(23);
display();
dequeue();
peek();
}
Output:

Deque (or double-ended queue)


The deque stands for Double Ended Queue. Deque is a linear data structure where the insertion and deletion
operations are performed from both ends. We can say that deque is a generalized version of the queue.
Though the insertion and deletion in a deque can be performed on both ends, it does not follow the FIFO
rule. The representation of a deque is given as follows -

Types of deque

There are two types of deque -


 Input restricted queue
 Output restricted queue

Input restricted Queue


In input restricted queue, insertion operation can be performed at only one end, while deletion can be
performed from both ends.

Output restricted Queue


In output restricted queue, deletion operation can be performed at only one end, while insertion can be
performed from both ends.
Operations performed on deque

There are the following operations that can be applied on a deque -


 Insertion at front
 Insertion at rear
 Deletion at front
 Deletion at rear

We can also perform peek operations in the deque along with the operations listed above. Through peek
operation, we can get the deque's front and rear elements of the deque. So, in addition to the above
operations, following operations are also supported in deque -
 Get the front item from the deque
 Get the rear item from the deque
 Check whether the deque is full or not
 Checks whether the deque is empty or not

Now, let's understand the operation performed on deque using an example.


Insertion at the front end
In this operation, the element is inserted from the front end of the queue. Before implementing the operation,
we first have to check whether the queue is full or not. If the queue is not full, then the element can be
inserted from the front end by using the below conditions -
 If the queue is empty, both rear and front are initialized with 0. Now, both will point to the first
element.
 Otherwise, check the position of the front if the front is less than 1 (front < 1), then reinitialize it
by front = n - 1, i.e., the last index of the array.

Insertion at the rear end


In this operation, the element is inserted from the rear end of the queue. Before implementing the operation,
we first have to check again whether the queue is full or not. If the queue is not full, then the element can be
inserted from the rear end by using the below conditions -
 If the queue is empty, both rear and front are initialized with 0. Now, both will point to the first
element.
 Otherwise, increment the rear by 1. If the rear is at last index (or size - 1), then instead of increasing
it by 1, we have to make it equal to 0.
Deletion at the front end
In this operation, the element is deleted from the front end of the queue. Before implementing the operation,
we first have to check whether the queue is empty or not.
If the queue is empty, i.e., front = -1, it is the underflow condition, and we cannot perform the deletion. If
the queue is not full, then the element can be inserted from the front end by using the below conditions -
If the deque has only one element, set rear = -1 and front = -1.
Else if front is at end (that means front = size - 1), set front = 0.
Else increment the front by 1, (i.e., front = front + 1).

Deletion at the rear end


In this operation, the element is deleted from the rear end of the queue. Before implementing the operation,
we first have to check whether the queue is empty or not.
If the queue is empty, i.e., front = -1, it is the underflow condition, and we cannot perform the deletion.
If the deque has only one element, set rear = -1 and front = -1.
If rear = 0 (rear is at front), then set rear = n - 1.
Else, decrement the rear by 1 (or, rear = rear -1).
Check empty
This operation is performed to check whether the deque is empty or not. If front = -1, it means that the deque
is empty.
Check full
This operation is performed to check whether the deque is full or not. If front = rear + 1, or front = 0 and
rear = n - 1 it means that the deque is full.
The time complexity of all of the above operations of the deque is O(1), i.e., constant.
Applications of deque
 Deque can be used as both stack and queue, as it supports both operations.
 Deque can be used as a palindrome checker means that if we read the string from both ends, the
string would be the same.

Implementation of deque
#include <stdio.h>
#define size 5
int deque[size];
int f = -1, r = -1;
// insert_front function will insert the value from the front
void insert_front(int x)
{
if((f==0 && r==size-1) || (f==r+1))
{
printf("Overflow");
}
else if((f==-1) && (r==-1))
{
f=r=0;
deque[f]=x;
}
else if(f==0)
{
f=size-1;
deque[f]=x;
}
else
{
f=f-1;
deque[f]=x;
}
}

// insert_rear function will insert the value from the rear


void insert_rear(int x)
{
if((f==0 && r==size-1) || (f==r+1))
{
printf("Overflow");
}
else if((f==-1) && (r==-1))
{
r=0;
deque[r]=x;
}
else if(r==size-1)
{
r=0;
deque[r]=x;
}
else
{
r++;
deque[r]=x;
}

// display function prints all the value of deque.


void display()
{
int i=f;
printf("\nElements in a deque are: ");

while(i!=r)
{
printf("%d ",deque[i]);
i=(i+1)%size;
}
printf("%d",deque[r]);
}

// getfront function retrieves the first value of the deque.


void getfront()
{
if((f==-1) && (r==-1))
{
printf("Deque is empty");
}
else
{
printf("\nThe value of the element at front is: %d", deque[f]);
}

// getrear function retrieves the last value of the deque.


void getrear()
{
if((f==-1) && (r==-1))
{
printf("Deque is empty");
}

else
{
printf("\nThe value of the element at rear is %d", deque[r]);
}

// delete_front() function deletes the element from the front


void delete_front()
{
if((f==-1) && (r==-1))
{
printf("Deque is empty");
}
else if(f==r)
{
printf("\nThe deleted element is %d", deque[f]);
f=-1;
r=-1;

}
else if(f==(size-1))
{
printf("\nThe deleted element is %d", deque[f]);
f=0;
}
else
{
printf("\nThe deleted element is %d", deque[f]);
f=f+1;
}
}

// delete_rear() function deletes the element from the rear


void delete_rear()
{
if((f==-1) && (r==-1))
{
printf("Deque is empty");
}
else if(f==r)
{
printf("\nThe deleted element is %d", deque[r]);
f=-1;
r=-1;

}
else if(r==0)
{
printf("\nThe deleted element is %d", deque[r]);
r=size-1;
}
else
{
printf("\nThe deleted element is %d", deque[r]);
r=r-1;
}
}

int main()
{
insert_front(20);
insert_front(10);
insert_rear(30);
insert_rear(50);
insert_rear(80);
display(); // Calling the display function to retrieve the values of deque
getfront(); // Retrieve the value at front-end
getrear(); // Retrieve the value at rear-end
delete_front();
delete_rear();
display(); // calling display function to retrieve values after deletion
return 0;
}
Output:

So, that's all about the article. Hope, the article will be helpful and informative to you.
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:


 Every element in a priority queue has some priority associated with it.

 An element with the higher priority will be deleted before the deletion of the lesser priority.
 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:
 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.
 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.
 poll(): It will remove '2' element from the priority queue as it has the highest priority queue.
 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

 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.

 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:
 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.

 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.
 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.
 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:


 It is used in the Dijkstra's shortest path algorithm.
 It is used in prim's algorithm
 It is used in data compression techniques like Huffman code.
 It is used in heap sort.
 It is also used in operating system like priority scheduling, load balancing and interrupt handling.

Program to create the priority queue using the binary max heap.
#include <stdio.h>
#include <stdio.h>
int heap[40];
int size=-1;

// retrieving the parent node of the child node


int parent(int i)
{

return (i - 1) / 2;
}

// retrieving the left child of the parent node.


int left_child(int i)
{
return i+1;
}
// retrieving the right child of the parent
int right_child(int i)
{
return i+2;
}
// Returning the element having the highest priority
int get_Max()
{
return heap[0];
}
//Returning the element having the minimum priority
int get_Min()
{
return heap[size];
}
// function to move the node up the tree in order to restore the heap property.
void moveUp(int i)
{
while (i > 0)
{
// swapping parent node with a child node
if(heap[parent(i)] < heap[i]) {

int temp;
temp=heap[parent(i)];
heap[parent(i)]=heap[i];
heap[i]=temp;

}
// updating the value of i to i/2
i=i/2;
}
}

//function to move the node down the tree in order to restore the heap property.
void moveDown(int k)
{
int index = k;

// getting the location of the Left Child


int left = left_child(k);

if (left <= size && heap[left] > heap[index]) {


index = left;
}

// getting the location of the Right Child


int right = right_child(k);
if (right <= size && heap[right] > heap[index]) {
index = right;
}

// If k is not equal to index


if (k != index) {
int temp;
temp=heap[index];
heap[index]=heap[k];
heap[k]=temp;
moveDown(index);
}
}

// Removing the element of maximum priority


void removeMax()
{
int r= heap[0];
heap[0]=heap[size];
size=size-1;
moveDown(0);
}
//inserting the element in a priority queue
void insert(int p)
{
size = size + 1;
heap[size] = p;

// move Up to maintain heap property


moveUp(size);
}

//Removing the element from the priority queue at a given index i.


void delete(int i)
{
heap[i] = heap[0] + 1;

// move the node stored at ith location is shifted to the root node
moveUp(i);

// Removing the node having maximum priority


removeMax();
}
int main()
{
// Inserting the elements in a priority queue

insert(20);
insert(19);
insert(21);
insert(18);
insert(12);
insert(17);
insert(15);
insert(16);
insert(14);
int i=0;

printf("Elements in a priority queue are : ");


for(int i=0;i<=size;i++)
{
printf("%d ",heap[i]);
}
delete(2); // deleting the element whose index is 2.
printf("\nElements in a priority queue after deleting the element are : ");
for(int i=0;i<=size;i++)
{
printf("%d ",heap[i]);
}
int max=get_Max();
printf("\nThe element which is having the highest priority is %d: ",max);

int min=get_Min();
printf("\nThe element which is having the minimum priority is : %d",min);
return 0;
}

In the above program, we have created the following functions:


 int parent(int i): This function returns the index of the parent node of a child node, i.e., i.
 int left_child(int i): This function returns the index of the left child of a given index, i.e., i.
 int right_child(int i): This function returns the index of the right child of a given index, i.e., i.
 void moveUp(int i): This function will keep moving the node up the tree until the heap property is
restored.
 void moveDown(int i): This function will keep moving the node down the tree until the heap
property is restored.
 void removeMax(): This function removes the element which is having the highest priority.
 void insert(int p): It inserts the element in a priority queue which is passed as an argument in a
function.
 void delete(int i): It deletes the element from a priority queue at a given index.
 int get_Max(): It returns the element which is having the highest priority, and we know that in max
heap, the root node contains the element which has the largest value, and highest priority.
 int get_Min(): It returns the element which is having the minimum priority, and we know that in
max heap, the last node contains the element which has the smallest value, and lowest priority.
Output
MODULE 4
TREE AND GRAPHS
Tree data structure is a specialized data structure to store data in hierarchical manner. It is used to
organize and store data in the computer to be used more effectively. It consists of a central node,
structural nodes, and sub-nodes, which are connected via edges. We can also say that tree data structure
has roots, branches, and leaves connected.

 Tree data structure is a hierarchical structure that is used to represent and organize data in a
way that is easy to navigate and search. It is a collection of nodes that are connected by edges
and has a hierarchical relationship between the nodes.
 The topmost node of the tree is called the root, and the nodes below it are called the child nodes.
Each node can have multiple child nodes, and these child nodes can also have their own child
nodes, forming a recursive structure.
Basic Terminologies In Tree Data Structure:
 Parent Node: The node which is a predecessor of a node is called the parent node of that
node. {B} is the parent node of {D, E}.
 Child Node: The node which is the immediate successor of a node is called the child node of
that node. Examples: {D, E} are the child nodes of {B}.
 Root Node: The topmost node of a tree or the node which does not have any parent node is
called the root node. {A} is the root node of the tree. A non-empty tree must contain exactly one
root node and exactly one path from the root to all other nodes of the tree.
 Leaf Node or External Node: The nodes which do not have any child nodes are called leaf
nodes. {K, L, M, N, O, P, G} are the leaf nodes of the tree.
 Ancestor of a Node: Any predecessor nodes on the path of the root to that node are called
Ancestors of that node. {A,B} are the ancestor nodes of the node {E}
 Descendant: A node x is a descendant of another node y if and only if y is an ancestor of x.
 Sibling: Children of the same parent node are called siblings. {D,E} are called siblings.
 Level of a node: The count of edges on the path from the root node to that node. The root node
has level 0.
 Internal node: A node with at least one child is called Internal Node.

 Neighbour of a Node: Parent or child nodes of that node are called neighbors of that node.

 Subtree: Any node of the tree along with its descendant.


Representation of Tree Data Structure:
A tree consists of a root node, and zero or more subtrees T1, T2, … , Tk such that there is an edge from
the root node of the tree to the root node of each subtree. Subtree of a node X consists of all the nodes
which have node X as the ancestor node.

struct Node {
int data;
struct Node* first_child;
struct Node* second_child;
struct Node* third_child;
struct Node* nth_child;
};
Basic Operations Of Tree Data Structure:
 Create – create a tree in the data structure.
 Insert − Inserts data in a tree.
 Search − Searches specific data in a tree to check whether it is present or not.
 Traversal: Depth-First-Search Traversal, Breadth-First-Search Traversal
Properties of Tree Data Structure:
o Number of edges: An edge can be defined as the connection between two nodes. If a tree has N
nodes then it will have (N-1) edges. There is only one path from each node to any other node of
the tree.
o Depth of a node: The depth of a node is defined as the length of the path from the root to that
node. Each edge adds 1 unit of length to the path. So, it can also be defined as the number of
edges in the path from the root of the tree to the node.
o Height of a node: The height of a node can be defined as the length of the longest path from the
node to a leaf node of the tree.
o Height of the Tree: The height of a tree is the length of the longest path from the root of the tree
to a leaf node of the tree.
o Degree of a Node: The total count of subtrees attached to that node is called the degree of the
node. The degree of a leaf node must be 0. The degree of a tree is the maximum degree of a node
among all the nodes in the tree.
Applications of Tree Data Structure:
 File System: This allows for efficient navigation and organization of files.
 Data Compression: Huffman coding is a popular technique for data compression that involves
constructing a binary tree where the leaves represent characters and their frequency of
occurrence. The resulting tree is used to encode the data in a way that minimizes the amount of
storage required.
 Compiler Design: In compiler design, a syntax tree is used to represent the structure of a
program.
 Database Indexing: B-trees and other tree structures are used in database indexing to efficiently
search for and retrieve data.
Advantages of Tree Data Structure:
 Tree offer Efficient Searching Depending on the type of tree, with average search times of
O(log n) for balanced trees like AVL.
 Trees provide a hierarchical representation of data, making it easy to organize and
navigate large amounts of information.
 The recursive nature of trees makes them easy to traverse and manipulate using recursive
algorithms.
Disadvantages of Tree Data Structure:
 Unbalanced Trees, meaning that the height of the tree is skewed towards one side, which can
lead to inefficient search times.
 Trees demand more memory space requirements than some other data structures like arrays
and linked lists, especially if the tree is very large.
 The implementation and manipulation of trees can be complex and require a good
understanding of the algorithms.
BINARY TREE
Binary Tree is a non-linear data structure where each node has at most two children. In this article,
we will cover all the basics of Binary Tree, Operations on Binary Tree, its implementation, advantages,
disadvantages which will help you solve all the problems based on Binary Tree.

What is Binary Tree?


Binary tree is a tree data structure(non-linear) in which each node can have at most two
children which are referred to as the left child and the right child.
The topmost node in a binary tree is called the root, and the bottom-most nodes are called leaves. A
binary tree can be visualized as a hierarchical structure with the root at the top and the leaves at the
bottom.
Representation of Binary Tree
Each node in a Binary Tree has three parts:
 Data
 Pointer to the left child
 Pointer to the right child
Representation of a Node of Binary Tree
// Structure of each node of the tree
struct node {
int data;
struct node* left;
struct node* right;
};
Operations On Binary Tree
1. Insertion in Binary Tree
 We can insert a node anywhere in a binary tree by inserting the node as the left or right child of
any node or by making the node as root of the tree.
 Algorithm to insert a node in a Binary Tree:
 Check if there is a node in the binary tree, which has missing left child. If such a node exists,
then insert the new node as its left child.
 Check if there is a node in the binary tree, which has missing right child. If such a node exists,
then insert the new node as its right child.
 If we don’t find any node with missing left or right child, then find the node which has both the
children missing and insert the node as its left or right child.

2. Traversal of Binary Tree


 Traversal of Binary Tree involves visiting all the nodes of the binary tree.
Tree Traversal algorithms can be classified broadly into two categories:
 Depth-First Search (DFS) Algorithms
 Breadth-First Search (BFS) Algorithms
 Depth-First Search (DFS) algorithms:
 Preorder Traversal (current-left-right): Visit the current node before visiting any nodes inside
the left or right subtrees. Here, the traversal is root – left child – right child. It means that the root
node is traversed first then its left child and finally the right child.
 Inorder Traversal (left-current-right): Visit the current node after visiting all nodes inside the
left subtree but before visiting any node within the right subtree. Here, the traversal is left child –
root – right child. It means that the left child is traversed first then its root node and finally the
right child.
 Postorder Traversal (left-right-current): Visit the current node after visiting all the nodes of
the left and right subtrees. Here, the traversal is left child – right child – root. It means that the
left child has traversed first then the right child and finally its root node.
Breadth-First Search (BFS) algorithms:
Level Order Traversal: Visit nodes level-by-level and left-to-right fashion at the same level. Here, the
traversal is level-wise. It means that the most left child has traversed first and then the other children of
the same level from left to right have traversed.
3. Deletion in Binary Tree
 We can delete any node in the binary tree and rearrange the nodes after deletion to again form a
valid binary tree.
 Algorithm to delete a node in a Binary Tree:
 Starting at the root, find the deepest and rightmost node in the binary tree and the node which
we want to delete.
 Replace the deepest rightmost node’s data with the node to be deleted.
 Then delete the deepest rightmost node.

Searching in Binary Tree


 We can search for an element in the node by using any of the traversal techniques.
 Algorithm to search a node in a Binary Tree:
 Start from the root node.
 Check if the current node’s value is equal to the target value.
 If the current node’s value is equal to the target value, then this node is the required node.
 Otherwise, if the node’s value is not equal to the target value, start the search in the left and
right child.
 If we do not find any node whose value is equal to target, then the value is not present in the
tree.
Advantages of Binary Tree
 Efficient Search: Binary trees are efficient when searching for a specific element, as each node
has at most two child nodes, allowing for binary search algorithms to be used.
 Memory Efficient: Binary trees require lesser memory as compared to other tree data structures,
therefore they are memory-efficient.
 Binary trees are relatively easy to implement and understand as each node has at most two
children, left child and right child.
Disadvantages of Binary Tree
 Limited structure: Binary trees are limited to two child nodes per node, which can limit their
usefulness in certain applications. For example, if a tree requires more than two child nodes per
node, a different tree structure may be more suitable.
 Unbalanced trees: Unbalanced binary trees, where one subtree is significantly larger than the
other, can lead to inefficient search operations. This can occur if the tree is not properly balanced
or if data is inserted in a non-random order.
 Space inefficiency: Binary trees can be space inefficient when compared to other data structures.
This is because each node requires two child pointers, which can be a significant amount of
memory overhead for large trees.
 Slow performance in worst-case scenarios: In the worst-case scenario, a binary tree can
become degenerate or skewed, meaning that each node has only one child. In this case, search
operations can degrade to O(n) time complexity, where n is the number of nodes in the tree.
Applications of Binary Tree
 Binary Tree can be used to represent hierarchical data.
 Huffman Coding trees are used in data compression algorithms.
 Priority Queue is another application of binary tree that is used for searching maximum or
minimum in O(1) time complexity.
 Useful for indexing segmented at the database is useful in storing cache in the system,
 Binary trees can be used to implement decision trees, a type of machine learning algorithm used
for classification and regression analysis.
Expression Tree
The expression tree is a binary tree in which each internal node corresponds to the operator and each leaf
node corresponds to the operand so for example expression tree for 3 + ((5+9)*2) would be:

Inorder traversal of expression tree produces infix version of given postfix expression (same with
postorder traversal it gives postfix expression)
Construction of Expression Tree:
 Now For constructing an expression tree we use a stack. We loop through input expression and
do the following for every character.
 If a character is an operand push that into the stack
 If a character is an operator pop two values from the stack make them its child and push the
current node again.
 In the end, the only element of the stack will be the root of an expression tree.
Examples:
Input: A B C*+ D/
Output: A + B * C / D
BINARY SEARCH TREE
Binary Search Tree is a data structure used in computer science for organizing and storing data in a
sorted manner. Binary search tree follows all properties of binary tree and its left child contains values
less than the parent node and the right child contains values greater than the parent node. This
hierarchical structure allows for efficient Searching, Insertion, and Deletion operations on the data
stored in the tree.

What is Binary Search Tree?


Binary Search Tree (BST) is a special type of binary tree in which the left child of a node has a value
less than the node’s value and the right child has a value greater than the node’s value. This property is
called the BST property and it makes it possible to efficiently search, insert, and delete elements in the
tree.
Properties of Binary Search Tree:
 The left subtree of a node contains only nodes with keys lesser than the node’s key.
 The right subtree of a node contains only nodes with keys greater than the node’s key.
 This means everything to the left of the root is less than the value of the root and everything to
the right of the root is greater than the value of the root. Due to this performing, a binary search
is very easy.
 The left and right subtree each must also be a binary search tree.
 There must be no duplicate nodes(BST may have duplicate values with different handling
approaches)
Basic Operations on Binary Search Tree:
1. Searching a node in BST:
Searching in BST means to locate a specific node in the data structure. In Binary search tree, searching a
node is easy because of its a specific order.
The steps of searching a node in Binary Search tree are listed as follows –
 First, compare the element to be searched with the root element of the tree.
 If root is matched with the target element, then return the node’s location.
 If it is not matched, then check whether the item is less than the root element, if it is smaller than
the root element, then move to the left subtree.
 If it is larger than the root element, then move to the right subtree.
 Repeat the above procedure recursively until the match is found.
 If the element is not found or not present in the tree, then return NULL.
 Now, let’s understand the searching in binary tree using an example:
 Below is given a BST and we have to search for element 6.

1ST STEP  2ND STEP

3RD STEP  4TH STEP


2. Insert a node into a BST:
A new key is always inserted at the leaf. Start searching a key from the root till a leaf node. Once a leaf
node is found, the new node is added as a child of the leaf node.

3. Delete a Node of BST:


It is used to delete a node with specific key from the BST and return the new BST.
Different scenarios for deleting the node:
Node to be deleted is the leaf node :
Its simple you can just null it out.
Node to be deleted has one child :
You can just replace the node with the child node.

Node to be deleted has two child :


Here we have to delete the node is such a way, that the resulting tree follows the properties of a
BST. The trick is to find the inorder successor of the node. Copy contents of the inorder successor to
the node, and delete the inorder successor.
Take Care of following things while deleting a node of a BST:
 Need to figure out what will be the replacement of the node to be deleted.
 Want minimal disruption to the existing tree structure
 Can take the replacement node from the deleted nodes left or right subtree.
 If taking if from the left subtree, we have to take the largest value in the left subtree.
 If taking if from the right subtree, we have to take the smallest value in the right subtree.
4. Traversal (Inorder traversal of BST) :
In case of binary search trees (BST), Inorder traversal gives nodes in non-decreasing order. We visit the
left child first, then the root, and then the right child.
Applications of BST:
 Graph algorithms: BSTs can be used to implement graph algorithms, such as in minimum
spanning tree algorithms.
 Priority Queues: BSTs can be used to implement priority queues, where the element with the
highest priority is at the root of the tree, and elements with lower priority are stored in the
subtrees.
 Self-balancing binary search tree: BSTs can be used as a self-balancing data structures such as
AVL tree and Red-black tree.
 Data storage and retrieval: BSTs can be used to store and retrieve data quickly, such as in
databases, where searching for a specific record can be done in logarithmic time.
Advantages:
 Fast search: Searching for a specific value in a BST has an average time complexity of O(log
n), where n is the number of nodes in the tree. This is much faster than searching for an element
in an array or linked list, which have a time complexity of O(n) in the worst case.
 In-order traversal: BSTs can be traversed in-order, which visits the left subtree, the root, and
the right subtree. This can be used to sort a dataset.
 Space efficient: BSTs are space efficient as they do not store any redundant information, unlike
arrays and linked lists.
Disadvantages:
 Skewed trees: If a tree becomes skewed, the time complexity of search, insertion, and deletion
operations will be O(n) instead of O(log n), which can make the tree inefficient.
 Additional time required: Self-balancing trees require additional time to maintain balance
during insertion and deletion operations.
 Efficiency: BSTs are not efficient for datasets with many duplicates as they will waste space.
What is the Height Balance of a node?
To check if the binary tree is height-balanced or not, you have to check the height balance of each node.
For this, you need to calculate the heights of the two subtrees for each node making this impractical.
Instead, we store the height balance information of every subtree in the root node of that subtree. Thus,
each node not only maintains its data and children’s information but also a height balance value.
The height balance of a node is calculated as follows:
eight balance of node = height of right subtree – height of left subtree
The above formula means that:
If the right subtree is taller, the height balance of the node will be positive.
If the left subtree is taller, the balance of the node will be negative.

Why do we need a Height-Balanced Binary Tree?


Let’s understand the need for a balanced binary tree through an example.

 The above tree is a binary search tree and also a height-balanced tree. Suppose we want to find
the value 79 in the above tree. First, we compare the value of the root node. Since the value of 79
is greater than 35, we move to its right child, i.e., 48. Since the value 79 is greater than 48, so we
move to the right child of 48. The value of the right child of node 48 is 79. The number of hops
required to search the element 79 is 2.
Similarly, any element can be found with at most 2 jumps because the height of the tree is 2.
 So it can be seen that any value in a balanced binary tree can be searched in O(logN) time
where N is the number of nodes in the tree. But if the tree is not height-balanced then in the
worst case, a search operation can take O(N) time.
Applications of Height-Balanced Binary Tree:
 Balanced trees are mostly used for in-memory sorts of sets and dictionaries.
 Balanced trees are also used extensively in database applications in which insertions and
deletions are fewer but there are frequent lookups for data required.
 It is used in applications that require improved searching apart from database applications.
 It has applications in storyline games as well.
 It is used mainly in corporate sectors where they have to keep the information about the
employees working there and their change in shifts.
Advantages of Height-Balanced Binary Tree:
 It will improve the worst-case lookup time at the expense of making a typical case roughly one
lookup less.
 As a general rule, a height-balanced tree would work better when the request frequencies across
the data set are more evenly spread,
 It gives better search time complexity.
Disadvantages of Height-Balanced Binary Tree:
 Longer running times for the insert and remove operations.
 Must keep balancing info in each node.
 To find nodes to balance, must go back up in the tree.
How to check if a given tree is height-balanced:
 You can check if a tree is height-balanced using recursion based on the idea that every subtree
of the tree will also be height-balanced. To check if a tree is height-balanced perform the
following operations:
 Use recursion and visit the left subtree and right subtree of each node:
 Check the height of the left subtree and right subtree.
 If the absolute difference between their heights is at most 1 then that node is height-balanced.
 Otherwise, that node and the whole tree is not balanced.
AVL TREE
 An AVL tree defined as a self-balancing Binary Search Tree (BST) where the difference
between heights of left and right subtrees for any node cannot be more than one.
 The difference between the heights of the left subtree and the right subtree for any node is known
as the balance factor of the node.
 The AVL tree is named after its inventors, Georgy Adelson-Velsky and Evgenii Landis, who
published it in their 1962 paper “An algorithm for the organization of information”.
Example of AVL Trees:

The above tree is AVL because the differences between the heights of left and right subtrees for every
node are less than or equal to 1.
Operations on an AVL Tree:
1. Insertion
2. Deletion
3. Searching [It is similar to performing a search in BST]
Rotating the subtrees in an AVL Tree:
An AVL tree may rotate in one of the following four ways to keep itself balanced:
Left Rotation:
When a node is added into the right subtree of the right subtree, if the tree gets out of balance, we do a
single left rotation.
Right Rotation:
If a node is added to the left subtree of the left subtree, the AVL tree may get out of balance, we do a
single right rotation.

Left-Right Rotation:
A left-right rotation is a combination in which first left rotation takes place after that right rotation
executes.

Right-Left Rotation:
A right-left rotation is a combination in which first right rotation takes place after that left rotation
executes.
Applications of AVL Tree:
 It is used to index huge records in a database and also to efficiently search in that.
 For all types of in-memory collections, including sets and dictionaries, AVL Trees are used.
 Database applications, where insertions and deletions are less common but frequent data lookups
are necessary
 Software that needs optimized search.
 It is applied in corporate areas and storyline games.
Advantages of AVL Tree:
 AVL trees can self-balance themselves.
 It is surely not skewed.
 It provides faster lookups than Red-Black Trees
 Better searching time complexity compared to other trees like binary tree.
 Height cannot exceed log(N), where, N is the total number of nodes in the tree.
Disadvantages of AVL Tree:
 It is difficult to implement.
 It has high constant factors for some of the operations.
 Less used compared to Red-Black trees.
 Due to its rather strict balance, AVL trees provide complicated insertion and removal operations
as more rotations are performed.
 Take more processing for balancing

B TREE
 B Tree is a specialized m-way tree that can be widely used for disk access. A B-Tree of order m
can have at most m-1 keys and m children. One of the main reason of using B tree is its capability
to store large number of keys in a single node and large key values by keeping the height of the
tree relatively small.
 A B tree of order m contains all the properties of an M way tree. In addition, it contains the
following properties.
 Every node in a B-Tree contains at most m children.
 Every node in a B-Tree except the root node and the leaf node contain at least m/2 children.
 The root nodes must have at least 2 nodes.
 All leaf nodes must be at the same level.
 A B tree of order 4 is shown in the following image.
While performing some operations on B Tree, any property of B Tree may violate such as number of
minimum children a node can have. To maintain the properties of B Tree, the tree may split or join.
Operations
1) Searching :
 Searching in B Trees is similar to that in Binary search tree. For example, if we search for an item
49 in the following B Tree.
 The process will something like following :
1) Compare item 49 with root node 78. since 49 < 78 hence, move to its left sub-tree.
2) Since, 40<49<56, traverse right sub-tree of 40.
3) 49>45, move to right. Compare 49.
4) match found, return.
 Searching in a B tree depends upon the height of the tree. The search algorithm takes O(log n) time
to search any element in a B tree.

2. Inserting
 Insertions are done at the leaf node level. The following algorithm needs to be followed in order to
insert an item into B Tree.
 Traverse the B Tree in order to find the appropriate leaf node at which the node can be inserted.
 If the leaf node contain less than m-1 keys then insert the element in the increasing order.
 Else, if the leaf node contains m-1 keys, then follow the following steps.
 Insert the new element in the increasing order of elements.
 Split the node into the two nodes at the median.
 Push the median element upto its parent node.
 If the parent node also contain m-1 number of keys, then split it too by following the same steps.
Example:
Insert the node 8 into the B Tree of order 5 shown in the following image.

8 will be inserted to the right of 5, therefore insert 8.

The node, now contain 5 keys which is greater than (5 -1 = 4 ) keys. Therefore split the node from the
median i.e. 8 and push it up to its parent node shown as follows.

3. Deletion
 Deletion is also performed at the leaf nodes. The node which is to be deleted can either be a leaf
node or an internal node. Following algorithm needs to be followed in order to delete a node from
a B tree.
 Locate the leaf node.
 If there are more than m/2 keys in the leaf node then delete the desired key from the node.
 If the leaf node doesn't contain m/2 keys then complete the keys by taking the element from eight
or left sibling.
 If the left sibling contains more than m/2 elements then push its largest element up to its parent and
move the intervening element down to the node where the key is deleted.
 If the right sibling contains more than m/2 elements then push its smallest element up to the parent
and move intervening element down to the node where the key is deleted.
 If neither of the sibling contain more than m/2 elements then create a new leaf node by joining two
leaf nodes and the intervening element of the parent node.
 If parent is left with less than m/2 nodes then, apply the above process on the parent too.
 If the the node which is to be deleted is an internal node, then replace the node with its in-order
successor or predecessor. Since, successor or predecessor will always be on the leaf node hence,
the process will be similar as the node is being deleted from the leaf node.
Example 1
Delete the node 53 from the B Tree of order 5 shown in the following figure.

53 is present in the right child of element 49. Delete it.

Now, 57 is the only element which is left in the node, the minimum number of elements that must be
present in a B tree of order 5, is 2. it is less than that, the elements in its left and right sub-tree are also not
sufficient therefore, merge it with the left sibling and intervening element of parent i.e. 49.
The final B tree is shown as follows.

Applications of B-Trees:
 It is used in large databases to access data stored on the disk
 Searching for data in a data set can be achieved in significantly less time using the B-Tree
 With the indexing feature, multilevel indexing can be achieved.
 Most of the servers also use the B-tree approach.
 B-Trees are used in CAD systems to organize and search geometric data.
 B-Trees are also used in other areas such as natural language processing, computer networks, and
cryptography.
Advantages of B-Trees:
 B-Trees have a guaranteed time complexity of O(log n) for basic operations like insertion,
deletion, and searching, which makes them suitable for large data sets and real-time applications.
 B-Trees are self-balancing.
 High-concurrency and high-throughput.
 Efficient storage utilization.
Disadvantages of B-Trees:
 B-Trees are based on disk-based data structures and can have a high disk usage.
 Not the best for all cases.
 Slow in comparison to other data structures.

Multi way tree


The m-way search trees are multi-way trees which are generalised versions of binary trees where each
node contains multiple elements. In an m-Way tree of order m, each node contains a maximum of m –
1 elements and m children.

The structure of a node of an m-Way tree is given below:


struct node {
int count;
int value[MAX + 1];
struct node* child[MAX + 1];
};
Here, count represents the number of children that a particular node has
 The values of a node stored in the array value
 The addresses of child nodes are stored in the child array
 The MAX macro signifies the maximum number of values that a particular node can contain
Searching in an m-Way search tree:
 Searching for a key in an m-Way search tree is similar to that of binary search tree
 To search for 77 in the 5-Way search tree, shown in the figure, we begin at the root & as 77>
76> 44> 18, move to the fourth sub-tree
 In the root node of the fourth sub-tree, 77< 80 & therefore we move to the first sub-tree of the
node. Since 77 is available in the only node of this sub-tree, we claim 77 was successfully
searched

Insertion in an m-Way search tree:


 The insertion in an m-Way search tree is similar to binary trees but there should be no more
than m-1 elements in a node. If the node is full then a child node will be created to insert the
further elements.
 Let us see the example given below to insert an element in an m-Way search tree.
Example: To insert a new element into an m-Way search tree we proceed in the same way as
one would in order to search for the element
 To insert 6 into the 5-Way search tree shown in the figure, we proceed to search for 6 and find
that we fall off the tree at the node [7, 12] with the first child node showing a null pointer
 Since the node has only two keys and a 5-Way search tree can accommodate up to 4 keys in a
node, 6 is inserted into the node like [6, 7, 12]
 But to insert 146, the node [148, 151, 172, 186] is already full, hence we open a new child node
and insert 146 into it.
Both these insertions have been illustrated below

Deletion in an m-Way search tree:


 Searching for a key in an m-Way search tree is similar to that of binary search tree
 To search for 77 in the 5-Way search tree, shown in the figure, we begin at the root & as 77>
76> 44> 18, move to the fourth sub-tree
 In the root node of the fourth sub-tree, 77< 80 & therefore we move to the first sub-tree of the
node. Since 77 is available in the only node of this sub-tree, we claim 77 was successfully
searched

 Searching for a key in an m-Way search tree is similar to that of binary search tree
 To search for 77 in the 5-Way search tree, shown in the figure, we begin at the root & as 77>
76> 44> 18, move to the fourth sub-tree
 In the root node of the fourth sub-tree, 77< 80 & therefore we move to the first sub-tree of the
node. Since 77 is available in the only node of this sub-tree, we claim 77 was successfully
searched
Advantages of Multi-way Trees
 Efficient Use of Space:
 Multi-way trees can use space more efficiently by storing multiple keys and children in each node,
reducing the overall height of the tree and minimizing the number of nodes.
 Reduced Tree Height:
 By having more children per node, multi-way trees often have a lower height compared to binary
trees, leading to shorter paths from the root to any leaf. This reduction in height results in faster
search, insertion, and deletion operations.
 Improved Disk Access:
 Multi-way trees are particularly well-suited for scenarios involving disk access. By storing
multiple keys in each node, they reduce the number of disk accesses required for operations, as
more data can be read or written in a single I/O operation.
 Balanced Structure:
 Many multi-way trees, like B-trees, maintain a balanced structure, ensuring that all leaf nodes are
at the same level. This balance provides consistent and predictable performance for search,
insertion, and deletion operations.
 Support for Range Queries:
 Multi-way trees facilitate efficient range queries and sequential access to data, which is beneficial
for applications like databases where such operations are common.
 Flexibility in Node Size:
 The node size in multi-way trees can be adjusted to optimize performance based on the underlying
hardware, such as matching the node size to the block size of the storage medium.
Disadvantages of Multi-way Trees
 Complex Implementation:The implementation of multi-way trees is more complex than simpler
structures like binary search trees. Handling node splits, merges, and balancing operations requires
additional code and careful handling.
 Memory Overhead:Each node in a multi-way tree contains multiple keys and pointers, which can
lead to significant memory overhead. This is particularly problematic if the keys or pointers are
large.
 Partial Node Utilization:To maintain balance and efficiency, nodes in multi-way trees may not be
fully utilized, leading to wasted space within nodes. This partial utilization can impact overall
memory efficiency.
 Insertion and Deletion Complexity:Insertion and deletion operations in multi-way trees can be
complex, involving multiple steps to ensure that the tree remains balanced. These operations can be
more intricate compared to simpler trees.
 Not Always Optimal for In-Memory Structures:Multi-way trees are optimized for scenarios
involving disk-based storage. For purely in-memory structures, simpler data structures like hash
tables or binary search trees may offer better performance due to lower overhead.

Applications of Multi-way Trees


 Database Indexing:Multi-way trees, particularly B-trees and B+ trees, are widely used in database
indexing. They support efficient range queries, insertion, deletion, and search operations, making
them ideal for managing large datasets.
 File Systems:Multi-way trees are used in file systems to manage hierarchical data structures.
Examples include the use of B-trees in the NTFS file system and the HFS+ file system in macOS.
 Network Routing:Multi-way trees can represent hierarchical routing tables in networking. They
help in efficiently managing and searching routing information.
 Game Trees:Multi-way trees are used in artificial intelligence to represent decision processes in
games. They allow for the representation of multiple possible moves and outcomes in game theory.
 Data Compression:Multi-way trees are used in data compression algorithms like Huffman coding
to efficiently represent variable-length codes.
 Information Retrieval:Multi-way trees are used in information retrieval systems to manage and
search through large volumes of text data efficiently.
GRAPHS
Graph data structures are a powerful tool for representing and analyzing complex relationships
between objects or entities. They are particularly useful in fields such as social network analysis,
recommendation systems, and computer networks. In the field of sports data science, graph data
structures can be used to analyze and understand the dynamics of team performance and player
interactions on the field.

What is a Graph?
Graph is a non-linear data structure consisting of vertices and edges. The vertices are sometimes also
referred to as nodes and the edges are lines or arcs that connect any two nodes in the graph. More
formally a Graph is composed of a set of vertices( V ) and a set of edges( E ). The graph is denoted
by G(V, E).
Components of a Graph:
 Vertices: Vertices are the fundamental units of the graph. Sometimes, vertices are also known as
vertex or nodes. Every node/vertex can be labeled or unlabelled.
 Edges: Edges are drawn or used to connect two nodes of the graph. It can be ordered pair of
nodes in a directed graph. Edges can connect any two nodes in any possible way. There are no
rules. Sometimes, edges are also known as arcs. Every edge can be labelled/unlabelled.
Representation of Graphs:
There are two ways to store a graph:
 Adjacency Matrix
 Adjacency List

Adjacency Matrix Representation of Graph:


In this method, the graph is stored in the form of the 2D matrix where rows and columns denote
vertices. Each entry in the matrix represents the weight of the edge between those vertices.
Adjacency List Representation of Graph:
This graph is represented as a collection of linked lists. There is an array of pointer which points to the
edges connected to that vertex.

Real-Life Applications of Graph:


 Graphs have numerous real-life applications across various fields. Some of them are listed
below:
 Graphs are used to represent social networks, such as networks of friends on social media.
 Graphs can be used to represent the topology of computer networks, such as the connections
between routers and switches.
 Graphs are used to represent the connections between different places in a transportation
network, such as roads and airports.
 Neural Networks: Vertices represent neurons and edges represent the synapses between them.
Neural networks are used to understand how our brain works and how connections change when
we learn. The human brain has about 10^11 neurons and close to 10^15 synapses.
 Compilers: Graphs are used extensively in compilers. They can be used for type inference, for
so-called data flow analysis, register allocation, and many other purposes. They are also used in
specialized compilers, such as query optimization in database languages.
 Robot planning: Vertices represent states the robot can be in and the edges the possible
transitions between the states. Such graph plans are used, for example, in planning paths for
autonomous vehicles.
Advantages of Graphs:
 Graphs are a versatile data structure that can be used to represent a wide range of relationships
and data structures.
 They can be used to model and solve a wide range of problems, including pathfinding, data
clustering, network analysis, and machine learning.
 Graph algorithms are often very efficient and can be used to solve complex problems quickly and
effectively.
 Graphs can be used to represent complex data structures in a simple and intuitive way, making
them easier to understand and analyze.
Disadvantages of Graphs:
 Graphs can be complex and difficult to understand, especially for people who are not familiar
with graph theory or related algorithms.
 Creating and manipulating graphs can be computationally expensive, especially for very large or
complex graphs.
 Graph algorithms can be difficult to design and implement correctly, and can be prone to bugs
and errors.
 Graphs can be difficult to visualize and analyze, especially for very large or complex graphs,
which can make it challenging to extract meaningful insights from the data.

BREADTH FIRST SEARCH (BFS) FOR A GRAPH:


 Breadth First Search (BFS) is a graph traversal algorithm that explores all the vertices in a
graph at the current depth before moving on to the vertices at the next depth level. It starts at a
specified vertex and visits all its neighbors before moving on to the next level of
neighbors. BFS is commonly used in algorithms for pathfinding, connected components, and
shortest path problems in graphs.
 The only catch here is, that, unlike trees, graphs may contain cycles, so we may come to the
same node again. To avoid processing a node more than once, we divide the vertices into two
categories:
o Visited
o Not visited.
Breadth First Search (BFS) for a Graph Algorithm:
Let’s discuss the algorithm for the BFS:

 Initialization: Enqueue the starting node into a queue and mark it as visited.
 Exploration: While the queue is not empty:
 Dequeue a node from the queue and visit it (e.g., print its value).
 For each unvisited neighbor of the dequeued node:
 Enqueue the neighbor into the queue.
 Mark the neighbor as visited.
 Termination: Repeat step 2 until the queue is empty.
How Does the BFS Algorithm Work?
 Starting from the root, all the nodes at a particular level are visited first and then the nodes of the
next level are traversed till all the nodes are visited.
 To do this a queue is used. All the adjacent unvisited nodes of the current level are pushed into
the queue and the nodes of the current level are marked visited and popped from the queue.
Illustration:
Let us understand the working of the algorithm with the help of the following example.
Step1: Initially queue and visited arrays are empty.

Step2: Push node 0 into queue and mark it visited.

Step 3: Remove node 0 from the front of queue and visit the unvisited neighbours and push them into
queue.
Step 4: Remove node 1 from the front of queue and visit the unvisited neighbours and push them into
queue.

Step 5: Remove node 2 from the front of queue and visit the unvisited neighbours and push them into
queue.

Step 6: Remove node 3 from the front of queue and visit the unvisited neighbours and push them into
queue. As we can see that every neighbours of node 3 is visited, so move to the next node that are in the
front of the queue.

Steps 7: Remove node 4 from the front of queue and visit the unvisited neighbours and push them into
queue.
As we can see that every neighbours of node 4 are visited, so move to the next node that is in the front
of the queue.
Now, Queue becomes empty, So, terminate these process of iteration.
Applications of BFS in Graphs:
 BFS has various applications in graph theory and computer science, including:
 Shortest Path Finding: BFS can be used to find the shortest path between two nodes in an
unweighted graph. By keeping track of the parent of each node during the traversal, the shortest
path can be reconstructed.
 Cycle Detection: BFS can be used to detect cycles in a graph. If a node is visited twice during
the traversal, it indicates the presence of a cycle.
 Connected Components: BFS can be used to identify connected components in a graph. Each
connected component is a set of nodes that can be reached from each other.
 Topological Sorting: BFS can be used to perform topological sorting on a directed acyclic graph
(DAG). Topological sorting arranges the nodes in a linear order such that for any edge (u, v), u
appears before v in the order.
 Level Order Traversal of Binary Trees: BFS can be used to perform a level order traversal of
a binary tree. This traversal visits all nodes at the same level before moving to the next level.
 Network Routing: BFS can be used to find the shortest path between two nodes in a network,
making it useful for routing data packets in network protocols.

Advantages of BFS:
1. The solution will definitely found out by BFS If there is some solution.
2. BFS will never get trapped in a blind alley, which means unwanted nodes.
3. If there is more than one solution then it will find a solution with minimal steps.
Disadvantages Of BFS:
1. Memory Constraints As it stores all the nodes of the present level to go for the next level.
2. If a solution is far away then it consumes time.
DFS- DEPTH FIRST TRAVERSAL OF A TREE
Depth First Traversal (or DFS) for a graph is similar to Depth First Traversal of a tree. The only catch
here is, that, unlike trees, graphs may contain cycles (a node may be visited twice). To avoid processing
a node more than once, use a boolean visited array. A graph can have more than one DFS traversal.
How does DFS work?
Depth-first search is an algorithm for traversing or searching tree or graph data structures. The algorithm
starts at the root node (selecting some arbitrary node as the root node in the case of a graph) and
explores as far as possible along each branch before backtracking.
Let us understand the working of Depth First Search with the help of the following illustration:
Depth-first search
Advantages Of DFS:
1. The memory requirement is Linear WRT Nodes.
2. Less time and space complexity rather than BFS.
3. The solution can be found out without much more search.
The disadvantage of DFS:
1. Not Guaranteed that it will give you a solution.
2. Cut-off depth is smaller so time complexity is more.
3. Determination of depth until the search has proceeded.
Applications of DFS:-
1. Finding Connected components.
2. Topological sorting.
3. Finding Bridges of the graph.
Introduction to Dijkstra’s Shortest Path Algorithm
The algorithm maintains a set of visited vertices and a set of unvisited vertices. It starts at the source
vertex and iteratively selects the unvisited vertex with the smallest tentative distance from the source. It
then visits the neighbors of this vertex and updates their tentative distances if a shorter path is found.
This process continues until the destination vertex is reached, or all reachable vertices have been visited.
Need for Dijkstra’s Algorithm (Purpose and Use-Cases)
The need for Dijkstra’s algorithm arises in many applications where finding the shortest path between
two points is crucial.
For example, It can be used in the routing protocols for computer networks and also used by map
systems to find the shortest path between starting point and the Destination
Can Dijkstra’s Algorithm work on both Directed and Undirected graphs?
Yes, Dijkstra’s algorithm can work on both directed graphs and undirected graphs as this algorithm is
designed to work on any type of graph as long as it meets the requirements of having non-negative edge
weights and being connected.
In a directed graph, each edge has a direction, indicating the direction of travel between the vertices
connected by the edge. In this case, the algorithm follows the direction of the edges when searching for
the shortest path.
In an undirected graph, the edges have no direction, and the algorithm can traverse both forward and
backward along the edges when searching for the shortest path.
Algorithm for Dijkstra’s Algorithm:
Mark the source node with a current distance of 0 and the rest with infinity.
Set the non-visited node with the smallest current distance as the current node.
For each neighbor, N of the current node adds the current distance of the adjacent node with the weight
of the edge connecting 0->1. If it is smaller than the current distance of Node, set it as the new current
distance of N.
Mark the current node 1 as visited.
Go to step 2 if there are any nodes are unvisited.
How does Dijkstra’s Algorithm works?
Let’s see how Dijkstra’s Algorithm works with an example given below:
Dijkstra’s Algorithm will generate the shortest path from Node 0 to all other Nodes in the graph.
Consider the below graph:

Dijkstra’s Algorithm
The algorithm will generate the shortest path from node 0 to all the other nodes in the graph.
For this graph, we will assume that the weight of the edges represents the distance between two
nodes.
As, we can see we have the shortest path from,
Node 0 to Node 1, from
Node 0 to Node 2, from
Node 0 to Node 3, from
Node 0 to Node 4, from
Node 0 to Node 6.
Initially we have a set of resources given below :
 The Distance from the source node to itself is 0. In this example the source node is 0.
 The distance from the source node to all other node is unknown so we mark all of them as
infinity.
Example: 0 -> 0, 1-> ∞,2-> ∞,3-> ∞,4-> ∞,5-> ∞,6-> ∞.
 we’ll also have an array of unvisited elements that will keep track of unvisited or unmarked
Nodes.
 Algorithm will complete when all the nodes marked as visited and the distance between them
added to the path. Unvisited Nodes:- 0 1 2 3 4 5 6.
Step 1: Start from Node 0 and mark Node as visited as you can check in below image visited Node is
marked red.

Step 2: Check for adjacent Nodes, Now we have to choices (Either choose Node1 with distance 2 or
either choose Node 2 with distance 6 ) and choose Node with minimum distance. In this step Node 1 is
Minimum distance adjacent Node, so marked it as visited and add up the distance.
Distance: Node 0 -> Node 1 = 2
Step 3: Then Move Forward and check for adjacent Node which is Node 3, so marked it as visited and
add up the distance, Now the distance will be:
Distance: Node 0 -> Node 1 -> Node 3 = 2 + 5 = 7

Step 4: Again we have two choices for adjacent Nodes (Either we can choose Node 4 with distance 10
or either we can choose Node 5 with distance 15) so choose Node with minimum distance. In this
step Node 4 is Minimum distance adjacent Node, so marked it as visited and add up the distance.
Distance: Node 0 -> Node 1 -> Node 3 -> Node 4 = 2 + 5 + 10 = 17
Step 5: Again, Move Forward and check for adjacent Node which is Node 6, so marked it as visited
and add up the distance, Now the distance will be:
Distance: Node 0 -> Node 1 -> Node 3 -> Node 4 -> Node 6 = 2 + 5 + 10 + 2 = 19

So, the Shortest Distance from the Source Vertex is 19 which is optimal o
MODULE V

LINEAR SEARCH
Linear Search is defined as a sequential search algorithm that starts at one end and goes through each
element of a list until the desired element is found, otherwise the search continues till the end of the data
set. In this article, we will learn about the basics of Linear Search Algorithm, Applications, Advantages,
Disadvantages, etc. to provide a deep understanding of Linear Search.

What is Linear Search?


Linear Search is a method for searching an element in a collection of elements. In Linear Search, each
element of the collection is visited one by one in a sequential fashion to find the desired element. Linear
Search is also known as Sequential Search.
Algorithm for Linear Search:
The algorithm for linear search can be broken down into the following steps:
 Start: Begin at the first element of the collection of elements.
 Compare: Compare the current element with the desired element.
 Found: If the current element is equal to the desired element, return true or index to the current
element.
 Move: Otherwise, move to the next element in the collection.
 Repeat: Repeat steps 2-4 until we have reached the end of collection.
 Not found: If the end of the collection is reached without finding the desired element, return that
the desired element is not in the array.
How Does Linear Search Algorithm Work?
In Linear Search Algorithm,
Every element is considered as a potential match for the key and checked for the same.
If any element is found equal to the key, the search is successful and the index of that element is returned.
If no element is found equal to the key, the search yields “No match found”.
For example: Consider the array arr[] = {10, 50, 30, 70, 80, 20, 90, 40} and key = 30
Step 1: Start from the first element (index 0) and compare key with each element (arr[i]).
Comparing key with first element arr[0]. SInce not equal, the iterator moves to the next element as a
potential match.

Comparing key with next element arr[1]. SInce not equal, the iterator moves to the next element as a
potential match.

Step 2: Now when comparing arr[2] with key, the value matches. So the Linear Search Algorithm will
yield a successful message and return the index of the element when key is found (here 2).

// C code to linearly search x in arr[].


#include <stdio.h>
int search(int arr[], int N, int x)
{
for (int i = 0; i < N; i++)
if (arr[i] == x)
return i;
return -1;
}
// Driver code
int main(void)
{
int arr[] = { 2, 3, 4, 10, 40 };
int x = 10;
int N = sizeof(arr) / sizeof(arr[0]);
// Function call
int result = search(arr, N, x);
(result == -1)
? printf("Element is not present in array")
: printf("Element is present at index %d", result);
return 0;
}
Applications of Linear Search:
 Unsorted Lists: When we have an unsorted array or list, linear search is most commonly used to
find any element in the collection.
 Small Data Sets: Linear Search is preferred over binary search when we have small data sets with
 Searching Linked Lists: In linked list implementations, linear search is commonly used to find
elements within the list. Each node is checked sequentially until the desired element is found.
 Simple Implementation: Linear Search is much easier to understand and implement as compared
to Binary Search or Ternary Search.
Advantages of Linear Search:
 Linear search can be used irrespective of whether the array is sorted or not. It can be used on arrays
of any data type.
 Does not require any additional memory.
 It is a well-suited algorithm for small datasets.
Disadvantages of Linear Search:
 Linear search has a time complexity of O(N), which in turn makes it slow for large datasets.
 Not suitable for large arrays.
When to use Linear Search?
 When we are dealing with a small dataset.
 When you are searching for a dataset stored in contiguous memory.
What is Binary Search Algorithm?
Binary search is a search algorithm used to find the position of a target value within a sorted array. It
works by repeatedly dividing the search interval in half until the target value is found or the interval is
empty. The search interval is halved by comparing the target element with the middle value of the search
space.
Conditions to apply Binary Search Algorithm in a Data Structure:
To apply Binary Search algorithm:
The data structure must be sorted.
Access to any element of the data structure takes constant time.
Binary Search Algorithm:
Divide the search space into two halves by finding the middle index “mid”.

Compare the middle element of the search space with the key.

 If the key is found at middle element, the process is terminated.


 If the key is not found at middle element, choose which half will be used as the next search space.
 If the key is smaller than the middle element, then the left side is used for next search.
 If the key is larger than the middle element, then the right side is used for next search.
 This process is continued until the key is found or the total search space is exhausted.
How does Binary Search Algorithm work?
To understand the working of binary search, consider the following illustration:
Consider an array arr[] = {2, 5, 8, 12, 16, 23, 38, 56, 72, 91}, and the target = 23.
First Step: Calculate the mid and compare the mid element with the key. If the key is less than mid
element, move to left and if it is greater than the mid then move search space to the right.
Key (i.e., 23) is greater than current mid element (i.e., 16). The search space moves to the right.
Key is less than the current mid 56. The search space moves to the left.

Second Step: If the key matches the value of the mid element, the element is found and stop search.

How to Implement Binary Search Algorithm?


The Binary Search Algorithm can be implemented in the following two ways

 Iterative Binary Search Algorithm


 Recursive Binary Search Algorithm
Applications of Binary Search Algorithm:
 Binary search can be used as a building block for more complex algorithms used in machine
learning, such as algorithms for training neural networks or finding the optimal hyperparameters
for a model.
 It can be used for searching in computer graphics such as algorithms for ray tracing or texture
mapping.
 It can be used for searching a database.
Advantages of Binary Search:
 Binary search is faster than linear search, especially for large arrays.
 More efficient than other searching algorithms with a similar time complexity, such as
interpolation search or exponential search.
 Binary search is well-suited for searching large datasets that are stored in external memory, such as
on a hard drive or in the cloud.
Disadvantages of Binary Search:
 The array should be sorted.
 Binary search requires that the data structure being searched be stored in contiguous memory
locations.
 Binary search requires that the elements of the array be comparable, meaning that they must be
able to be ordered.

HASHING
Hashing in Data Structures refers to the process of transforming a given key to another value. It involves
mapping data to a specific index in a hash table using a hash function that enables fast retrieval of
information based on its key. The transformation of a key to the corresponding value is done using a Hash
Function and the value obtained from the hash function is called Hash Code .
Components of Hashing
There are majorly three components of hashing:

 Key: A Key can be anything string or integer which is fed as input in the hash function the
technique that determines an index or location for storage of an item in a data structure.
 Hash Function: The hash function receives the input key and returns the index of an element in
an array called a hash table. The index is known as the hash index .
 Hash Table: Hash table is a data structure that maps keys to values using a special function called
a hash function. Hash stores the data in an associative manner in an array where each data value
has its own unique index.
How does Hashing work?
Suppose we have a set of strings {“ab”, “cd”, “efg”} and we would like to store it in a table.
Our main objective here is to search or update the values stored in the table quickly in O(1) time and we
are not concerned about the ordering of strings in the table. So the given set of strings can act as a key and
the string itself will act as the value of the string but how to store the value corresponding to the key?

 Step 1: We know that hash functions (which is some mathematical formula) are used to calculate
the hash value which acts as the index of the data structure where the value will be stored.
 Step 2: So, let’s assign
o “a” = 1,
o “b”=2, .. etc, to all alphabetical characters.
 Step 3: Therefore, the numerical value by summation of all characters of the string:
 ab” = 1 + 2 = 3,
 “cd” = 3 + 4 = 7 ,
 “efg” = 5 + 6 + 7 = 18
 Step 4: Now, assume that we have a table of size 7 to store these strings. The hash function that is
used here is the sum of the characters in key mod Table size . We can compute the location of the
string in the array by taking the sum(string) mod 7 .
 Step 5: So we will then store
 “ab” in 3 mod 7 = 3,
 “cd” in 7 mod 7 = 0, and
 “efg” in 18 mod 7 = 4.
The above technique enables us to calculate the location of a given string by using a simple hash function
and rapidly find the value that is stored in that location. Therefore the idea of hashing seems like a great
way to store (key, value) pairs of the data in a table.

What is Collision?
Collision in Hashing occurs when two different keys map to the same hash value. Hash collisions can be
intentionally created for many hash algorithms. The probability of a hash collision depends on the size of
the algorithm, the distribution of hash values and the efficiency of Hash function.
The hashing process generates a small number for a big key, so there is a possibility that two keys could
produce the same value. The situation where the newly inserted key maps to an already occupied, and it
must be handled using some collision handling technology.
How to handle Collisions?
There are mainly two methods to handle collision:

 Separate Chaining
 Open Addressing

2) Separate Chaining
The idea is to make each cell of the hash table point to a linked list of records that have the same hash
function value. Chaining is simple but requires additional memory outside the table.
Example: We have given a hash function and we have to insert some elements in the hash table using a
separate chaining method for collision resolution technique.
Hash function = key % 5,
Elements = 12, 15, 22, 25 and 37.
Hence In this way, the separate chaining method is used as the collision resolution technique.
2) Open Addressing
In open addressing, all elements are stored in the hash table itself. Each table entry contains either a record
or NIL. When searching for an element, we examine the table slots one by one until the desired element is
found or it is clear that the element is not in the table.
2.a) Linear Probing
 In linear probing, the hash table is searched sequentially that starts from the original location of the
hash. If in case the location that we get is already occupied, then we check for the next location.
 Algorithm:
Calculate the hash key. i.e. key = data % size
Check, if hashTable[key] is empty
store the value directly by hashTable[key] = data
If the hash index already has some value then
check for next index using key = (key+1) % size
Check, if the next index is available hashTable[key] then store the value. Otherwise try for
next index.
Do the above process till we find the space.
 Example: Let us consider a simple hash function as “key mod 5” and a sequence of keys that are
to be inserted are 50, 70, 76, 85, 93.
2.b) Quadratic Probing
Quadratic probing is an open addressing scheme in computer programming for resolving hash collisions in
hash tables. Quadratic probing operates by taking the original hash index and adding successive values of
an arbitrary quadratic polynomial until an open slot is found.
An example sequence using quadratic probing is:
H + 1 2 , H + 2 2 , H + 3 2 , H + 4 2 …………………. H + k 2
This method is also known as the mid-square method because in this method we look for i 2 ‘th probe
(slot) in i’th iteration and the value of i = 0, 1, . . . n – 1. We always start from the original hash location. If
only the location is occupied then we check the other slots.
Example: Let us consider table Size = 7, hash function as Hash(x) = x % 7 and collision resolution
strategy to be f(i) = i 2 . Insert = 22, 30, and 50
Doubling hashing
Doubling hashing is a collision resolution technique used in open addressing schemes for hash tables. It
combines two hash functions to calculate the probe sequence, which helps in distributing the keys more
uniformly across the table, reducing clustering and improving the performance of the hash table.
How Doubling Hashing Works
 Primary Hash Function: The first hash function, ℎ1(𝑘)h1(k), is used to determine the initial
position of the key 𝑘k in the hash table.
 Secondary Hash Function: The second hash function, ℎ2(𝑘)h2(k), is used to determine the step
size for probing in case of a collision.
Probe Sequence: The probe sequence is calculated as follows:
index𝑖=(ℎ1(𝑘)+𝑖⋅ℎ2(𝑘))mod 𝑚indexi=(h1(k)+i⋅h2(k))modm
where 𝑖i is the probe number (starting from 0) and 𝑚m is the size of the hash table.
Example
Let's consider a simple example:
 Hash table size, 𝑚=10m=10
 Primary hash function, ℎ1(𝑘)=𝑘mod 10h1(k)=kmod10
 Secondary hash function, ℎ2(𝑘)=1+(𝑘mod 9)h2(k)=1+(kmod9)
To insert keys into the hash table using doubling hashing:
 Insert key 19:
ℎ1(19)=19mod 10=9h1(19)=19mod10=9
ℎ2(19)=1+(19mod 9)=1+1=2h2(19)=1+(19mod9)=1+1=2
Initial index: 9 (no collision)
 Insert key 28:
ℎ1(28)=28mod 10=8h1(28)=28mod10=8
ℎ2(28)=1+(28mod 9)=1+1=2h2(28)=1+(28mod9)=1+1=2
Initial index: 8 (no collision)
 Insert key 18 (causes collision with key 28):
ℎ1(18)=18mod 10=8h1(18)=18mod10=8
ℎ2(18)=1+(18mod 9)=1+0=1h2(18)=1+(18mod9)=1+0=1
 Initial index: 8 (collision)
Next index: (8+1⋅1)mod 10=9(8+1⋅1)mod10=9 (collision with key 19)
Next index: (8+2⋅1)mod 10=0(8+2⋅1)mod10=0 (no collision, place key 18 here)
By using both the primary and secondary hash functions, doubling hashing effectively resolves collisions
and distributes keys more uniformly, improving the overall efficiency of the hash table.
Insertion sort
 Insertion sort is a simple sorting algorithm that works by building a sorted array one element at a
time. It is considered an “in-place” sorting algorithm, meaning it doesn’t require any additional
memory space beyond the original array.
To achieve insertion sort, follow these steps:
 We have to start with second element of the array as first element in the array is assumed to be
sorted.
 Compare second element with the first element and check if the second element is smaller then
swap them.
 Move to the third element and compare it with the second element, then the first element and swap
as necessary to put it in the correct position among the first three elements.
 Continue this process, comparing each element with the ones before it and swapping as needed to
place it in the correct position among the sorted elements.
 Repeat until the entire array is sorted.
Working of Insertion Sort Algorithm:
Consider an array having elements: {23, 1, 10, 5, 2}

 First Pass:
o Current element is 23
o The first element in the array is assumed to be sorted.
o The sorted part until 0th index is : [23]
 Second Pass:
o Compare 1 with 23 (current element with the sorted part).
o Since 1 is smaller, insert 1 before 23.
o The sorted part until 1st index is: [1, 23]
 Third Pass:
o Compare 10 with 1 and 23 (current element with the sorted part).
o Since 10 is greater than 1 and smaller than 23, insert 10 between 1 and 23.
o The sorted part until 2nd index is: [1, 10, 23]
 Fourth Pass:
o Compare 5 with 1, 10, and 23 (current element with the sorted part).
o Since 5 is greater than 1 and smaller than 10, insert 5 between 1 and 10.
o The sorted part until 3rd index is: [1, 5, 10, 23]
 Fifth Pass:
o Compare 2 with 1, 5, 10, and 23 (current element with the sorted part).
o Since 2 is greater than 1 and smaller than 5 insert 2 between 1 and 5.
o The sorted part until 4th index is: [1, 2, 5, 10, 23]
Final Array:
The sorted array is: [1, 2, 5, 10, 23]
// C program for insertion sort
#include <math.h>
#include <stdio.h>

/* Function to sort an array using insertion sort*/


void insertionSort(int arr[], int n)
{
int i, key, j;
for (i = 1; i < n; i++) {
key = arr[i];
j = i - 1;

/* Move elements of arr[0..i-1], that are


greater than key, to one position ahead
of their current position */
while (j >= 0 && arr[j] > key) {
arr[j + 1] = arr[j];
j = j - 1;
}
arr[j + 1] = key;
}
}
// A utility function to print an array of size n
void printArray(int arr[], int n)
{
int i;
for (i = 0; i < n; i++)
printf("%d ", arr[i]);
printf("\n");
}

/* Driver program to test insertion sort */


int main()
{
int arr[] = { 12, 11, 13, 5, 6 };
int n = sizeof(arr) / sizeof(arr[0]);

insertionSort(arr, n);
printArray(arr, n);

return 0;
}
Advantages of Insertion Sort:
 Simple and easy to implement.
 Stable sorting algorithm.
 Efficient for small lists and nearly sorted lists.
 Space-efficient.
Disadvantages of Insertion Sort:
 Inefficient for large lists.
 Not as efficient as other sorting algorithms (e.g., merge sort, quick sort) for most cases.
Applications of Insertion Sort:
 Insertion sort is commonly used in situations where:
 The list is small or nearly sorted.
 Simplicity and stability are important.
Selection sort
Selection sort is a simple and efficient sorting algorithm that works by repeatedly selecting the smallest
(or largest) element from the unsorted portion of the list and moving it to the sorted portion of the list.
The algorithm repeatedly selects the smallest (or largest) element from the unsorted portion of the list and
swaps it with the first element of the unsorted part. This process is repeated for the remaining unsorted
portion until the entire list is sorted.
How does Selection Sort Algorithm work?
Lets consider the following array as an example: arr[] = {64, 25, 12, 22, 11}
First pass:
For the first position in the sorted array, the whole array is traversed from index 0 to 4 sequentially. The
first position where 64 is stored presently, after traversing whole array it is clear that 11 is the lowest
value.
Thus, replace 64 with 11. After one iteration 11, which happens to be the least value in the array, tends to
appear in the first position of the sorted list.

Second Pass:
For the second position, where 25 is present, again traverse the rest of the array in a sequential manner.
After traversing, we found that 12 is the second lowest value in the array and it should appear at the
second place in the array, thus swap these values.
Third Pass:
Now, for third place, where 25 is present again traverse the rest of the array and find the third least value
present in the array.
While traversing, 22 came out to be the third least value and it should appear at the third place in the array,
thus swap 22 with element present at third position.

Fourth pass:
Similarly, for fourth position traverse the rest of the array and find the fourth least element in the array
As 25 is the 4th lowest value hence, it will place at the fourth position.

Fifth Pass:
At last the largest value present in the array automatically get placed at the last position in the array
The resulted array is the sorted array.
// C program for implementation of selection sort
#include <stdio.h>

void swap(int *xp, int *yp)


{
int temp = *xp;
*xp = *yp;
*yp = temp;
}

void selectionSort(int arr[], int n)


{
int i, j, min_idx;

// One by one move boundary of unsorted subarray


for (i = 0; i < n-1; i++)
{
// Find the minimum element in unsorted array
min_idx = i;
for (j = i+1; j < n; j++)
if (arr[j] < arr[min_idx])
min_idx = j;

// Swap the found minimum element with the first element


if(min_idx != i)
swap(&arr[min_idx], &arr[i]);
}
}

/* Function to print an array */


void printArray(int arr[], int size)
{
int i;
for (i=0; i < size; i++)
printf("%d ", arr[i]);
printf("\n");
}

// Driver program to test above functions


int main()
{
int arr[] = {64, 25, 12, 22, 11};
int n = sizeof(arr)/sizeof(arr[0]);
selectionSort(arr, n);
printf("Sorted array: \n");
printArray(arr, n);
return 0;
}
Advantages of Selection Sort Algorithm
 Simple and easy to understand.
 Works well with small datasets.
Disadvantages of the Selection Sort Algorithm
 Selection sort has a time complexity of O(n^2) in the worst and average case.
 Does not work well on large datasets.
 Does not preserve the relative order of items with equal keys which means it is not stable.

QuickSort
QuickSort is a sorting algorithm based on the Divide and Conquer algorithm that picks an element as a
pivot and partitions the given array around the picked pivot by placing the pivot in its correct position in
the sorted array.
How does QuickSort work?
 The key process in quickSort is a partition(). The target of partitions is to place the pivot (any
element can be chosen to be a pivot) at its correct position in the sorted array and put all smaller
elements to the leftof the pivot, and all greater elements to the right of the pivot.
 Partition is done recursively on each side of the pivot after the pivot is placed in its correct
position and this finally sorts the array.

There are many different choices for picking pivots.


 Always pick the first element as a pivot.
 Always pick the last element as a pivot (implemented below)
 Pick a random element as a pivot.
 Pick the middle as the pivot.

Partition Algorithm:

The logic is simple, we start from the leftmost element and keep track of the index of smaller (or equal)
elements as i. While traversing, if we find a smaller element, we swap the current element with arr[i].
Otherwise, we ignore the current element.
Let us understand the working of partition and the Quick Sort algorithm with the help of the following
example:
Consider: arr[] = {10, 80, 30, 90, 40}.
 Compare 10 with the pivot and as it is less than pivot arrange it accrodingly.

 Compare 80 with the pivot. It is greater than pivot.


 Compare 30 with pivot. It is less than pivot so arrange it accordingly.

 Compare 90 with the pivot. It is greater than the pivot.

 Arrange the pivot in its correct position.


Illustration of Quicksort:

As the partition process is done recursively, it keeps on putting the pivot in its actual position in the
sorted array. Repeatedly putting pivots in their actual position makes the array sorted.
Follow the below images to understand how the recursive implementation of the partition algorithm
helps to sort the array.
 Initial partition on the main array:

 Partitioning of the subarrays:

// C program for QuickSort


#include <stdio.h>

// Utility function to swap tp integers


void swap(int* p1, int* p2)
{
int temp;
temp = *p1;
*p1 = *p2;
*p2 = temp;
}
int partition(int arr[], int low, int high)
{
// choose the pivot
int pivot = arr[high];

// Index of smaller element and Indicate


// the right position of pivot found so far
int i = (low - 1);

for (int j = low; j <= high - 1; j++) {


// If current element is smaller than the pivot
if (arr[j] < pivot) {
// Increment index of smaller element
i++;
swap(&arr[i], &arr[j]);
}
}
swap(&arr[i + 1], &arr[high]);
return (i + 1);
}

// The Quicksort function Implement


void quickSort(int arr[], int low, int high)
{
// when low is less than high
if (low < high) {
// pi is the partition return index of pivot
int pi = partition(arr, low, high);
// Recursion Call
// smaller element than pivot goes left AND // higher element goes right
quickSort(arr, low, pi - 1);
quickSort(arr, pi + 1, high);
}
}
int main()
{
int arr[] = { 10, 7, 8, 9, 1, 5 };
int n = sizeof(arr) / sizeof(arr[0]);

// Function call
quickSort(arr, 0, n - 1);

// Print the sorted array


printf("Sorted Array\n");
for (int i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
return 0;
}
// This Code is Contributed By Diwakar Jha
Advantages of Quick Sort:
 It is a divide-and-conquer algorithm that makes it easier to solve problems.
 It is efficient on large data sets.
 It has a low overhead, as it only requires a small amount of memory to function.
Disadvantages of Quick Sort:
 It has a worst-case time complexity of O(N2), which occurs when the pivot is chosen poorly.
 It is not a good choice for small data sets.
 It is not a stable sort, meaning that if two elements have the same key, their relative order
will not be preserved in the sorted output in case of quick sort, because here we are swapping
elements according to the pivot’s position (without considering their original positions).
 Merge sort is a sorting algorithm that follows the divide-and-conquer approach. It works by
recursively dividing the input array into smaller subarrays and sorting those subarrays then
merging them back together to obtain the sorted array.
 In simple terms, we can say that the process of merge sort is to divide the array into two
halves, sort each half, and then merge the sorted halves back together. This process is
 repeated until the entire array is sorted.
MERGE SORT

How does Merge Sort work?


Merge sort is a popular sorting algorithm known for its efficiency and stability. It follows the divide-
and-conquer approach to sort a given array of elements.
Here’s a step-by-step explanation of how merge sort works:
1. Divide: Divide the list or array recursively into two halves until it can no more be divided.
2. Conquer: Each subarray is sorted individually using the merge sort algorithm.
3. Merge: The sorted subarrays are merged back together in sorted order. The process continues
until all elements from both subarrays have been merged.

llustration of Merge Sort:

Let’s sort the array or list [38, 27, 43, 10] using Merge Sort
Let’s look at the working of above example:
Divide:
 [38, 27, 43, 10] is divided into [38, 27] and [43, 10].
 [38, 27] is divided into [38] and [27].
 [43, 10] is divided into [43] and [10].
Conquer:
 [38] is already sorted.
 [27] is already sorted.
 [43] is already sorted.
 [10] is already sorted.
Merge:
 Merge [38] and [27] to get [27, 38].
 Merge [43] and [10] to get [10,43].
 Merge [27, 38] and [10,43] to get the final sorted
 list [10, 27, 38, 43]
Therefore, the sorted list is [10, 27, 38, 43].
// C program for Merge Sort
#include <stdio.h>
#include <stdlib.h>
// Merges two subarrays of arr[].
// First subarray is arr[l..m]
// Second subarray is arr[m+1..r]
void merge(int arr[], int l, int m, int r)
{
int i, j, k;
int n1 = m - l + 1;
int n2 = r - m;
// Create temp arrays
int L[n1], R[n2];
// Copy data to temp arrays L[] and R[]
for (i = 0; i < n1; i++)
L[i] = arr[l + i];
for (j = 0; j < n2; j++)
R[j] = arr[m + 1 + j];
// Merge the temp arrays back into arr[l..r
i = 0;
j = 0;
k = l;
while (i < n1 && j < n2) {
if (L[i] <= R[j]) {
arr[k] = L[i];
i++;
}
else {
arr[k] = R[j];
j++;
}
k++;
}
// Copy the remaining elements of L[],
// if there are any
while (i < n1) {
arr[k] = L[i];
i++;
k++;
}
// Copy the remaining elements of R[],
// if there are any
while (j < n2) {
arr[k] = R[j];
j++;
k++;
}
}
// l is for left index and r is right index of the
// sub-array of arr to be sorted
void mergeSort(int arr[], int l, int r)
{
if (l < r) {
int m = l + (r - l) / 2;

// Sort first and second halves


mergeSort(arr, l, m);
mergeSort(arr, m + 1, r);

merge(arr, l, m, r);
}
}

// Function to print an array


void printArray(int A[], int size)
{
int i;
for (i = 0; i < size; i++)
printf("%d ", A[i]);
printf("\n");
}
// Driver code
int main()
{
int arr[] = { 12, 11, 13, 5, 6, 7 };
int arr_size = sizeof(arr) / sizeof(arr[0]);

printf("Given array is \n");


printArray(arr, arr_size);

mergeSort(arr, 0, arr_size - 1);

printf("\nSorted array is \n");


printArray(arr, arr_size);
return 0;
}
Advantages of Merge Sort:
 Stability: Merge sort is a stable sorting algorithm, which means it maintains the relative
order of equal elements in the input array.
 Guaranteed worst-case performance: Merge sort has a worst-case time complexity of O(N
logN), which means it performs well even on large datasets.
 Simple to implement: The divide-and-conquer approach is straightforward.
Disadvantage of Merge Sort:
 Space complexity: Merge sort requires additional memory to store the merged sub-arrays
during the sorting process.
 Not in-place: Merge sort is not an in-place sorting algorithm, which means it requires
additional memory to store the sorted data. This can be a disadvantage in applications where
memory usage is a concern.
Applications of Merge Sort:
 Sorting large datasets
 External sorting (when the dataset is too large to fit in memory)
 Inversion counting (counting the number of inversions in an array)
 Finding the median of an array

Exchange sort
Exchange sort is an algorithm used to sort in ascending as well as descending order. It compares
the first element with every element if any element seems out of order it swaps.
Example:
Input: arr[] = {5, 1, 4, 2, 8}
Output: {1, 2, 4, 5, 8}
Explanation: Working of exchange sort:
 1st Pass:
Exchange sort starts with the very first elements,
 comparing with other elements to check which one is greater.
( 5 1 4 2 8 ) –> ( 1 5 4 2 8 ).
Here, the algorithm compares the first two elements and swaps since 5 > 1.
No swap since none of the elements is smaller than 1 so after 1st iteration (1 5 4 2 8)
 2nd Pass:
(1 5 4 2 8 ) –> ( 1 4 5 2 8 ), since 4 < 5
( 1 4 5 2 8 ) –> ( 1 2 5 4 8 ), since 2 < 4
( 1 2 5 4 8 ) No change since in this there is no other element smaller than 2
 3rd Pass:
(1 2 5 4 8 ) -> (1 2 4 5 8 ), since 4 < 5
after completion of the iteration, we found array is sorted
 After completing the iteration it will come out of the loop, Therefore array is sorted.
To sort in Ascending order:
procedure ExchangeSort(num: list of sortable items)
n = length(A)
// outer loop
for i = 1 to n – 2 do
// inner loop
for j = i + 1 to n-1 do
if num[i] > num[j] do
swap(num[i], num[j])
end if
end for
end for
end procedure
Time Complexity: O(N^2)
Auxiliary Space : O(1)
Advantages of using Exchange sort over other sorting methods:
 There are some situations where exchange sort may be preferable over other algorithms. For
example, exchange sort may be useful when sorting very small arrays or when sorting data
that is already mostly sorted. In these cases, the overhead of implementing a more complex
algorithm may not be worth the potential performance gains.
 Another advantage of the exchange sort is that it is stable, meaning that it preserves the
relative order of equal elements. This can be important in some applications, such as when
sorting records that contain multiple fields.

What is a Hash Function?


A hash function is a function that takes an input (or ‘message’) and returns a fixed-size string of bytes.
The output, typically a number, is called the hash code or hash value. The main purpose of a hash
function is to efficiently map data of arbitrary size to fixed-size values, which are often used as indexes
in hash tables.

Key Properties of Hash Functions

 Deterministic: A hash function must consistently produce the same output for the same
input.
 Fixed Output Size: The output of a hash function should have a fixed size, regardless of the
size of the input.
 Efficiency: The hash function should be able to process input quickly.
 Uniformity: The hash function should distribute the hash values uniformly across the output
space to avoid clustering.
 Pre-image Resistance: It should be computationally infeasible to reverse the hash function,
i.e., to find the
 original input given a hash value.
 Collision Resistance: It should be difficult to find two different inputs that produce the same
hash value.
 Avalanche Effect: A small change in the input should produce a significantly different hash
value.
Applications of Hash Functions
 Hash Tables: The most common use of hash functions in DSA is in hash tables, which
provide an efficient way to store and retrieve data.
 Data Integrity: Hash functions are used to ensure the integrity of data by generating
checksums.
 Cryptography: In cryptographic applications, hash functions are used to create secure hash
algorithms like SHA-256.
 Data Structures: Hash functions are utilized in various data structures such as Bloom filters
and hash sets.
Types of Hash Functions
There are many hash functions that use numeric or alphanumeric keys. This article focuses on
discussing different hash functions:
1. Division Method.
2. Multiplication Method
3. Mid-Square Method
4. Folding Method
5. Cryptographic Hash Functions
6. Universal Hashing
7. Perfect Hashing
Let’s begin discussing these methods in detail.
1. Division Method
The division method involves dividing the key by a prime number and using the remainder as the hash
value.
h(k)=k mod m
Where k is the key and 𝑚m is a prime number.
Advantages:
 Simple to implement.
 Works well when 𝑚m is a prime number.
Disadvantages:
 Poor distribution if 𝑚m is not chosen wisely.
2. Multiplication Method
In the multiplication method, a constant 𝐴A (0 < A < 1) is used to multiply the key. The fractional part
of the product is then multiplied by 𝑚m to get the hash value.
h(k)=⌊m(kAmod1)⌋
Where ⌊ ⌋ denotes the floor function.
Advantages:
 Less sensitive to the choice of 𝑚m.
Disadvantages:
 More complex than the division method.
3. Mid-Square Method
In the mid-square method, the key is squared, and the middle digits of the result are taken as the hash
value.
Steps:
1. Square the key.
2. Extract the middle digits of the squared value.
Advantages:
 Produces a good distribution of hash values.
Disadvantages:
 May require more computational effort.
4. Folding Method
The folding method involves dividing the key into equal parts, summing the parts, and then taking the
modulo with respect to 𝑚m.
Steps:
1. Divide the key into parts.
2. Sum the parts.
3. Take the modulo 𝑚m of the sum.
Advantages:
 Simple and easy to implement.
Disadvantages:
 Depends on the choice of partitioning scheme.
5. Cryptographic Hash Functions
Cryptographic hash functions are designed to be secure and are used in cryptography. Examples include
MD5, SHA-1, and SHA-256.
Characteristics:
 Pre-image resistance.
 Second pre-image resistance.
 Collision resistance.
Advantages:
 High security.
Disadvantages:
 Computationally intensive.
6. Universal Hashing
Universal hashing uses a family of hash functions to minimize the chance of collision for any given set
of inputs.
h(k)=((a⋅k+b)modp)modm
Where a and b are randomly chosen constants, p is a prime number greater than m, and k is the key.
Advantages:
 Reduces the probability of collisions.
Disadvantages:
 Requires more computation and storage.
7. Perfect Hashing
Perfect hashing aims to create a collision-free hash function for a static set of keys. It guarantees that no
two keys will hash to the same value.
Types:
 Minimal Perfect Hashing: Ensures that the range of the hash function is equal to the number
of keys.
 Non-minimal Perfect Hashing: The range may be larger than the number of keys.
Advantages:
 No collisions.
Disadvantages:
 Complex to construct.

File structures

encompass various aspects of how data is physically stored, organized, and accessed on storage media.
This includes organizing records into blocks, different types of file organizations, and methods of indexing
and hashing. Here’s a detailed breakdown of each concept:

Physical Storage Media and File Organization

1. Physical Storage Media

 Hard Disk Drives (HDDs): Common storage medium with spinning disks and read/write heads.
Data is stored in tracks and sectors.
 Solid State Drives (SSDs): Uses flash memory with no moving parts, offering faster data access
and durability.
 Optical Discs: Includes CDs, DVDs, used for data distribution and archival storage.
 Magnetic Tapes: Used for backups and archival storage, sequential access only.
 Cloud Storage: Data stored on remote servers accessible via the internet.

2. File Organization

 Sequential File Organization: Records are stored one after another, in sequence.
 Direct or Hashed File Organization: Uses a hash function to compute the address of the data
record, allowing direct access.
 Indexed File Organization: Uses an index to keep track of the records, allowing efficient search
operations.
Organization of Records into Blocks

 Blocks: Smallest unit of data transfer between storage and memory. In HDDs, a typical block size
is 512 bytes or 4 KB.
 Blocking Factor: The number of records per block.
 Record Splitting: Dividing a record across multiple blocks.
 Fixed-Length vs. Variable-Length Records: Fixed-length records are easier to manage, while
variable-length records are more space-efficient.

Sequential Files

 Characteristics: Data is stored in a linear order, typically based on a key field. Sequential files are
efficient for batch processing but not for random access.
 Operations: Efficient for sequential access (reading from start to end) but slow for random access
since it might require scanning the entire file.

Indexing and Hashing

1. Indexing

 Primary Index: An index on the primary key of a file, which uniquely identifies records.
Typically, the primary index is dense (every search key in the data file has an index entry).
 Secondary Index: An index on non-primary keys, which might not be unique. Secondary indices
can be sparse or dense, and are used to speed up queries that search for records based on non-
primary key attributes.
 Clustered Index: Data records are sorted on the index key, enhancing performance for range
queries.
 Non-clustered Index: The index is separate from the data records, typically contains pointers to
the actual data locations.

2. Hashing

 Static Hashing: Uses a fixed hash table size. Can suffer from overflow problems handled by
overflow chaining or open addressing.
 Dynamic Hashing: Adjusts the hash table size dynamically (e.g., extendible hashing, linear
hashing).
 Collision Resolution: Techniques like chaining (using linked lists) or open addressing (probing for
the next available slot).

Index Files

 Structure: Contains index entries (key-pointer pairs) that map search keys to data record locations.
 Types: Dense index (index entry for every search key) and sparse index (index entry for some
search keys).
Comparisons: Indexing vs. Hashing

Indexing

 Advantages:
o Efficient for range queries.
o Can handle ordered data.
o Suitable for both equality and range searches.
 Disadvantages:
o Requires additional storage for index structures.
o Index maintenance can be complex with frequent updates.

Hashing

 Advantages:
o Direct access to records, providing fast search, insert, and delete operations.
o Efficient for equality searches.
 Disadvantages:
o Inefficient for range queries.
o Collision handling adds complexity.
o Hash table size management (static vs. dynamic) can be challenging.

Data structure for symbol table


o A compiler contains two type of symbol table: global symbol table and scope symbol table.
o Global symbol table can be accessed by all the procedures and scope symbol table.

The above grammar can be represented in a hierarchical data structure of symbol tables:
The global symbol table contains one global variable and two procedure names. The name mentioned in the
sum_num table is not available for sum_id and its child tables.

Data structure hierarchy of symbol table is stored in the semantic analyzer. If you want to search the name
in the symbol table then you can search it using the following algorithm:
o First a symbol is searched in the current symbol table.
o If the name is found then search is completed else the name will be searched in the symbol table of
parent until,
o The name is found or global symbol is searched

You might also like