Data Structures and Algorithm

You might also like

Download as docx, pdf, or txt
Download as docx, pdf, or txt
You are on page 1of 44

S

Swami Keshvanand Institute of Technology,


Management & Gramothan, Jaipur

In-house Training
On

Data Structure & Algorithms


Session 2023-24

Trainer Name: Dr. Mithlesh Arya

Submitted by:

Harshini Sharma
22ESKCA044
Computer Science(AI)
ACKNOWLEDGEMENT

I would like to convey my heartfelt appreciation to the numerous individuals who have played a
vital role in the development of this project and have left an indelible mark on my academic
journey. Their influence, guidance, and support have been instrumental throughout my study.

I am deeply grateful to Dr. Mithlesh Arya, the esteemed Professor and Head of the Department
of Computer Science and Engineering at SKIT, Jaipur, for affording me the valuable opportunity
to partake in the In-house Internship program. It was under their guidance that I was able to
embark on this educational journey.

I extend my sincere thanks to Mr. Manish Bhardwaj, the Coordinator for the II-year In-house
Internship program for the academic year 2023-24. His unwavering support, cooperation, and
motivation have been a constant source of inspiration and have offered me the encouragement
and blessings necessary to push forward.

My appreciation also goes out to Mr. Pawan Kumar Patidar, whose assistance was
instrumental in my understanding and learning of Data Structures and Algorithms.

Lastly, I would like to express my heartfelt gratitude to all the dedicated faculty and staff
members of both the Department of Computer Science and Engineering and the Department of
Information Technology. Their indirect and direct support and contributions have played a
significant role in my academic journey, and for that, I am truly thankful.

Page | 1
Table of Contents
1. Introduction
1.1 Defination Of Data Structures
1.2 Classification
1.3 Performing Arthmetic Operator

2. Stack
2.1 Defination of Stack
2.2 Advantage of Stack
2.3 Disadvantage of Stack
2.4 Application of Stack
2.5 Implementation of Stack

3. Searching
3.1 Defination of Searching
3.2 Advantage of Searching
3.3 Disadvantage of Searching
3.4 Application of Searching
3.5 Implementation Searching

4. Queue
4.1 Defination of Queue
4.2 Advantage of Queue
4.3 Disadvantage of Queue
4.4 Application of Queue
4.5 Implementation of Queue
4.6 Implementation of Circular Queue

5. Sorting
5.1 Defination of Sorting
5.2 Bubble Sort
5.3 Insertion Sort
5.4 Quick Sort
5.5 Selection sort

6. Linked List
6.1 Singly Linked List
6.2 Doubly Linked List
6.3 Circular Linked List
6.4 Advantage
6.5 Disadvantage

Page | 2
CHAPTER 1
DATA STRUCTURE:

As the name indicates, Data Structure is used for organizing the data in memory. There are
various ways of organizing the data in the memory, for eg. array, list, stack, queue, and many
more.

The data structure isn’t a programming language like C, C++ Java , etc. It is a set of algorithms
that can be used in any programming language to organize the data in the memory.

Here is the list of courses to learn data structure for a particular programming language

‘n’ number of algorithms were proposed to organize the data in memory. These algorithms are
referred to as Abstract data types. Abstract data types are nothing but a set of rules.

A data structure is not only used for organizing the data. It is also used for processing, retrieving,
and storing data. There are different basic and advanced types of data structures that are used in
almost every program or software system that has been developed. So we must have good
knowledge about data structures.

Trees are hierarchical data structures with a root element and various sub-elements connected in
a branching manner. They have applications in areas like hierarchical data representation and
search algorithms. Binary trees, where each node has at most two child nodes, are particularly
important and form the basis for more advanced structures like binary search trees and AVL
trees.

Understanding DSA is crucial for writing efficient, scalable, and maintainable code. Efficient
algorithms and appropriate data structures are essential for solving real-world problems and
improving the performance of software applications. Whether working on software development,
artificial intelligence, or any other domain, a solid understanding of DSA is valuable.

In conclusion, Data Structures and Algorithms are the backbone of computer science and
programming. They empower developers to create robust, efficient solutions to a wide range of
problems, making them indispensable skills for anyone in the field of software development.

Page | 3
CLASSIFICATION OF DATA STRUCTURE

Figure 1.1 data structure

Primitive data structure


Primitive data structure is a data structure that can hold a single value in a specific location
whereas the non-linear data structure can hold multiple values either in a contiguous location or
random locations

The examples of primitive data structure are float, character, integer and pointer. The value to the
primitive data structure is provided by the programmer. The following are the four primitive data
structures:

o Integer: The integer data type contains the numeric values. It contains the whole
numbers that can be either negative or positive. When the range of integer data type is not
large enough then in that case, we can use long.
o Float: The float is a data type that can hold decimal values. When the precision of
decimal value increases then the Double data type is used.

Page | 4
o Boolean: It is a data type that can hold either a True or a False value. It is mainly used
for checking the condition.
o Character: It is a data type that can hold a single character value both uppercase and
lowercase such as 'A' or 'a'.

Non-primitive data structure


The non-primitive data structure is a kind of data structure that can hold multiple values either in
a contiguous or random location. The non-primitive data types are defined by the programmer.
The non-primitive data structure is further classified into two categories, i.e., linear and non-
linear data structure.

In case of linear data structure, the data is stored in a sequence, i.e., one data after another data.
When we access the data from the linear data structure, we just need to start from one place and
will find other data in a sequence.

The following are the types of linear data structure:

o Array: An array is a data structure that can hold the elements of same type. It cannot
contain the elements of different types like integer with character. The commonly used
operation in an array is insertion, deletion, traversing, searching.
o String: String is defined as an array of characters. The difference between the character
array and string is that the string data structure terminates with a 'NULL' character, and it
is denoted.
o Stack: Stack is a data structure that follows the principle LIFO (Last In First Out). All
the operations on the stack are performed from the top of the stack such as PUSH and
POP operation. The push operation is the process of inserting element into the stack
while the pop operation is the process of removing element from the stack. The stack data
structure can be implemented by using either array or linked list.
o Queue: Queue is a data structure that can be implemented by using array. The difference
between the stack and queue data structure is that the elements in the queue are inserted
from the rear end while the elements in the queue are removed from the front end.

Page | 5
Non-linear data structure: Data structures where data elements are not placed
sequentially or linearly are called non-linear data structures. In a non-linear data structure, we
can’t traverse all the elements in a single run only.

Tree:
A tree is a non-linear and hierarchical data structure where the elements are arranged in a tree-
like structure. In a tree, the topmost node is called the root node.

Graph:
A graph is a non-linear data structure that consists of vertices (or nodes) and edges. It consists
of a finite set of vertices and set of edges that connect a pair of nodes.Examples of on-linear
data structures are trees and graphs.

#include <stdio.h>
#include <stdlib.h>
int add(int,int);
int sub(int,int);
int mult(int,int);
void main(){
int a,b,c;
int ch;
int choice;
do
{
printf("1. Addition \n");
printf("2. Subtraction \n");
printf("3. Multiplication\n");

printf("Enter two numbers\n");


scanf("%d%d",&a,&b);

printf("Enter your choice");


scanf(" %d",&ch);

switch(ch){
case 1:
printf("Add=%d\n",add(a,b));
break;

case 2:

Page | 6
printf(" Sub =%d\n",sub(a,b));
break;
case 3:
printf("Mult=%d\n",mult(a,b));
break;
default:
printf("operation does not exist");
}

printf("Enter 1 to continue");
scanf("%d",&choice);
}
while(choice==1);
}

int add(int a,int b){


int c;
c= a+b;
return c;

}
int sub(int a, int b){
int c;
c= a-b;
return c;

}
int mult(int a, int b) {
int c;
c= a*b;
return c;
}

OUTPUT:
Enter an operation (+ for addition, - for subtraction, * for multiplication, q to quit): +
Enter the first number: 10
Enter the second number: 5

Page | 7
Result: 15
Enter 1 to continue1
Enter an operation (+ for addition, - for subtraction, * for multiplication, q to quit): -
Enter the first number: 8
Enter the second number: 3
Result: 5
Enter 1 to continue1
Enter an operation (+ for addition, - for subtraction, * for multiplication, q to quit): *
Enter the first number: 7
Enter the second number: 4
Result: 28
Enter 1 to continue0

Page | 8
CHAPTER 2

STACK

A stack is a linear data structure that follows the Last-In-First-Out (LIFO) principle. It's a
collection of elements with two primary operations:

Push: Adding an element to the top of the stack.


Pop: Removing the top element from the stack.
Traverse: Display all elements in the stack from top to bottom.

In simpler terms, imagine a stack of plates. You can only add or remove plates from the top of
the stack. The last plate you add will be the first one you can remove. This behaviour is why it's
referred to as "Last-In-First-Out."

Figure 2.1 stack

Page | 9
Advantages of Stack:

Easy implementation: Stack data structure is easy to implement using arrays or linked lists, and
its operations are simple to understand and implement.
Efficient memory utilization: Stack uses a contiguous block of memory, making it more efficient
in memory utilization as compared to other data structures.
Fast access time: Stack data structure provides fast access time for adding and removing
elements as the elements are added and removed from the top of the stack.
Helps in function calls: Stack data structure is used to store function calls and their states, which
helps in the efficient implementation of recursive function calls.
Supports backtracking: Stack data structure supports backtracking algorithms, which are used in
problem-solving to explore all possible solutions by storing the previous states.
Used in Compiler Design: Stack data structure is used in compiler design for parsing and syntax
analysis of programming languages.
Enables undo/redo operations: Stack data structure is used to enable undo and redo operations in
various applications like text editors, graphic design tools, and software development
environments.

Disadvantages of Stack:
Limited capacity: Stack data structure has a limited capacity as it can only hold a fixed number
of elements. If the stack becomes full, adding new elements may result in stack overflow, leading
to the loss of data.
No random access: Stack data structure does not allow for random access to its elements, and it
only allows for adding and removing elements from the top of the stack. To access an element in
the middle of the stack, all the elements above it must be removed.
Memory management: Stack data structure uses a contiguous block of memory, which can result
in memory fragmentation if elements are added and removed frequently.
Not suitable for certain applications: Stack data structure is not suitable for applications that
require accessing elements in the middle of the stack, like searching or sorting algorithms.
Stack overflow and underflow: Stack data structure can result in stack overflow if too many
elements are pushed onto the stack, and it can result in stack underflow if too many elements are
popped from the stack.

Page | 10
Recursive function calls limitations: While stack data structure supports recursive function calls,
too many recursive function calls can lead to stack overflow, resulting in the termination of the
program.

Application of Stack Data Structure:

Function calls and recursion: When a function is called, the current state of the program is
pushed onto the stack. When the function returns, the state is popped from the stack to resume
the previous function’s execution.

Undo/Redo operations: The undo-redo feature in various applications uses stacks to keep
track of the previous actions. Each time an action is performed, it is pushed onto the stack. To
undo the action, the top element of the stack is popped, and the reverse operation is performed.

Expression evaluation: Stack data structure is used to evaluate expressions in infix, postfix,
and prefix notations. Operators and operands are pushed onto the stack, and operations are
performed based on the stack’s top elements.

Browser history: Web browsers use stacks to keep track of the web pages you visit. Each
time you visit a new page, the URL is pushed onto the stack, and when you hit the back button,
the previous URL is popped from the stack.

Balanced Parentheses: Stack data structure is used to check if parentheses are balanced or
not. An opening parenthesis is pushed onto the stack, and a closing parenthesis is popp

#include <stdio.h>
#include<stdlib.h>

int stack[5],item;

int top = -1;


void push()
{
int x;
if(top==4){
printf("Stack Overflow\n");
}
else{
printf("Enter The Number\n");

Page | 11
scanf("%d",&x);
top++;
stack[top]=x;
}
}
void pop()
{
if(top==-1){
printf("Stack is Empty");
return;
}
else{
item = stack[top];
top--;
printf("Poped value is %d",item);
}
}
void traverse()
{
if (top == -1)
{
printf("Stack is empty\n");
return;
}
else
{
for (int i = top; i >= 0; i--)
{
printf("%d\n", stack[i]);
}
}
}

int main()
{
printf("Stack Operation\n");

Page | 12
printf("1.Push \n2.Pop \n3.Traverse\n");
int ch,c ;
do
{
printf("\nEnter the Operator\n");
scanf("%d",&c);

switch(c)
{
case 1:
push();
break;
case 2:
pop();
break;
case 3:
traverse();
break;

default:
printf("Enter another operator\n");
break;
}
}while (1);
}

OUTPUT:
Stack Operation
1.Push
2.Pop
3.Traverse

Enter the Operator


1
Enter The Number
10

Page | 13
Enter the Operator
1
Enter The Number
20
Enter the Operator
1
Enter The Number
30
Enter the Operator
3
30
20
10
Enter the Operator
2
Poped value is 30
Enter the Operator
2
Poped value is 20
Enter the Operator
2
Poped value is 10
Enter the Operator
2
Stack is Empty

Enter the Operator


3
Stack is empty

Enter the Operator


4
Enter another operator

Enter the Operator

Page | 14
CHAPTER 3

Searching is a fundamental concept in programming that involves locating a specific element


within a collection of data, often within an array or a list. Two common search algorithms used
in programming are linear search and binary search, each with its own strengths and limitations.
Searching have two types

Linear search : Also known as sequential search, is a straightforward approach where each
element in the collection is examined one by one until the target element is found or all elements
have been checked. It is akin to flipping through pages of a book to find a specific word. Linear
search is simple to implement and can be used on both sorted and unsorted data. However, its
ficiency decreases as the size of the data set grows, as it requires a linear number of comparisons.
In the worst case, when the target element is the last one or not present at all, the algorithm will
perform N comparisons, where N is the number of elements in the collection.

Figure 3.1 linear search

Advantages:

Page | 15
Simplicity: Linear search is straightforward to understand and implement. It doesn't require
any prior knowledge about the data structure, and its logic is easy to grasp for beginners.

Universal Applicability: Linear search can be applied to both sorted and unsorted data. This
versatility makes it suitable for cases where the data is not organized in any particular order.

Low Overhead: Linear search doesn't require any pre-processing steps like sorting, which
means it has lower initial overhead compared to some other search algorithms.

Ease of Implementation: The algorithm can be implemented in a variety of programming


languages with minimal code, making it accessible to programmers of all skill levels.

Disadvantages:

Inefficiency for Large Data: One of the main drawbacks of linear search is its inefficiency
for large datasets. As the size of the data increases, the time required to search grows linearly.
This is because every element must be checked, leading to a worst-case time complexity of O(n),
where n is the number of elements.

Performance: Linear search performs poorly in scenarios where quick retrieval of data is
crucial. It's not suitable for cases where frequent searches are required, especially in applications
dealing with extensive databases.

No Exploitation of Data Order: Linear search doesn't take advantage of any order within
the data. Even if the data is sorted, linear search would still examine all elements, leading to a
suboptimal search time.

Lack of Optimization: Unlike more advanced search algorithms like binary search, linear
search does not employ any optimization techniques to reduce the search space efficiently. This
can lead to unnecessary comparisons and wasted processing time.

Complexity:

The time complexity of linear search is directly proportional to the size of the data, making it
O(n), where n represents the number of elements in the collection. The best-case scenario occurs
when the target element is found in the first comparison, resulting in O(1) time complexity.
However, the worst-case scenario, where the target element is the last or not present at all, leads

Page | 16
to O(n) time complexity. The average-case time complexity is also O(n), making linear search
less efficient compared to algorithms with lower complexities, especially for large datasets.

Writing a C function called linear Search that takes an integer array arr, its size n, and an integer
target as parameters. The function should return the index (0-based) of the target element in the
array if found, and -1 if the element is not present. Implement the linear search algorithm in the
function.

#include<stdio.h>
#include<stdlib.h>

int search(int n,int target,int *arr){


for (int i = 0; i < n; i++)
{
if(arr[i]==target){
return (i+1);
}
}

printf("Item not found in the array\n");


return 0;

int main(){
int n,target,i;
printf("The size of the array\n");
scanf("%d",&n);
int arr[n] ;// Dynamically allocate memory
printf("Enter the elements\n");
for (int i = 0; i < n; i++)
{
scanf("%d",&arr[i]);
}

Page | 17
printf("\nEnter the Target\n");
scanf("%d",&target);

int ans = search(n,target,arr);


printf("Index found at %d",ans);

Binary search is a powerful searching algorithm used to locate a specific element within a
sorted collection of data. It employs a divide-and-conquer strategy, repeatedly dividing the
search interval in half, leading to efficient element retrieval. While binary search offers
numerous advantages, it also presents certain limitations that programmers need to consider.

Figure 3.2 binary search

Advantages:

Efficiency: Binary search exhibits superior performance, especially for large datasets. With
each comparison, it eliminates half of the remaining search space, resulting in a logarithmic time
complexity of O(log n), where n is the number of elements. This makes binary search
exceptionally efficient and suitable for scenarios where quick retrieval is essential.

Optimal Use of Sorted Data: Binary search takes full advantage of sorted data, as it
exploits the inherent order to drastically reduce the search space. This contrasts with linear
search, which doesn't capitalize on data order.

Page | 18
Rapid Convergence: Due to its divide-and-conquer nature, binary search converges rapidly
towards the target element. This property makes it particularly well-suited for real-time
applications or systems requiring swift responses.

Fewer Comparisons: Binary search requires significantly fewer comparisons compared to


linear search. In the worst case, the number of comparisons is proportional to the logarithm of
the number of elements, leading to substantial time savings.

Disadvantages:

Sorting Requirement: Binary search mandates that the data be sorted before searching. This
pre-processing step can add extra overhead and time complexity, especially when modifications
to the data structure necessitate re-sorting.

Limited Applicability: Binary search can only be applied to sorted data. If the data is not
already sorted, additional processing is required to sort it, which might negate the benefits of
binary search in certain cases.

Memory Consumption: The iterative approach of binary search requires minimal additional
memory. However, the recursive implementation can lead to stack overflow issues for extremely
large datasets or deep recursion levels.

Complexity in Unsorted Data: Binary search is ill-suited for unsorted data or data with
irregular patterns. Attempting to apply binary search in such cases can yield incorrect results or
increased time complexity.

Complexity:

Binary search boasts a logarithmic time complexity of O(log n), where n represents the number
of elements in the collection. Each comparison reduces the search space by half, leading to rapid
convergence. The best-case, worst-case, and average-case time complexities are all O(log n).

Page | 19
While binary search exhibits excellent time efficiency, its memory complexity remains constant,
as it doesn't require any additional memory proportional to the size of the data

Writing a C function called Binary search that takes an integer array arr, its size n, and an integer
target as parameters. The function should return the index (0-based) of the target element in the
array if found, and -1 if the element is not present. Implement the linear search algorithm in the
function.

#include <stdio.h>
#include <stdlib.h>

int search(int n, int target, int *arr, int l, int h)


{
int mid;
while (l <= h)
{
mid = (l + h) / 2;
if (arr[mid] == target)
{
return mid + 1;
}
else if (arr[mid] < target)
{
l = mid + 1;
}
else
{
h = mid - 1;
}
}

return -1;
}

int main()
{
int n, target, i;

Page | 20
printf("The size of the array\n");
scanf("%d", &n);

int arr[n];
int l = 0;
int h = n - 1;

printf("Enter the elements\n");


for (int i = 0; i < n; i++)
{
scanf("%d", &arr[i]);
}

printf("\nEnter the Target\n");


scanf("%d", &target);

int ans = search(n, target, arr, l, h);

if (ans == -1)
{
printf("Item not found");
}
else

printf("Index found at %d", ans);


}

Page | 21
Chapter 4
Queue:
A queue is a linear data structure in which elements are inserted at the rear (enqueue) and
removed from the front (dequeue). It follows the "First-In-First-Out" (FIFO) principle, meaning
that the element that is added first will be the one removed first. Queues can be implemented
using arrays or linked lists. The basic operations of a queue data structure are:

Enqueue (Insertion): Adds an element to the rear (end) of the queue.


Dequeue (Deletion): Removes the front (first) element from the queue.
Peek (Front): Retrieves the front element without removing it.
IsEmpty: Checks if the queue is empty.
IsFull: Checks if the queue is full (for bounded implementations).

Advantages of Queues:

Order Preservation: Queues maintain the order in which elements are added. The element
that enters the queue first is the first one to leave.

FIFO Behavior: Queues are suitable for scenarios where you want to process elements in the
order they were received, such as print spooling or task scheduling.

Page | 22
Efficient Insertion and Removal: Enqueue and dequeue operations take constant time in
well-implemented queues (amortized time for dynamic arrays).

Synchronization: Queues can be used to implement synchronization mechanisms in


multithreading scenarios, ensuring that tasks are executed in a controlled order.

Breadth-First Search: In graph algorithms like breadth-first search, queues are used to keep
track of the nodes to be explored.

Disadvantages of Queues:
Limited Access: Unlike arrays, random access to elements in a queue is not efficient.
Accessing elements other than the front one requires dequeuing elements until the desired
element is reached.

Memory Overhead: In linked list implementations, each element requires additional


memory for the next pointer. Dynamic array implementations may have to resize, leading to
memory management overhead.

Applications of Queues:

Print Spooling: Queues are used in printer spoolers to manage the order of print jobs.
Task Scheduling: Operating systems use queues to manage and schedule processes or
threads based on their priority.

Breadth-First Search: In graph traversal algorithms like BFS, a queue is used to explore
nodes level by level.

Buffering: Queues are used to buffer incoming data when the rate of data production is
different from the rate of data consumption, such as in networking or I/O operations.

Call Center Systems: In call centers, incoming calls are usually placed in a queue before
being connected to a customer service representative.

Simulations: Queues are used in computer simulations to model scenarios where entities wait
for service, like customers in a bank.

Task Management: Task queues are used in distributed computing systems to manage and
distribute tasks across multiple nodes.

Page | 23
implementing a basic queue data structure using an array and switch cases in C. The program
should allow users to perform enqueue, dequeue, and traverse operations. The queue should have
a maximum capacity of 5 elements. Implement the following operations:

Enqueue: Add an integer element to the rear of the queue. If the queue is full, display an error
message.
Dequeue: Remove the front element from the queue. If the queue is empty, display an error
message.
Traverse: Display all elements in the queue from front to rear.

#include <stdio.h>

#include<stdlib.h>

int front =-1,rear = -1,n=5;

int queue[5];

#define RESET "\x1B[0m"


#define RED "\x1B[31m"
#define GREEN "\x1B[32m"
#define YELLOW "\x1B[33m"
#define BLUE "\x1B[34m"
#define MAGENTA "\x1B[35m"
#define CYAN "\x1B[36m"
#define WHITE "\x1B[37m"
int enqueue(){
int val;
if(rear==n-1){
printf("Queue Overflow\n");
return 0;
}

Page | 24
else{
if(front==-1){
front++;
}
printf("Enter the Value\n");
scanf("%d",&val);
rear++;
queue[rear]= val;
}
}

int dequeue(){
int item;
if(front==-1){
printf(RED"Queue is Empty"RESET);
}
else{
item = queue[front];
front++;
printf("Deleted value is %d",item);

if(front>rear){
front = rear = -1;
}
}
}
void traverse(){
if(front==-1){
printf(RED "Queue is empty\n"RESET);
return ;
}
else{
printf(GREEN"---------------\n"RESET);
printf(GREEN"Queue:"RESET);

for (int i = front; i <= rear; i++)

Page | 25
{
printf(GREEN "%d "RESET,queue[i]);
}
Printf(“\n”);
printf(GREEN"-----------------"RESET);
}
}
int main()
{
printf("Queue Operation\n");
printf("1.Enqueue \n2.Dqueue \n3.Traverse\n");
int ch,c ;
do
{
printf("\nEnter the Operator\n");
scanf("%d",&c);

switch(c)
{
case 1:
enqueue();
break;
case 2:
dequeue();
break;
case 3:
traverse();
break;
default:
printf("Enter another operator\n");
break;
}
}while (1);
}

Output:
Page | 26
Queue Operation
1.Enqueue
2.Dqueue
3.Traverse

Enter the Operator


3
Queue is empty
Enter the Operator
2
Queue is Empty
Enter the Operator
1
Enter the Value
4
Enter the Operator
1
Enter the Value
6
Enter the Operator
2
Deleted value is 4
Enter the Operator
3
---------------
Queue:6
-----------------
Enter the Operator
2
Deleted value is 6
Enter the Operator

Page | 27
3
Queue is empty

Definition of Circular Queue:

A circular queue, also known as a circular buffer or a ring buffer, is a data structure that
represents a linear collection of elements stored in a circular arrangement. In a circular queue,
the last element is connected back to the first element, forming a loop. This arrangement allows
for efficient reuse of storage and provides a fixed-size queue with seamless wrap-around
behavior.

Figure 4.2 Circular Queue

Advantages of Circular Queue:


Efficient Space Utilization: Circular queues optimize space utilization as they reuse the
positions that become vacant due to dequeuing, reducing memory wastage compared to a simple
linear queue.

Page | 28
Constant Time Enqueue and Dequeue: Enqueuing (adding) and dequeuing (removing)
elements from a circular queue take constant time complexity, O(1), regardless of the size of the
queue. This is beneficial for real-time applications and situations where performance is crucial.
Faster Enqueue Operations: In a circular queue, enqueuing can be faster than in other linear
data structures because it avoids shifting of elements to accommodate new elements at the
beginning.
Fixed Size: Circular queues have a fixed capacity, which can be advantageous in situations
where you need to limit memory usage or manage resources efficiently.
Cyclic Behavior: Circular queues exhibit a cyclic or wrap-around behavior, which can be useful
for scenarios where you need to maintain a rolling history of data, such as in audio or video
streaming applications.

Disadvantages of Circular Queue:

Fixed Capacity: The fixed size of a circular queue can be a limitation if the application requires
dynamic resizing to accommodate varying amounts of data. Resizing a circular queue involves
complex operations.
Wasted Memory: In some cases, a circular queue may still have unused space due to its fixed
capacity, especially when the size of the data doesn't align well with the capacity.

Overhead of Wrapping Logic: The wrap-around behavior of a circular queue requires


additional logic to handle indices and positions correctly, which might introduce complexity and
potential programming errors.
Limited Use Cases: Circular queues are not suitable for scenarios where the number of elements
is unknown or highly dynamic, as their fixed size can lead to data loss or inefficient memory
usage.

Applications of Circular Queue:

Data Streaming: Circular queues are often used in applications that involve continuous data
streams, such as audio and video players, where the most recent data overwrites the oldest data.
Printer Spooling: Print job spooling systems can use circular queues to manage the order of print
jobs in a printer's queue.
Real-time Systems: In real-time systems, where predictable and fast response times are
essential, circular queues can be used to manage tasks or events with constant-time enqueue and
dequeue operations.
Buffering: Circular queues can serve as efficient buffers for data transmission between different
components, like in communication systems.
Memory Management: Some memory allocation strategies use circular queues to keep track of
available memory blocks or resources.
Task Scheduling: In operating systems, circular queues can be employed for scheduling tasks in
a round-robin manner, where each task gets a turn in a cyclic order.

Page | 29
#include <stdio.h>
#include<stdlib.h>
int front =-1,rear = -1;

#define RESET "\x1B[0m"


#define RED "\x1B[31m"
#define GREEN "\x1B[32m"

int enqueue(int *queue,int n){


int val;
if((front == rear +1) || (front == 0 && rear == n-1)){
printf("Queue is full\n");
return 0;
}
else{
if(front==-1){
front = 0;
}
printf("Enter the Value\n");
scanf("%d",&val);
rear = (rear+1)%n;
queue[rear]= val;
}
}

int traverse(int *queue,int n){


if(front==-1){
printf(RED "Queue is empty\n"RESET);
return 0;
}
else{
printf(GREEN"---------------\n");
printf("Queue:");

for(int i = front ; i!=rear ; i= (i+1)%n){


printf("%d ",queue[i]);
}
printf("%d ",queue[rear]);
printf("\n");

Page | 30
printf("-----------------"RESET);

}
}
int dequeue(int *queue,int n){
int item;
if(front==-1){
printf(RED"Queue is Empty"RESET);
return 0;
}
else{

if(front == rear){
front = -1;
rear =-1;
}
else{
item = queue[front];
front = (front+1)%n ;
printf("Deleted value is %d",item);
}

}
}
int main()
{
int n ;
printf("Enter the Size of the Circular Queue\n");
scanf("%d",&n);

int queue[n];
printf("Circular Queue Operation\n");
printf("1.Enqueue \n2.Dqueue \n3.Traverse\n");
int ch,c ;
do
{
printf("\nEnter the Operator\n");
scanf("%d",&c);

switch(c)
{
case 1:
enqueue(queue,n);

Page | 31
break;
case 2:
dequeue(queue,n);
break;
case 3:
traverse(queue,n);
break;

default:
printf("Enter another operator\n");
break;
}
printf("\nEnter 1 to continue\n");
scanf("%d",&ch);

}while (1);
}

Page | 32
CHAPTER 5
SORTING
In programming, sorting refers to the process of rearranging a collection of items or elements in
a specific order, typically following a particular criterion. The primary goal of sorting is to make
it easier and more efficient to search for, access, and manipulate data. Sorting is a fundamental
operation in computer science and plays a crucial role in various algorithms and applications.

**Bubble Sort:**
Bubble Sort is a simple sorting algorithm that repeatedly steps through the list to be sorted,
compares adjacent elements, and swaps them if they are in the wrong order. The process is
repeated until the entire list is sorted. While Bubble Sort is straightforward to understand and
implement, it's not very efficient for large datasets due to its higher time complexity compared to
more advanced sorting algorithms.

Figure 5.1 bubble sort

Advantages of Bubble Sort:


 Bubble sort is easy to understand and implement.
 It does not require any additional memory space.
 It is a stable sorting algorithm, meaning that elements with the same key value
maintain their relative order in the sorted output.
Disadvantages of Bubble Sort :
 Bubble sort has a time complexity of O(N2) which makes it very slow for large data
sets.

Page | 33
 Bubble sort is a comparison-based sorting algorithm, which means that it requires a
comparison operator to determine the relative order of elements in the input data
set. It can limit the efficiency of the algorithm in certain cases

**Applications of Bubble Sort:**


Bubble Sort is seldom used in practical applications due to its inefficiency on larger datasets.
However, it can still be useful in situations where:
1. The dataset is very small.
2. The dataset is already mostly sorted or requires only a few adjustments.
3. The primary goal is educational, and demonstrating the concept of sorting is more important
than efficiency.

**Complexity:**
- **Time Complexity:**
- Best Case: O(n) (when the input data is already sorted)
- Average Case: O(n^2)
- Worst Case: O(n^2)

- Space Complexity:
- Bubble Sort has a space complexity of O(1), as it only requires a constant amount of
additional memory for swapping elements.

In summary, while Bubble Sort is easy to understand and implement, it is not suitable for sorting
large datasets due to its poor time complexity. Its simplicity and minimal memory usage can be
advantageous in certain situations, but for most practical applications, other sorting algorithms
with better performance characteristics are preferred.

Code:
#include<stdio.h>
void swap(int *a, int *b){
int c = *a ;
*a = *b ;
*b = c;
}
void bubblesort(int arr[],int n){
for(int i = 0 ; i<n-1;i++){

Page | 34
int p = 0;
printf("Pass %d: ",i+1);
for(int j = 0 ; j<n;j++){
if(arr[j]>arr[j+1]){
swap(&arr[j],&arr[j+1]);
}
printf("%d ",arr[p]);
p++;
}
printf("\n");
}
}
int main(){
int n;
printf("Enter the size of arr\n");
scanf("%d",&n);
int arr[n];
printf("Enter the numbers\n");
for(int i = 0 ; i<n; i++){
scanf("%d",&arr[i]);
}
printf("The array is ");
for (int i = 0; i < n; i++)
{
printf("%d ",arr[i]);
}
printf("\n");
bubblesort(arr,n);
}

OUTPUT:
Enter the size of arr
5
Enter the numbers
35
20

Page | 35
21
28
9
The array is 35 20 21 28 9
Pass 1: 20 21 28 9 35
Pass 2: 20 21 9 28 35
Pass 3: 20 9 21 28 35
Pass 4: 9 20 21 28 35

Selection Sort:
Definition:
Selection Sort is a simple comparison-based sorting algorithm that divides the input list into two
parts: the sorted part and the unsorted part. It repeatedly selects the smallest (or largest,
depending on the sorting order) element from the unsorted part and moves it to the end of the
sorted part.

Advantages:
Simplicity: Selection Sort is easy to understand and implement. It involves minimal logic,
making it suitable for small lists or educational purposes.
Memory Efficiency: Selection Sort requires minimal additional memory space. It only involves
a few variable assignments for swapping elements, making it memory-efficient.

Disadvantages:
Inefficiency: Selection Sort's time complexity is generally poor, especially for larger lists. It has
a worst-case, average-case, and best-case time complexity of O(n^2), making it inefficient for
large datasets.
Lack of Adaptiveness: The algorithm does not take advantage of existing order in the input list.
Even if a portion of the list is already sorted, Selection Sort will continue its selection and
swapping process without optimization.

Page | 36
Unstable Sort: Selection Sort is inherently unstable, meaning it does not guarantee the
preservation of the relative order of equal elements after sorting.

Applications:
Selection Sort's inefficiency makes it less suitable for practical applications involving large
datasets. However, due to its simplicity, it can be used as an introductory example to teach
sorting algorithms and their basic concepts.

Complexity:
Time Complexity: Selection Sort has a time complexity of O(n^2) in all cases (best, average,
and worst).
Space Complexity: Selection Sort has a space complexity of O(1) since it uses a constant
amount of additional memory for swapping elements.

Project
#include<stdio.h>

void swap(int *a, int *b){


int c = *a ;
*a = *b ;
*b = c;

void selectionsort(int arr[],int n){


for(int i = 0 ; i<n;i++){
printf("Pass %d: ",i+1);
int min = arr[i];
for(int j = i+1 ; j<n;j++){
if(min>arr[j]){
min = arr[j];
swap(&arr[j],&arr[i]);
}
}
for(int i = 0 ; i<n ; i++){
printf("%d ",arr[i]);

Page | 37
}
printf("\n");

}
int main(){
int n;
printf("Enter the size of arr\n");
scanf("%d",&n);
int arr[n];
printf("Enter the numbers\n");

for(int i = 0 ; i<n; i++){

scanf("%d",&arr[i]);
}

printf("The array is ");


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

Insertion Sort:
Definition:
Insertion Sort is a simple sorting algorithm that builds the final sorted array one item at a time. It
works by iterating through the array, gradually shifting elements to their correct positions by
comparing each element with its adjacent elements and inserting it into its correct position.

Advantages:

Simple Implementation: Insertion Sort is easy to understand and implement, making it a good
choice for small datasets and educational purposes.

Page | 38
Efficient for Small Data: It performs well for small datasets or partially sorted arrays, as the
overhead of the algorithm is relatively low compared to more complex algorithms like QuickSort
or MergeSort.
Adaptive: It becomes more efficient when dealing with nearly sorted arrays, as it requires fewer
comparisons and swaps in such cases.
Online Sort: Insertion Sort can efficiently handle streaming data that arrives one by one, as it
can keep the sorted portion of the array updated after each new element is inserted.

Disadvantages:

Inefficient for Large Data: For larger datasets, Insertion Sort becomes less efficient compared
to more advanced sorting algorithms like MergeSort or QuickSort. Its time complexity is
quadratic (O(n^2)) in the worst case, which can make it slow for large datasets.
Sensitivity to Initial Order: The performance of Insertion Sort can vary significantly based on
the initial order of the elements. Highly unsorted data may require a large number of
comparisons and swaps.

Applications:
Insertion Sort's simplicity and adaptability make it suitable for certain scenarios:
When dealing with small datasets where the overhead of more complex algorithms might
outweigh their benefits.
When working with almost sorted or partially sorted data.
In cases where stability (maintaining the relative order of equal elements) is important.

Complexity:

Time Complexity: In the worst case, the time complexity of Insertion Sort is O(n^2), where 'n'
is the number of elements in the array. In the best case (when the array is already sorted), the
time complexity is O(n). On average, it falls between the worst and best case scenarios.

Page | 39
Space Complexity: Insertion Sort has a space complexity of O(1), meaning it doesn't require
additional memory proportional to the input size. It sorts the array in-place without requiring
additional data structures.
Overall, while Insertion Sort has its advantages for specific use cases, its limitations in terms of
efficiency for larger datasets often make it less suitable for general-purpose sorting of large
amounts of data.

CHAPTER 6
Definition of Linked List:
A linked list is a linear data structure used in computer science and programming for organizing
and storing a collection of elements, called nodes. Each node contains both data and a reference
(or pointer) to the next node in the sequence. The last node typically points to a null reference,
indicating the end of the list. Linked lists provide a dynamic way to manage and manipulate data,
as their size can be adjusted during runtime, unlike static arrays.

Advantages of Linked Lists:

Dynamic Size: Linked lists can dynamically adjust their size, allowing for efficient memory
utilization as elements can be added or removed without the need for contiguous memory
allocation.
Insertions and Deletions: Inserting or deleting elements from a linked list can be more efficient
compared to arrays. In most cases, it only involves updating a few pointers, whereas arrays may
require shifting elements.
Memory Allocation: Linked lists can be useful when the available memory is fragmented or
when the exact size of the data structure is unknown in advance.
Efficient Operations: Linked lists can be efficient for insertions and deletions in the middle of
the list, unlike arrays where such operations can be time-consuming.
Ease of Concatenation: Merging two linked lists is relatively straightforward by redirecting
pointers, making concatenation efficient.

Page | 40
Disadvantages of Linked Lists:

Memory Overhead: Each node in a linked list requires additional memory to store both the data
and the reference to the next node, leading to increased memory consumption compared to
arrays.
Traversal Complexity: Unlike arrays, which allow direct access to elements using indices,
linked lists require traversal from the beginning to reach a specific element, resulting in
potentially slower access times.
Cache Inefficiency: Linked lists can result in poor cache locality since nodes are not stored in
contiguous memory locations, potentially leading to slower performance in certain scenarios.
Complexity: Managing pointers and references in linked lists can introduce complexity, such as
dealing with null references or handling various edge cases during operations.
Applications of Linked Lists:

Implementation of Data Structures: Linked lists are fundamental in implementing other data
structures like stacks, queues, and hash tables.
Dynamic Memory Allocation: Linked lists are used in memory management systems to allocate
and deallocate memory blocks efficiently.
Undo Functionality: Some applications require maintaining a history of actions, like in text
editors where linked lists can be used for undo and redo operations.
Music and Video Playlists: Linked lists are well-suited for implementing playlists, where each
node represents a song or video, and the next node points to the next item in the playlist.
Sparse Data Structures: In cases where most elements are empty or null, linked lists can save
memory by only allocating space for elements with data.

Page | 41
Page | 42
Page | 43

You might also like