0% found this document useful (0 votes)
10 views152 pages

Experiment Dsa

The document outlines a series of experiments focused on implementing fundamental data structures and algorithms in C, including string functions, arrays, searching algorithms, stacks, and queues using both arrays and linked lists. Each experiment includes a clear aim, theoretical background, source code, and learning outcomes that emphasize practical skills and understanding of data manipulation. The experiments aim to enhance problem-solving abilities and provide hands-on experience with essential programming concepts.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
10 views152 pages

Experiment Dsa

The document outlines a series of experiments focused on implementing fundamental data structures and algorithms in C, including string functions, arrays, searching algorithms, stacks, and queues using both arrays and linked lists. Each experiment includes a clear aim, theoretical background, source code, and learning outcomes that emphasize practical skills and understanding of data manipulation. The experiments aim to enhance problem-solving abilities and provide hands-on experience with essential programming concepts.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 152

EXPERIMENT 1

AIM: To implement string functions like strcopy(), strlen(), strcat() etc. without the use of
<string.h>
THEORY:
strcpy() - String Copy Function:
strcpy() copies the contents of one string to another.
We'll iterate through each character of the source string and copy it to the destination string
until we reach the null character ('\0').
strlen() - String Length Function:
strlen() calculates the length of a string.
We'll iterate through the characters of the string until we find the null character ('\0'),
counting the number of characters encountered.
strcat() - String Concatenation Function:
strcat() appends one string to the end of another.
We'll find the end of the destination string and then copy each character from the source
string to the end of the destination string until we reach the null character ('\0').
SOURCE CODE:
#include<stdio.h>

//Function to copy the string from one to another


void stringcopy(char* destination, char *source){
if (*source!='\0'){
*destination=*source;
stringcopy(destination+1, source+1);
}
else
*destination='\0';
}
//Function to determine lenght of aa string
int stringlength(char *source){
int len=0;
while (*source!='\0'){
len++;
source++;
}
return len;}
//Function to concatenate 2 Strings
void stringconcat(char *start,char*end){
while (*start!='\0'){
start++;
}
while(*end!='\0'){
*start=*end;
start++;
end++;
}
*start='\0';
}

int main(){
char alpha[50]="CopyIt";
char beta[50];
char gamma[50]="Here";
stringcopy(beta, alpha);
printf("The original string is %s.\n",alpha);
printf("The copied string is %s.\n",beta);
printf("The length of this string is %d.\n",
stringlength(alpha));
stringconcat(alpha,gamma);
printf("The concatenated string is %s.",alpha);}

OUTPUT:

LEARNING OUTCOME:
Understanding of string manipulation at a fundamental level.
Practice in implementing algorithms for common string operations.
Improved problem-solving skills by implementing standard library functions from scratch.
EXPERIMENT 2
AIM: To implement one dimensional, two dimensional and multi-dimensional arrays.
THEORY:
Arrays are data structures used to store elements of the same type sequentially in memory.
One-dimensional arrays are linear collections accessed using a single index, declared with a
single pair of square brackets. Two-dimensional arrays, arrays of arrays, organize elements
into rows and columns, declared with two pairs of square brackets. Multi-dimensional arrays
extend this concept to more dimensions. They are declared with multiple pairs of square
brackets, and each dimension is accessed using an index. Initialization and access follow
similar patterns across all array types, offering efficient storage and retrieval of data in
various dimensions.
SOURCE CODE:
#include<stdio.h>
//Function to display One Dimensional Array
void displayONE(int arr[], int len){
for (int i = 0; i < len; i++)
{
printf("%d ",arr[i]);
}
}
//Function to display Two Dimensional Array
void displayTWO(int arr[][3], int rows,int cols){
for (int i = 0; i < rows; i++)
{
for (int j = 0; j < cols; j++)
{
printf("%d ",arr[i][j]);
}
printf("\n");
}
}
// Function to display Three Dimensional Array
void displayTHREE(int arr[][2][2], int dim1, int dim2, int
dim3){
for (int i = 0; i < dim1; i++){
for (int j = 0; j < dim2; j++){
for (int k = 0; k < dim3; k++){
printf("%d ", arr[i][j][k]);
}
printf("\n");
}
printf("\n");
}
}
int main(){
//One-Dimensional Array
int one[10]={1,2,3,4,5,6,7,8,9,10};
printf("The 1-D array is: \n");
displayONE(one,10);
printf("\n\n");
//Two Dimensional Array
int two[3][3]={{1,2,3},{4,5,6},{7,8,9}};
printf("The 2-D array is: \n");
displayTWO(two,3,3);
printf("\n");
//Multi Dimensional Array
printf("The Multi-D array is: \n");
int multi[2][2][2]={{{1,2},{3,4}},{{5,6},{7,8}}};
displayTHREE(multi, 2,2,2);
}

OUTPUT:

LEARNING OUTCOME:
Familiarity with accessing elements of arrays using indices.
Knowledge of how memory is organized for multi-dimensional arrays.
Experience in handling multi-dimensional data structures for various applications.
EXPERIMENT 3
AIM: To implement linear search and binary search on arrays.
THEORY:
Linear search is a simple searching algorithm that sequentially checks each element of the
array until the desired element is found or the end of the array is reached. It works well for
small arrays or unordered arrays.
Binary search is an efficient searching algorithm for sorted arrays. It compares the target
value with the middle element of the array. If they match, the search is successful. If not, the
half in which the target cannot lie is eliminated, and the search continues on the remaining
half.
SOURCE CODE:
#include<stdio.h>
//Function to implement Linear Search
int linearsearch(int arr[],int size, int key){
for (int i = 0; i < size; i++)
{
if(arr[i]==key){
return i;
}
}
return -1;
}
//Function to implement Binary Search
int binarysearch(int arr[], int low, int high, int key){
if (low<=high){
int mid=(low+high)/2;
if (arr[mid]==key)
{
return mid;
}
else if (arr[mid]<key)
{
return binarysearch(arr,mid+1, high,key);
}
else
return binarysearch(arr, low,mid-1,key);
}
return -1;
}
int main(){
int arr[10]={1,2,3,4,5,6,7,8,9,10};
int key1=5;
int key2=8;
int linear=linearsearch(arr,10,key1);
int binary=binarysearch(arr,0,9,key2);
if (linear!=-1)
{
printf("Value %d found by Linear Search at index
%d.\n",key1,linear);
}
else{
printf("Value %d not found.",key1);
}
if (binary!=-1)
{
printf("Value %d found by Binary Search at index
%d.\n",key2,binary);
}
else{
printf("Value %d not found.",key2);
}
return 0;
}

OUTPUT:

LEARNING OUTCOME:
Familiarity with sequential search (linear search) and its limitations.
Understanding of efficient searching (binary search) on sorted arrays.
Insights into the importance of data organization for efficient search operations.
EXPERIMENT 4
AIM: To Implement stack using array and show the stack operations.
THEORY: A stack is a linear data structure that follows the Last In, First Out (LIFO)
principle. It means that the last element added to the stack is the first one to be removed.
Stacks are widely used in programming for various applications such as expression
evaluation, function call management (call stack), and backtracking algorithms.

In an array-based implementation of a stack, a fixed-size array is used to store the stack


elements, and a pointer (usually called 'top') keeps track of the top element of the stack. Stack
operations mainly include Push, Pop, Peek.
SOURCE CODE:
#include <stdio.h>
#include <stdlib.h>
#define MAX 10
//Creating Stack
int stack[MAX], top = -1;
//Function to Push element into Stack
void push(int stack[], int value)
{
if (top == MAX - 1)
{
printf("\nStack Overflow!");
return;
}
top++;
stack[top] = value;
printf("\n%d pushed into the Stack.",value);
}
//Function to Pop element from Stack
void pop(int stack[])
{
if (top == -1)
{
printf("\nStack Underflow!");
return;
}
printf("\n%d popped from the Stack.",stack[top]);
top--;
}
//Function to peek the topmost element of Stack
void peek(int stack[])
{
if (top == -1)
{
printf("\nStack is Empty!");
return;
}
printf("\nThe topmost element is %d.", stack[top]);
}
//Function to display the complete Stack
void display(int stack[])
{ if(top==-1){
printf("\nEmpty Stack!");
}
else
printf("\nThe elements of stack are : ");
for (int i = top; i > -1; i--)
{
printf("%d, ", stack[i]);
}
}
int main()
{
pop(stack);
push(stack,5);
push(stack,10);
peek(stack);
push(stack,48);
pop(stack);
push(stack,7);
push(stack,93);
display(stack);
return 0;
}

OUTPUT:
LEARNING OUTCOME:
Understanding of stack data structure and its operations.
Familiarity with array-based implementation of data structures.
Ability to implement common stack operations like push, pop, and peek in C.
Handling of edge cases such as stack overflow and underflow.
EXPERIMENT 5
AIM: To Implement stack using Linked List and show the stack operations.
THEORY: A stack is a linear data structure that follows the Last In, First Out (LIFO)
principle. It means that the last element added to the stack is the first one to be removed.
Stacks are widely used in programming for various applications such as expression
evaluation, function call management (call stack), and backtracking algorithms.

In a linked list-based implementation of a stack, each element of the stack is represented by a


node in a singly linked list. The top of the stack is represented by the head of the linked list.
Stack operations include Push, Pop, Peek, IsEmpty.
SOURCE CODE:
#include <stdio.h>
#include <stdlib.h>
// Basic Structure
struct Node
{
int data;
struct Node *next;
};
// Head Node
struct Node *head = NULL;
// Function to Create New Nodes
struct Node *createnode(int value)
{
struct Node *newnode = (struct Node *)malloc(sizeof(struct
Node));
newnode->data = value;
newnode->next = NULL;
return newnode;
}
// Function to push element into stack
struct Node *push(struct Node *head, int value)
{
struct Node *newnode = createnode(value);
printf("\n%d successfully pushed onto the stack.",value);
if (head == NULL)
{
head = newnode;
}
else
{
newnode->next = head;
return newnode;
}
}
// Function to pop element form stack
struct Node *pop(struct Node *head)
{
if (head == NULL)
{
printf("\nStack Underflow!");
return head;
}

struct Node *current = head;


head = current->next;
free(current);
return head;
}
// Function to display the Complete stack
void display(struct Node *head)
{
struct Node *current = head;
while (current != NULL)
{
printf("\n%d ", current->data);
current = current->next;
}
}
int main()
{
head = push(head, 5);
head = pop(head);
head = pop(head);
head = push(head, 7);
head = push(head, 9);
head = push(head, 12);
display(head);
}

OUTPUT:

LEARNING OUTCOME:
Develop practical skills in constructing a stack using linked lists, delving into concepts of
memory management and node manipulation crucial for dynamic data structures.
Showcase adeptness in executing essential stack operations, including push, pop, peek, and
display, illuminating the core functionalities of a stack-based data structure.
Grasp the advantages inherent in utilizing linked lists over arrays for dynamic data structures
like stacks, fostering efficient memory usage and adaptability to varying data sizes.
EXPERIMENT 6
AIM: To implement Queue using array and implement Queue Operations.
THEORY: A queue is a linear data structure that follows the First In, First Out (FIFO)
principle. It means that the element that is added first to the queue will be the first one to be
removed. Queues are widely used in programming for various applications such as job
scheduling, breadth-first search algorithms, and implementing caches.

In an array-based implementation of a queue, a fixed-size array is used to store the queue


elements, and two pointers, usually called 'front' and 'rear', keep track of the front and rear
ends of the queue respectively.
SOURCE CODE:
#include <stdio.h>
#include <stdbool.h>

#define MAX_SIZE 100

// Define the queue structure


typedef struct {
int arr[MAX_SIZE];
int front, rear;
} Queue;

// Function to initialize the queue


void initQueue(Queue *queue) {
queue->front = -1;
queue->rear = -1;
}

// Function to check if the queue is empty


bool isEmpty(Queue *queue) {
return queue->front == -1;
}

// Function to check if the queue is full


bool isFull(Queue *queue) {
return (queue->rear + 1) % MAX_SIZE == queue->front;
}

// Function to enqueue an element into the queue


void enqueue(Queue *queue, int item) {
if (isFull(queue)) {
printf("Queue Overflow\n");
return;
}
if (isEmpty(queue))
queue->front = 0;
queue->rear = (queue->rear + 1) % MAX_SIZE;
queue->arr[queue->rear] = item;
}

// Function to dequeue an element from the queue


int dequeue(Queue *queue) {
if (isEmpty(queue)) {
printf("Queue Underflow\n");
return -1; // Return an error value
}
int item = queue->arr[queue->front];
if (queue->front == queue->rear) {
queue->front = -1;
queue->rear = -1;
} else {
queue->front = (queue->front + 1) % MAX_SIZE;
}
return item;
}

// Function to return the front element of the queue


int front(Queue *queue) {
if (isEmpty(queue)) {
printf("Queue is empty\n");
return -1; // Return an error value
}
return queue->arr[queue->front];
}

// Function to display the elements of the queue


void display(Queue *queue) {
if (isEmpty(queue)) {
printf("Queue is empty\n");
return;
}
printf("Queue elements:\n");
int i = queue->front;
while (i != queue->rear) {
printf("%d\n", queue->arr[i]);
i = (i + 1) % MAX_SIZE;
}
printf("%d\n", queue->arr[queue->rear]);
}

int main() {
Queue queue;
initQueue(&queue);

enqueue(&queue, 1);
enqueue(&queue, 2);
enqueue(&queue, 3);

display(&queue); // Output: Queue elements: 1 2 3

printf("Dequeued element: %d\n", dequeue(&queue)); //


Output: Dequeued element: 1
display(&queue); // Output: Queue elements: 2 3

return 0;
}

OUTPUT:

LEARNING OUTCOME:
Acquire practical experience in implementing a queue data structure using an array, fostering
comprehension of dynamic memory allocation and management, essential for building
efficient data structures.
Demonstrate proficiency in executing fundamental queue operations, including enqueue,
dequeue, and front, showcasing mastery over core functionalities crucial for data
manipulation and algorithm design.
Understand the importance of error handling and boundary checks in queue operations,
ensuring robustness and reliability in real-world applications, thereby promoting software
resilience and stability.
EXPERIMENT 7
AIM: - Implement queue using Linked List and show the queue operations.
THEORY: A queue implemented with a linked list follows FIFO (First In, First Out) order.
Each element is a node containing data and a pointer to the next node. Operations include
enqueue (adding an element to the end), dequeue (removing the first element), peek (viewing
the front element without removing it), and isEmpty (checking if the queue is empty). Linked
list implementation offers dynamic memory allocation, efficient insertion and deletion, but
may have higher overhead due to maintaining pointers. This design ensures efficient handling
of data in a sequential manner, crucial in scenarios like task scheduling and breadth-first
search algorithms.
SOURCE CODE:
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

// Define the node structure for the linked list


typedef struct Node {
int data;
struct Node* next;
} Node;

// Define the queue structure


typedef struct {
Node* front;
Node* rear;
} Queue;

void initQueue(Queue *queue) {


queue->front = NULL;
queue->rear = NULL;
}

// Function to check if the queue is empty


bool isEmpty(Queue *queue) {
return queue->front == NULL;
}

void enqueue(Queue *queue, int item) {


Node* newNode = (Node*)malloc(sizeof(Node));
if (newNode == NULL) {
printf("Memory allocation failed\n");
return;
}
newNode->data = item;
newNode->next = NULL;
if (isEmpty(queue)) {
queue->front = newNode;
queue->rear = newNode;
} else {
queue->rear->next = newNode;
queue->rear = newNode;
}
}

// Function to dequeue an element from the queue


int dequeue(Queue *queue) {
if (isEmpty(queue)) {
printf("Queue Underflow\n");
return -1; // Return an error value
}
Node* temp = queue->front;
int item = temp->data;
queue->front = queue->front->next;
if (queue->front == NULL) {
queue->rear = NULL;
}
free(temp);
return item;
}

// Function to return the front element of the queue


int front(Queue *queue) {
if (isEmpty(queue)) {
printf("Queue is empty\n");
return -1; // Return an error value
}
return queue->front->data;
}

// Function to display the elements of the queue


void display(Queue *queue) {
if (isEmpty(queue)) {
printf("Queue is empty\n");
return;
}
printf("Queue elements:\n");
Node* current = queue->front;
while (current != NULL) {
printf("%d\n", current->data);
current = current->next;
}
}
int main() {
Queue queue;
initQueue(&queue);

enqueue(&queue, 47);
enqueue(&queue, 45);
enqueue(&queue, 36);
display(&queue);
printf("Dequeued element: %d\n", dequeue(&queue));
display(&queue);
return 0;
}

OUTPUT:

LEARNING OUTCOME:
1. Define the characteristics of a queue and its FIFO (First In First Out) behavior.
2. Explain the structure of a linked list and its relevance to queue implementation.
3. Demonstrate proficiency in implementing queue operations (enqueue, dequeue, peek,
isEmpty, size) using a linked list.
4. Analyze the time complexity of queue operations in the linked list implementation.
5. Apply queue-based problem-solving strategies to real-world scenarios, emphasizing the
importance of efficient data structures.
EXPERIMENT 8
AIM: Implement singly linked list with all operations.
THEORY: A singly linked list is a fundamental data structure composed of nodes where
each node points to the next node in the sequence. Operations include insertion, deletion, and
traversal. Insertion involves adding a new node at the beginning, end, or middle of the list,
updating pointers accordingly. Deletion removes a node by updating pointers to bypass it.
Traversal involves visiting each node sequentially. Implementing these operations requires
managing pointers effectively to maintain the integrity of the list. Understanding singly
linked lists is crucial as they serve as the basis for more complex data structures and
algorithms, contributing to efficient memory management and problem-solving.
SOURCE CODE:#include <stdio.h>
#include <stdlib.h>

// Node structure for singly linked list


struct Node {
int data;
struct Node* next;
};

typedef struct Node Node;

// Function to create a new node


Node* createNode(int data) {
Node* newNode = (Node*)malloc(sizeof(Node));
if (newNode == NULL) {
printf("Memory allocation failed.\n");
exit(1);
}
newNode->data = data;
newNode->next = NULL;
return newNode;
}
// Function to insert a new node at the beginning of the
linked list
Node* insert(Node* head, int data) {
Node* newNode = createNode(data);
newNode->next = head;
return newNode;
}
Node* deleteNode(Node* head, int data) {
Node* current = head;
Node* prev = NULL;

// If the node to be deleted is the head node


if (current != NULL && current->data == data) {
head = current->next;
free(current);
return head;
}

// Search for the node to be deleted


while (current != NULL && current->data != data) {
prev = current;
current = current->next;
}

// If the node with data was not found


if (current == NULL) {
printf("Node with data %d not found.\n", data);
return head;
}

// Node with data found, delete it


prev->next = current->next;
free(current);
return head;
}

// Function to search for a node with given data in the linked


list
Node* search(Node* head, int data) {
Node* current = head;
while (current != NULL) {
if (current->data == data) {
printf("Node with data %d found.\n", data);
return current;
}
current = current->next;
}
printf("Node with data %d not found.\n", data);
return NULL;
}

// Function to print the linked list


void printList(Node* head) {
Node* current = head;
while (current != NULL) {
printf("%d ", current->data);
current = current->next;
}
printf("\n");
}

// Function to free memory allocated for the linked list


void freeList(Node* head) {
Node* current = head;
while (current != NULL) {
Node* temp = current;
current = current->next;
free(temp);
}
}

// Main function
int main() {
Node* head = NULL;

// Insert nodes into the linked list


head = insert(head, 1);
head = insert(head, 2);
head = insert(head, 3);
head = insert(head, 4);
head = insert(head, 5);

printf("Original list: ");


printList(head); // Output: 5 4 3 2 1

// Search for a node with data 3


search(head, 3);

// Delete a node with data 3


head = deleteNode(head, 3);
printf("List after deletion of node with data 3: ");
printList(head); // Output: 5 4 2 1

// Free memory allocated for the linked list


freeList(head);

return 0;
}

OUTPUT:

LEARNING OUTCOME:
1. Develop a comprehensive understanding of singly linked lists, including their structure and
node traversal.
2. Master the implementation of insertion operations (at the beginning, end, and middle) with
correct pointer manipulation.
3. Demonstrate proficiency in deletion operations, ensuring proper reorganization of pointers
to maintain list integrity.
4. Apply traversal techniques to efficiently access and process data within a singly linked list.
5. Analyze the time and space complexity of each operation, fostering a deeper understanding
of algorithmic efficiency in linked list manipulation.
EXPERIMENT 9
AIM: Implement doubly linked list with all operations.
THEORY: A doubly linked list is a linear data structure comprising nodes with two pointers:
one pointing to the next node and another to the previous node. Operations include insertion,
deletion, and traversal. Insertion can occur at the beginning, end, or middle of the list,
requiring updates to adjacent pointers. Deletion removes a node, necessitating adjustment of
adjacent pointers to maintain connectivity. Traversal involves moving forward or backward
through the list by following pointers. Implementing doubly linked lists demands careful
management of both forward and backward pointers to ensure efficient data manipulation.
Mastery of these operations facilitates efficient data storage and retrieval in various
applications.
SOURCE CODE:#include <stdio.h>
#include <stdlib.h>

// Doubly linked list node structure


struct Node {
int data;
struct Node* prev;
struct Node* next;
};

typedef struct Node Node;

// Function to create a new node


Node* createNode(int data) {
Node* newNode = (Node*)malloc(sizeof(Node));
if (newNode == NULL) {
printf("Memory allocation failed.\n");
exit(1);
}
newNode->data = data;
newNode->prev = NULL;
newNode->next = NULL;
return newNode;
}

// Function to insert a new node at the beginning of the list


void insertAtBeginning(Node** headRef, int data) {
Node* newNode = createNode(data);
if (*headRef == NULL) {
*headRef = newNode;
return;
}
newNode->next = *headRef;
(*headRef)->prev = newNode;
*headRef = newNode;
}

// Function to insert a new node at the end of the list


void insertAtEnd(Node** headRef, int data) {
Node* newNode = createNode(data);
if (*headRef == NULL) {
*headRef = newNode;
return;
}
Node* lastNode = *headRef;
while (lastNode->next != NULL) {
lastNode = lastNode->next;
}
lastNode->next = newNode;
newNode->prev = lastNode;
}

// Function to insert a new node at a specific position


void insertAtPosition(Node** headRef, int position, int data)
{
if (position == 0) {
insertAtBeginning(headRef, data);
return;
}
Node* newNode = createNode(data);
Node* current = *headRef;
for (int i = 0; i < position - 1 && current != NULL; i++)
{
current = current->next;
}
if (current == NULL) {
printf("Position out of range.\n");
return;
}
newNode->next = current->next;
if (current->next != NULL) {
current->next->prev = newNode;
}
newNode->prev = current;
current->next = newNode;
}

// Function to delete a node with given key


void deleteNode(Node** headRef, int key) {
Node* current = *headRef;
if (current != NULL && current->data == key) {
*headRef = current->next;
if (*headRef != NULL) {
(*headRef)->prev = NULL;
}
free(current);
return;
}
while (current != NULL && current->data != key) {
current = current->next;
}
if (current == NULL) {
printf("Key not found.\n");
return;
}
if (current->prev != NULL) {
current->prev->next = current->next;
}
if (current->next != NULL) {
current->next->prev = current->prev;
}
free(current);
}

// Function to search for a key in the list


int search(Node* head, int key) {
Node* current = head;
while (current != NULL) {
if (current->data == key) {
return 1; // Key found
}
current = current->next;
}
return 0; // Key not found
}

// Function to display the linked list


void display(Node* head) {
Node* current = head;
while (current != NULL) {
printf("%d <-> ", current->data);
current = current->next;
}
printf("NULL\n");
}

// Main function
int main() {
Node* head = NULL;
insertAtBeginning(&head, 5);
insertAtBeginning(&head, 10);
insertAtEnd(&head, 15);
insertAtEnd(&head, 20);
insertAtPosition(&head, 2, 12);

display(head); // Output: 10 <-> 5 <-> 12 <-> 15 <-> 20


<-> NULL

deleteNode(&head, 12);
display(head); // Output: 10 <-> 5 <-> 15 <-> 20 <-> NULL

printf("%s\n", search(head, 5) ? "Key found" : "Key not


found"); // Output: Key found
printf("%s\n", search(head, 25) ? "Key found" : "Key not
found"); // Output: Key not found

return 0;
}

OUTPUT:

LEARNING OUTCOME:
1. Gain a deep understanding of doubly linked lists, including their node structure and
bidirectional traversal.
2. Develop proficiency in implementing insertion operations (at the beginning, end, and
middle) with accurate pointer manipulation for both forward and backward links.
3. Master deletion operations, ensuring proper adjustment of adjacent pointers to maintain list
integrity in both directions.
4. Apply traversal techniques to efficiently navigate through the list in both forward and
backward directions.
5. Analyze the time and space complexity of each operation, fostering a comprehensive
understanding of algorithmic efficiency in doubly linked list manipulation.
EXPERIMENT 10
AIM: Implement circular linked list with all operations.
THEORY: A circular linked list is a data structure where the last node points back to the
first, forming a circular arrangement. Operations include insertion, deletion, and traversal.
Insertion involves adding nodes at the beginning, end, or middle, with proper adjustment of
pointers to maintain circular connectivity. Deletion removes nodes, necessitating updates to
adjacent pointers to preserve circularity. Traversal requires careful handling to avoid infinite
loops while visiting each node exactly once. Implementing circular linked lists demands
meticulous pointer management to ensure seamless circular navigation. Mastery of these
operations enables efficient storage and manipulation of data in applications requiring
cyclical data structures.
SOURCE CODE:#include <stdio.h>
#include <stdlib.h>

// Circular linked list node structure


struct Node {
int data;
struct Node* next;
};

typedef struct Node Node;

// Function to create a new node


Node* createNode(int data) {
Node* newNode = (Node*)malloc(sizeof(Node));
if (newNode == NULL) {
printf("Memory allocation failed.\n");
exit(1);
}
newNode->data = data;
newNode->next = NULL;
return newNode;
}

// Function to insert a new node at the beginning of the list


void insertAtBeginning(Node** headRef, int data) {
Node* newNode = createNode(data);
if (*headRef == NULL) {
newNode->next = newNode;
*headRef = newNode;
return;
}
Node* lastNode = *headRef;
while (lastNode->next != *headRef) {
lastNode = lastNode->next;
}
newNode->next = *headRef;
lastNode->next = newNode;
*headRef = newNode;
}

// Function to insert a new node at the end of the list


void insertAtEnd(Node** headRef, int data) {
Node* newNode = createNode(data);
if (*headRef == NULL) {
newNode->next = newNode;
*headRef = newNode;
return;
}
Node* lastNode = *headRef;
while (lastNode->next != *headRef) {
lastNode = lastNode->next;
}
newNode->next = *headRef;
lastNode->next = newNode;
}

// Function to insert a new node at a specific position


void insertAtPosition(Node** headRef, int position, int data)
{
if (position == 0) {
insertAtBeginning(headRef, data);
return;
}
Node* newNode = createNode(data);
Node* current = *headRef;
for (int i = 0; i < position - 1 && current->next !=
*headRef; i++) {
current = current->next;
}
newNode->next = current->next;
current->next = newNode;
}

// Function to delete a node with given key


void deleteNode(Node** headRef, int key) {
if (*headRef == NULL) {
printf("List is empty.\n");
return;
}
Node* current = *headRef;
Node* prev = NULL;
while (current->data != key) {
if (current->next == *headRef) {
printf("Key not found.\n");
return;
}
prev = current;
current = current->next;
}
if (current->next == *headRef && prev == NULL) { // Only
one node in the list
free(current);
*headRef = NULL;
return;
}
if (current == *headRef) { // If the node to be deleted is
head
prev = *headRef;
while (prev->next != *headRef) {
prev = prev->next;
}
*headRef = (*headRef)->next;
prev->next = *headRef;
free(current);
} else if (current->next == *headRef) { // If the node to
be deleted is the last node
prev->next = *headRef;
free(current);
} else { // If the node to be deleted is neither head nor
last
prev->next = current->next;
free(current);
}
}

// Function to search for a key in the list


int search(Node* head, int key) {
if (head == NULL) {
return 0;
}
Node* current = head;
do {
if (current->data == key) {
return 1; // Key found
}
current = current->next;
} while (current != head);
return 0; // Key not found
}

// Function to display the circular linked list


void display(Node* head) {
if (head == NULL) {
printf("List is empty.\n");
return;
}
Node* current = head;
do {
printf("%d -> ", current->data);
current = current->next;
} while (current != head);
printf("(head)\n");
}

// Main function
int main() {
Node* head = NULL;

insertAtBeginning(&head, 5);
insertAtBeginning(&head, 10);
insertAtEnd(&head, 15);
insertAtEnd(&head, 20);
insertAtPosition(&head, 2, 12);

display(head); // Output: 10 -> 5 -> 12 -> 15 -> 20 ->


(head)

deleteNode(&head, 12);
display(head); // Output: 10 -> 5 -> 15 -> 20 -> (head)

printf("%s\n", search(head, 5) ? "Key found" : "Key not


found"); // Output: Key found
printf("%s\n", search(head, 25) ? "Key found" : "Key not
found"); // Output: Key not found
return 0;
}

OUTPUT:

LEARNING OUTCOME:
1. Develop a comprehensive understanding of circular linked lists, including their unique
structure and traversal.
2. Master the implementation of insertion operations (at the beginning, end, and middle),
ensuring proper adjustment of pointers to maintain circular connectivity.
3. Demonstrate proficiency in deletion operations, accurately updating pointers to remove
nodes while preserving circularity.
4. Apply traversal techniques to navigate the circular list efficiently, ensuring each node is
visited exactly once.
5. Analyze the time and space complexity of each operation, fostering a deeper understanding
of algorithmic efficiency in circular linked list manipulation.

EXPERIMENT 11
AIM: Implement reversing of a Singly linked list.
THEORY: Reversing a singly linked list involves changing the direction of pointers to flip
the list end-to-end. The process typically includes iterating through the list while reassigning
each node's pointer to its preceding node, effectively reversing the order. Implementation can
be iterative or recursive, with careful consideration of pointer manipulation to avoid losing
connectivity. Reversing a singly linked list facilitates efficient traversal from the previous tail
to the new head, enabling operations like searching or sorting in reverse order. Mastery of
this operation enhances understanding of pointer manipulation and algorithmic problem-
solving in linked list manipulation tasks.
SOURCE CODE:#include <stdio.h>
#include <stdlib.h>

// Node structure for singly linked list


struct Node {
int data;
struct Node* next;
};

typedef struct Node Node;

// Function to create a new node


Node* createNode(int data) {
Node* newNode = (Node*)malloc(sizeof(Node));
if (newNode == NULL) {
printf("Memory allocation failed.\n");
exit(1);
}
newNode->data = data;
newNode->next = NULL;
return newNode;
}

// Function to insert a new node at the beginning of the


linked list
Node* insert(Node* head, int data) {
Node* newNode = createNode(data);
newNode->next = head;
return newNode;
}

// Function to print the linked list


void printList(Node* head) {
Node* current = head;
while (current != NULL) {
printf("%d ", current->data);
current = current->next;
}
printf("\n");
}

// Function to reverse the linked list


Node* reverseList(Node* head) {
Node* prev = NULL;
Node* current = head;
Node* next = NULL;
while (current != NULL) {
next = current->next; // Store the next node
current->next = prev; // Reverse the current node's
pointer
prev = current; // Move pointers one position ahead
current = next;
}
return prev; // New head of the reversed list
}

// Main function
int main() {
Node* head = NULL;

// Insert nodes into the linked list


head = insert(head, 1);
head = insert(head, 2);
head = insert(head, 3);
head = insert(head, 4);
head = insert(head, 5);

printf("Original list: ");


printList(head); // Output: 5 4 3 2 1
// Reverse the linked list
head = reverseList(head);

printf("Reversed list: ");


printList(head); // Output: 1 2 3 4 5

return 0;
}

OUTPUT:

LEARNING OUTCOME:
1. Understand the concept of reversing a singly linked list, comprehending its significance in
data manipulation.
2. Implement iterative and recursive algorithms for reversing a singly linked list,
demonstrating proficiency in pointer manipulation.
3. Analyze the time and space complexity of both iterative and recursive approaches to
reversing a singly linked list.
4. Apply the reversed singly linked list in various problem-solving scenarios, such as
palindrome detection or efficiently accessing elements in reverse order.
5. Evaluate the effectiveness of different reversal techniques, fostering a deeper
understanding of algorithmic optimization and data structure manipulation.
EXPERIMENT 12
AIM: - Implement reversing of a Doubly linked list.
THEORY: Reversing a doubly linked list involves altering the direction of both forward and
backward pointers to invert the list. The process requires traversing the list while swapping
the pointers of each node with its previous and next nodes, effectively reversing the order.
Implementation can be iterative or recursive, ensuring proper adjustment of pointers to
maintain bidirectional connectivity. Reversing a doubly linked list enables efficient traversal
from the previous tail to the new head and vice versa, enhancing operations like searching or
sorting in reverse order. Mastery of this operation deepens understanding of bidirectional
pointer manipulation and algorithmic strategies in linked list manipulation tasks.
SOURCE CODE:#include <stdio.h>
#include <stdlib.h>

// Doubly linked list node structure


struct Node {
int data;
struct Node* prev;
struct Node* next;
};

typedef struct Node Node;

// Function to create a new node


Node* createNode(int data) {
Node* newNode = (Node*)malloc(sizeof(Node));
if (newNode == NULL) {
printf("Memory allocation failed.\n");
exit(1);
}
newNode->data = data;
newNode->prev = NULL;
newNode->next = NULL;
return newNode;
}
// Function to insert a new node at the end of the list
void insertAtEnd(Node** headRef, int data) {
Node* newNode = createNode(data);
if (*headRef == NULL) {
*headRef = newNode;
return;
}
Node* lastNode = *headRef;
while (lastNode->next != NULL) {
lastNode = lastNode->next;
}
lastNode->next = newNode;
newNode->prev = lastNode;
}

// Function to display the doubly linked list


void display(Node* head) {
Node* current = head;
while (current != NULL) {
printf("%d <-> ", current->data);
current = current->next;
}
printf("NULL\n");
}

// Function to reverse the doubly linked list


void reverse(Node** headRef) {
Node *current = *headRef, *temp = NULL;
while (current != NULL) {
// Swapping prev and next pointers of the current node
temp = current->prev;
current->prev = current->next;
current->next = temp;
// Move to the next node
current = current->prev;
}
// Check if head is not NULL
if (temp != NULL) {
// Update head to the last node (which is now first
after reversal)
*headRef = temp->prev;
}
}

// Main function
int main() {
Node* head = NULL;

// Insert elements into the doubly linked list


insertAtEnd(&head, 1);
insertAtEnd(&head, 2);
insertAtEnd(&head, 3);
insertAtEnd(&head, 4);
insertAtEnd(&head, 5);

printf("Original doubly linked list:\n");


display(head); // Output: 1 <-> 2 <-> 3 <-> 4 <-> 5 <->
NULL

// Reverse the doubly linked list


reverse(&head);

printf("\nReversed doubly linked list:\n");


display(head); // Output: 5 <-> 4 <-> 3 <-> 2 <-> 1 <->
NULL

return 0;
}

OUTPUT:

LEARNING OUTCOME:
1. Grasp the concept of reversing a doubly linked list, understanding its importance in
bidirectional data manipulation.
2. Implement iterative and recursive algorithms for reversing a doubly linked list,
demonstrating proficiency in bidirectional pointer manipulation.
3. Analyze the time and space complexity of both iterative and recursive approaches to
reversing a doubly linked list.
4. Apply the reversed doubly linked list in various problem-solving scenarios, such as
efficient traversal in both forward and reverse directions.
5. Evaluate the effectiveness of different reversal techniques, fostering a deeper
understanding of algorithmic optimization and bidirectional data structure manipulation.

EXPERIMENT 13
AIM: Implement Binary Trees using Arrays.
THEORY: Implementing binary trees using arrays involves mapping tree nodes to array
indices, optimizing memory usage while enabling efficient operations. In this approach, the
root node is stored at index 0, with each node's left child at index 2*i + 1 and right child at
index 2*i + 2. Operations include insertion, deletion, and traversal. Insertion involves placing
elements at the next available index, while deletion requires maintaining tree balance.
Traversal methods like inorder, preorder, and postorder are adapted to navigate the array
representation efficiently. Implementing binary trees with arrays offers a balance between
memory efficiency and ease of traversal, facilitating various tree-based algorithms and
applications.

SOURCE CODE:#include <stdio.h>


#include <stdlib.h>
#define MAX_SIZE 100

// Binary tree node structure


struct TreeNode {
int data;
};

typedef struct TreeNode TreeNode;

// Function to create a new tree node


TreeNode* createNode(int data) {
TreeNode* newNode = (TreeNode*)malloc(sizeof(TreeNode));
if (newNode == NULL) {
printf("Memory allocation failed.\n");
exit(1);
}
newNode->data = data;
return newNode;
}

// Function to insert a new node in the binary tree


void insert(TreeNode* tree[], int index, int data) {
tree[index] = createNode(data);
}

// Function to get the left child index of a node


int leftChild(int index) {
return (2 * index) + 1;
}

// Function to get the right child index of a node


int rightChild(int index) {
return (2 * index) + 2;
}

// Function to display the binary tree using array


representation
void display(TreeNode* tree[], int index, int size) {
if (index < size && tree[index] != NULL) {
printf("%d ", tree[index]->data);
display(tree, leftChild(index), size);
display(tree, rightChild(index), size);
}
}

// Main function
int main() {
TreeNode* tree[MAX_SIZE] = {NULL};
int size = 0; // Number of nodes in the tree

// Insert nodes into the binary tree


insert(tree, 0, 1);
insert(tree, 1, 2);
insert(tree, 2, 3);
insert(tree, 3, 4);
insert(tree, 4, 5);

size = 5; // Update the size

// Display the binary tree


printf("Binary tree represented using arrays:\n");
display(tree, 0, size); // Output: 1 2 4 5 3

return 0;
}

OUTPUT:

LEARNING OUTCOME:
1. Understand the concept of representing binary trees using arrays, grasping the mapping
between tree nodes and array indices.
2. Implement efficient algorithms for insertion and deletion in a binary tree using array-based
representation, ensuring proper adjustment of array indices.
3. Master traversal techniques adapted to array-based binary trees, including inorder,
preorder, and postorder traversal, for efficient navigation.
4. Analyze the time and space complexity of operations in array-based binary trees, fostering
a deeper understanding of algorithmic efficiency.
5. Apply array-based binary trees in various problem-solving scenarios, such as binary search
tree operations or heap-based data structures, demonstrating proficiency in data structure
manipulation and algorithmic optimization.

EXPERIMENT 14
AIM: Implement Binary Tress using Linked List.
THEORY: Implementing binary trees using linked lists involves each node containing
pointers to its left and right children. In this approach, nodes are dynamically allocated,
allowing for flexible memory usage. Operations include insertion, deletion, and traversal.
Insertion involves recursively traversing the tree to find the appropriate position for the new
node. Deletion requires handling cases such as nodes with one or two children. Traversal
methods like inorder, preorder, and postorder recursively visit nodes to process data
efficiently. Implementing binary trees with linked lists offers ease of insertion and deletion,
making it suitable for dynamic data structures and algorithms requiring frequent
modifications.
SOURCE CODE:#include <stdio.h>
#include <stdlib.h>

// Binary tree node structure


struct TreeNode {
int data;
struct TreeNode* left;
struct TreeNode* right;
};

typedef struct TreeNode TreeNode;

// Function to create a new tree node


TreeNode* createNode(int data) {
TreeNode* newNode = (TreeNode*)malloc(sizeof(TreeNode));
if (newNode == NULL) {
printf("Memory allocation failed.\n");
exit(1);
}
newNode->data = data;
newNode->left = NULL;
newNode->right = NULL;
return newNode;
}

// Function to insert a new node into the binary tree


TreeNode* insert(TreeNode* root, int data) {
if (root == NULL) {
return createNode(data);
}
if (data < root->data) {
root->left = insert(root->left, data);
} else if (data > root->data) {
root->right = insert(root->right, data);
}
return root;
}

// Function to perform an in-order traversal of the binary


tree
void inOrderTraversal(TreeNode* root) {
if (root != NULL) {
inOrderTraversal(root->left);
printf("%d ", root->data);
inOrderTraversal(root->right);
}
}

// Main function
int main() {
TreeNode* root = NULL;

// Insert nodes into the binary tree


root = insert(root, 50);
insert(root, 30);
insert(root, 20);
insert(root, 40);
insert(root, 70);
insert(root, 60);
insert(root, 80);

printf("In-order traversal: ");


inOrderTraversal(root); // Output: 20 30 40 50 60 70 80
printf("\n");

return 0;
}

OUTPUT:

LEARNING OUTCOME:
1. Develop a comprehensive understanding of binary trees implemented using linked lists,
including the structure of tree nodes and pointer management.
2. Implement insertion and deletion operations in a binary tree using linked list
representation, ensuring proper handling of pointers for tree modification.
3. Master traversal techniques adapted to linked list-based binary trees, including inorder,
preorder, and postorder traversal, for efficient data processing.
4. Analyze the time and space complexity of operations in linked list-based binary trees,
fostering a deeper understanding of algorithmic efficiency.
5. Apply linked list-based binary trees in various problem-solving scenarios, such as binary
search tree operations or hierarchical data representation, demonstrating proficiency in
dynamic data structure manipulation and algorithmic optimization.
EXPERIMENT 15
AIM: Implement insertion and deletion on binary search tree.
THEORY: Insertion in a binary search tree (BST) involves recursively comparing the value
of the new node with nodes in the tree. Starting from the root, the algorithm traverses left or
right based on comparison results until a suitable position is found. Once located, the new
node is inserted as a leaf node, maintaining the BST property. Deletion in a BST requires
careful handling to preserve the tree's structure and ordering. Cases include removing a leaf
node, a node with one child, or a node with two children. Adjustments are made to reorganize
the tree while preserving the BST property.
SOURCE CODE:#include <stdio.h>
#include <stdlib.h>

// Binary search tree node structure


struct TreeNode {
int data;
struct TreeNode* left;
struct TreeNode* right;
};

typedef struct TreeNode TreeNode;

// Function to create a new tree node


TreeNode* createNode(int data) {
TreeNode* newNode = (TreeNode*)malloc(sizeof(TreeNode));
if (newNode == NULL) {
printf("Memory allocation failed.\n");
exit(1);
}
newNode->data = data;
newNode->left = NULL;
newNode->right = NULL;
return newNode;
}

// Function to insert a new node into the binary search tree


TreeNode* insert(TreeNode* root, int data) {
if (root == NULL) {
return createNode(data);
}
if (data < root->data) {
root->left = insert(root->left, data);
} else if (data > root->data) {
root->right = insert(root->right, data);
}
return root;
}

// Function to find the minimum value node in a binary search


tree
TreeNode* findMin(TreeNode* node) {
TreeNode* current = node;
while (current && current->left != NULL) {
current = current->left;
}
return current;
}

// Function to delete a node from the binary search tree


TreeNode* deleteNode(TreeNode* root, int data) {
if (root == NULL) {
return root;
}
// Search for the node to be deleted
if (data < root->data) {
root->left = deleteNode(root->left, data);
} else if (data > root->data) {
root->right = deleteNode(root->right, data);
} else {
// Node with only one child or no child
if (root->left == NULL) {
TreeNode* temp = root->right;
free(root);
return temp;
} else if (root->right == NULL) {
TreeNode* temp = root->left;
free(root);
return temp;
}
// Node with two children: Get the inorder successor
(smallest in the right subtree)
TreeNode* temp = findMin(root->right);
// Copy the inorder successor's content to this node
root->data = temp->data;
// Delete the inorder successor
root->right = deleteNode(root->right, temp->data);
}
return root;
}

// Function to perform an in-order traversal of the binary


search tree
void inOrderTraversal(TreeNode* root) {
if (root != NULL) {
inOrderTraversal(root->left);
printf("%d ", root->data);
inOrderTraversal(root->right);
}
}

// Main function
int main() {
TreeNode* root = NULL;

// Insert nodes into the binary search tree


root = insert(root, 50);
insert(root, 30);
insert(root, 20);
insert(root, 40);
insert(root, 70);
insert(root, 60);
insert(root, 80);

printf("Binary search tree after insertion:\n");


inOrderTraversal(root); // Output: 20 30 40 50 60 70 80
printf("\n");

// Delete nodes from the binary search tree


root = deleteNode(root, 20);
root = deleteNode(root, 30);
root = deleteNode(root, 50);

printf("Binary search tree after deletion:\n");


inOrderTraversal(root); // Output: 40 60 70 80
printf("\n");

return 0;
}

OUTPUT:

LEARNING OUTCOME:
1. Gain a thorough understanding of insertion algorithms in binary search trees (BSTs),
comprehending the importance of maintaining the BST property.
2. Implement insertion operations in a BST, demonstrating proficiency in recursively
comparing and traversing nodes to maintain ordering.
3. Master deletion algorithms in BSTs, including handling cases of removing leaf nodes,
nodes with one child, and nodes with two children.
4. Apply insertion and deletion techniques in various problem-solving scenarios, such as
maintaining sorted data or efficiently organizing hierarchical structures.
5. Analyze the time and space complexity of insertion and deletion operations in BSTs,
fostering a deeper understanding of algorithmic efficiency in dynamic data structures.

EXPERIMENT 16
AIM: Implement DFS on binary trees.
THEORY: Depth-First Search (DFS) is a traversal algorithm used to explore binary trees
systematically. It starts at the root node and explores as far as possible along each branch
before backtracking. In binary trees, DFS typically involves three traversal methods: inorder,
preorder, and postorder. In inorder traversal, nodes are visited in ascending order. Preorder
traversal visits the root node first, followed by the left and right subtrees. Postorder traversal
visits the left and right subtrees before the root node. DFS on binary trees facilitates various
operations like searching, sorting, and expression evaluation, providing efficient data
exploration in tree structures.
SOURCE CODE:#include <stdio.h>
#include <stdlib.h>

// Binary tree node structure


struct TreeNode {
int data;
struct TreeNode* left;
struct TreeNode* right;
};

typedef struct TreeNode TreeNode;

// Function to create a new tree node


TreeNode* createNode(int data) {
TreeNode* newNode = (TreeNode*)malloc(sizeof(TreeNode));
if (newNode == NULL) {
printf("Memory allocation failed.\n");
exit(1);
}
newNode->data = data;
newNode->left = NULL;
newNode->right = NULL;
return newNode;
}

// Function for Depth-First Search (DFS) traversal - Pre-order


void preOrderTraversal(TreeNode* root) {
if (root != NULL) {
printf("%d ", root->data); // Process the current node
preOrderTraversal(root->left); // Recur on left
subtree
preOrderTraversal(root->right); // Recur on right
subtree
}
}

// Function for Depth-First Search (DFS) traversal - In-order


void inOrderTraversal(TreeNode* root) {
if (root != NULL) {
inOrderTraversal(root->left); // Recur on left subtree
printf("%d ", root->data); // Process the current node
inOrderTraversal(root->right); // Recur on right
subtree
}
}

// Function for Depth-First Search (DFS) traversal - Post-


order
void postOrderTraversal(TreeNode* root) {
if (root != NULL) {
postOrderTraversal(root->left); // Recur on left
subtree
postOrderTraversal(root->right); // Recur on right
subtree
printf("%d ", root->data); // Process the current node
}
}

// Main function
int main() {
// Create a binary tree
TreeNode* root = createNode(1);
root->left = createNode(2);
root->right = createNode(3);
root->left->left = createNode(4);
root->left->right = createNode(5);

printf("Pre-order traversal: ");


preOrderTraversal(root); // Output: 1 2 4 5 3
printf("\n");

printf("In-order traversal: ");


inOrderTraversal(root); // Output: 4 2 5 1 3
printf("\n");

printf("Post-order traversal: ");


postOrderTraversal(root); // Output: 4 5 2 3 1
printf("\n");

return 0;
}
#include <stdio.h>
#include <stdlib.h>

// Binary tree node structure


struct TreeNode {
int data;
struct TreeNode* left;
struct TreeNode* right;
};

typedef struct TreeNode TreeNode;

// Function to create a new tree node


TreeNode* createNode(int data) {
TreeNode* newNode = (TreeNode*)malloc(sizeof(TreeNode));
if (newNode == NULL) {
printf("Memory allocation failed.\n");
exit(1);
}
newNode->data = data;
newNode->left = NULL;
newNode->right = NULL;
return newNode;
}

// Function for Depth-First Search (DFS) traversal - Pre-order


void preOrderTraversal(TreeNode* root) {
if (root != NULL) {
printf("%d ", root->data); // Process the current node
preOrderTraversal(root->left); // Recur on left
subtree
preOrderTraversal(root->right); // Recur on right
subtree
}
}

// Function for Depth-First Search (DFS) traversal - In-order


void inOrderTraversal(TreeNode* root) {
if (root != NULL) {
inOrderTraversal(root->left); // Recur on left subtree
printf("%d ", root->data); // Process the current node
inOrderTraversal(root->right); // Recur on right
subtree
}
}

// Function for Depth-First Search (DFS) traversal - Post-


order
void postOrderTraversal(TreeNode* root) {
if (root != NULL) {
postOrderTraversal(root->left); // Recur on left
subtree
postOrderTraversal(root->right); // Recur on right
subtree
printf("%d ", root->data); // Process the current node
}
}

// Main function
int main() {
// Create a binary tree
TreeNode* root = createNode(1);
root->left = createNode(2);
root->right = createNode(3);
root->left->left = createNode(4);
root->left->right = createNode(5);

printf("Pre-order traversal: ");


preOrderTraversal(root); // Output: 1 2 4 5 3
printf("\n");

printf("In-order traversal: ");


inOrderTraversal(root); // Output: 4 2 5 1 3
printf("\n");
printf("Post-order traversal: ");
postOrderTraversal(root); // Output: 4 5 2 3 1
printf("\n");

return 0;
}

OUTPUT:

LEARNING OUTCOME:
1. Develop a comprehensive understanding of Depth-First Search (DFS) traversal algorithms
applied to binary trees, including inorder, preorder, and postorder methods.
2. Implement DFS algorithms effectively to systematically explore binary tree structures,
grasping the concept of recursive traversal.
3. Master the application of DFS for tasks such as searching, sorting, and expression
evaluation in binary trees, demonstrating proficiency in data exploration.
4. Analyze the differences between inorder, preorder, and postorder DFS traversal methods,
fostering a deeper understanding of their respective applications.
5. Apply DFS traversal techniques to solve diverse problem-solving scenarios, enhancing
algorithmic proficiency in tree-based data structures.
EXPERIMENT 17
AIM: Implement BFS on binary trees.
THEORY: Breadth-First Search (BFS) is a traversal algorithm used to explore binary trees
level by level. It starts at the root node and systematically visits each level, exploring all
nodes at the current level before moving to the next. In binary trees, BFS traverses nodes in a
left-to-right fashion, facilitating operations such as level-order traversal. BFS on binary trees
is efficient for tasks like finding the shortest path, level-order printing, or hierarchical data
exploration. By systematically exploring all nodes at each level, BFS ensures optimal
exploration of binary tree structures, enabling a wide range of applications in tree-based
algorithms and data structures.
SOURCE CODE-
#include <stdio.h>
#include <stdlib.h>

// Binary tree node structure


struct TreeNode {
int data;
struct TreeNode* left;
struct TreeNode* right;
};

typedef struct TreeNode TreeNode;

// Structure for a queue node


struct QueueNode {
TreeNode* treeNode;
struct QueueNode* next;
};

typedef struct QueueNode QueueNode;

// Structure for a queue


struct Queue {
QueueNode* front;
QueueNode* rear;
};
typedef struct Queue Queue;

// Function to create a new tree node


TreeNode* createNode(int data) {
TreeNode* newNode = (TreeNode*)malloc(sizeof(TreeNode));
if (newNode == NULL) {
printf("Memory allocation failed.\n");
exit(1);
}
newNode->data = data;
newNode->left = NULL;
newNode->right = NULL;
return newNode;
}

// Function to create a new queue node


QueueNode* createQueueNode(TreeNode* treeNode) {
QueueNode* newNode =
(QueueNode*)malloc(sizeof(QueueNode));
if (newNode == NULL) {
printf("Memory allocation failed.\n");
exit(1);
}
newNode->treeNode = treeNode;
newNode->next = NULL;
return newNode;
}

// Function to create an empty queue


Queue* createQueue() {
Queue* queue = (Queue*)malloc(sizeof(Queue));
if (queue == NULL) {
printf("Memory allocation failed.\n");
exit(1);
}
queue->front = NULL;
queue->rear = NULL;
return queue;
}

// Function to check if the queue is empty


int isEmpty(Queue* queue) {
return (queue->front == NULL);
}

// Function to enqueue a tree node into the queue


void enqueue(Queue* queue, TreeNode* treeNode) {
QueueNode* newNode = createQueueNode(treeNode);
if (isEmpty(queue)) {
queue->front = newNode;
queue->rear = newNode;
} else {
queue->rear->next = newNode;
queue->rear = newNode;
}
}

// Function to dequeue a tree node from the queue


TreeNode* dequeue(Queue* queue) {
if (isEmpty(queue)) {
printf("Queue is empty.\n");
exit(1);
}
QueueNode* temp = queue->front;
TreeNode* treeNode = temp->treeNode;
queue->front = queue->front->next;
free(temp);
return treeNode;
}

// Function for Breadth-First Search (BFS) traversal


void bfsTraversal(TreeNode* root) {
if (root == NULL) {
return;
}
Queue* queue = createQueue();
enqueue(queue, root);
while (!isEmpty(queue)) {
TreeNode* current = dequeue(queue);
printf("%d ", current->data);
if (current->left != NULL) {
enqueue(queue, current->left);
}
if (current->right != NULL) {
enqueue(queue, current->right);
}
}
}

// Main function
int main() {
// Create a binary tree
TreeNode* root = createNode(1);
root->left = createNode(2);
root->right = createNode(3);
root->left->left = createNode(4);
root->left->right = createNode(5);

printf("BFS traversal: ");


bfsTraversal(root); // Output: 1 2 3 4 5
printf("\n");

return 0;
}

OUTPUT:

LEARNING OUTCOME:
1. Gain a comprehensive understanding of Breadth-First Search (BFS) traversal applied to
binary trees, focusing on exploring levels systematically.
2. Implement BFS algorithms effectively to traverse binary tree structures in a level-by-level
manner, grasping the concept of queue-based traversal.
3. Master the application of BFS for tasks such as finding the shortest path, level-order
printing, or hierarchical data exploration in binary trees.
4. Analyze the efficiency of BFS compared to other traversal methods, fostering a deeper
understanding of its advantages in certain scenarios.
5. Apply BFS traversal techniques to solve diverse problem-solving scenarios, enhancing
algorithmic proficiency in tree-based data structures.

EXPERIMENT 18
AIM: Implement inorder, preorder and postorder traversal on trees.
THEORY: Inorder, preorder, and postorder are three traversal methods used to explore tree
structures systematically. In inorder traversal, nodes are visited in ascending order, starting
from the leftmost subtree, then the root, and finally the right subtree. Preorder traversal visits
the root node first, followed by the left and right subtrees recursively. Postorder traversal
visits the left and right subtrees before the root node. These traversal methods facilitate
various operations like searching, sorting, and expression evaluation in trees, offering
different perspectives on tree exploration and manipulation, each with its unique applications
and advantages in algorithmic problem-solving.
SOURCE CODE:

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

// Structure for a binary tree node


struct TreeNode {
int data;
struct TreeNode* left;
struct TreeNode* right;
};

// Function to create a new binary tree node


struct TreeNode* createNode(int data) {
struct TreeNode* newNode = (struct TreeNode*)malloc(sizeof(struct TreeNode));
if (newNode == NULL) {
printf("Memory allocation failed\n");
exit(1);
}
newNode->data = data;
newNode->left = NULL;
newNode->right = NULL;
return newNode;
}

// Inorder traversal
void inorderTraversal(struct TreeNode* root) {
if (root != NULL) {
inorderTraversal(root->left);
printf("%d ", root->data);
inorderTraversal(root->right);
}
}

// Preorder traversal
void preorderTraversal(struct TreeNode* root) {
if (root != NULL) {
printf("%d ", root->data);
preorderTraversal(root->left);
preorderTraversal(root->right);
}
}

// Postorder traversal
void postorderTraversal(struct TreeNode* root) {
if (root != NULL) {
postorderTraversal(root->left);
postorderTraversal(root->right);
printf("%d ", root->data);
}
}

int main() {
// Create a sample binary tree
struct TreeNode* root = createNode(1);
root->left = createNode(2);
root->right = createNode(3);
root->left->left = createNode(4);
root->left->right = createNode(5);

printf("Inorder traversal: ");


inorderTraversal(root);
printf("\n");

printf("Preorder traversal: ");


preorderTraversal(root);
printf("\n");

printf("Postorder traversal: ");


postorderTraversal(root);
printf("\n");

return 0;}
OUTPUT:

LEARNING OUTCOME:
1. Develop a comprehensive understanding of the three fundamental tree traversal methods:
inorder, preorder, and postorder.
2. Implement each traversal algorithm effectively, demonstrating proficiency in navigating
tree structures recursively.
3. Master the application of inorder traversal for sorting elements in ascending order within a
tree.
4. Apply preorder traversal to efficiently construct expression trees or generate prefix
expressions.
5. Utilize postorder traversal for tasks such as evaluating mathematical expressions or
deleting nodes in a tree.

EXPERIMENT 19
AIM: - Implement the concept of AVL trees.
THEORY: AVL trees are self-balancing binary search trees designed to maintain their
balance through rotations after insertion or deletion operations. Each node in an AVL tree is
associated with a balance factor, ensuring the tree remains balanced by enforcing a maximum
height difference of 1 between the left and right subtrees. Insertion and deletion operations in
AVL trees trigger rotations to restore balance, ensuring efficient search, insertion, and
deletion operations with a worst-case time complexity of O(log n). AVL trees are crucial in
scenarios requiring fast search operations while maintaining a balanced tree structure,
offering optimal performance in dynamic data storage and retrieval applications.
SOURCE CODE:
#include <stdio.h>
#include <stdlib.h>

// Structure for a binary tree node


struct TreeNode {
int data;
struct TreeNode* left;
struct TreeNode* right;
int height; // Height of the node
};

// Function to get the height of a node


int height(struct TreeNode* node) {
if (node == NULL)
return 0;
return node->height;
}
// Function to get the maximum of two integers
int max(int a, int b) {
return (a > b) ? a : b;
}
struct TreeNode* createNode(int data) {
struct TreeNode* newNode = (struct TreeNode*)malloc(sizeof(struct TreeNode));
if (newNode == NULL) {
printf("Memory allocation failed\n");
exit(1);
}
newNode->data = data;
newNode->left = NULL;
newNode->right = NULL;
newNode->height = 1; // New node is initially at height 1
return newNode;
}

struct TreeNode* rightRotate(struct TreeNode* y) {


struct TreeNode* x = y->left;
struct TreeNode* T2 = x->right;
x->right = y;
y->left = T2;
// Update heights
y->height = max(height(y->left), height(y->right)) + 1;
x->height = max(height(x->left), height(x->right)) + 1;
// Return new root
return x;
}
// Function to left rotate subtree rooted with x
struct TreeNode* leftRotate(struct TreeNode* x) {
struct TreeNode* y = x->right;
struct TreeNode* T2 = y->left;
// Perform rotation
y->left = x;
x->right = T2;
// Update heights
x->height = max(height(x->left), height(x->right)) + 1;
y->height = max(height(y->left), height(y->right)) + 1;
// Return new root
return y;
}

int getBalance(struct TreeNode* node) {


if (node == NULL)
return 0;
return height(node->left) - height(node->right);
}
struct TreeNode* insertNode(struct TreeNode* node, int data) {
// Perform the normal BST insertion
if (node == NULL)
return createNode(data);

if (data < node->data)


node->left = insertNode(node->left, data);
else if (data > node->data)
node->right = insertNode(node->right, data);
else // Duplicate data not allowed
return node;

// Update height of this ancestor node


node->height = 1 + max(height(node->left), height(node->right));

int balance = getBalance(node);


// Left Left Case
if (balance > 1 && data < node->left->data)
return rightRotate(node);
// Right Right Case
if (balance < -1 && data > node->right->data)
return leftRotate(node);
// Left Right Case
if (balance > 1 && data > node->left->data) {
node->left = leftRotate(node->left);
return rightRotate(node);
}
// Right Left Case
if (balance < -1 && data < node->right->data) {
node->right = rightRotate(node->right);
return leftRotate(node);
}
return node;
}
// Function to perform inorder traversal of the AVL tree
void inorderTraversal(struct TreeNode* root) {
if (root != NULL) {
inorderTraversal(root->left);
printf("%d ", root->data);
inorderTraversal(root->right);
}
}
int main() {
struct TreeNode* root = NULL;
// Inserting nodes into the AVL tree
root = insertNode(root, 10);
root = insertNode(root, 20);
root = insertNode(root, 30);
root = insertNode(root, 40);
root = insertNode(root, 50);
root = insertNode(root, 25)
printf("Inorder traversal of the AVL tree: ");
inorderTraversal(root);
printf("\n");

return 0;
}

OUTPUT:

LEARNING OUTCOME:
1. Develop a deep understanding of AVL trees, comprehending their self-balancing nature
and the importance of maintaining balance factors for efficient operations.
2. Implement AVL tree algorithms effectively, including insertion, deletion, and rotation
operations, ensuring balanced tree structure at all times.
3. Master the concept of balance factors and their significance in AVL trees, demonstrating
proficiency in detecting and correcting tree imbalances.
4. Apply AVL trees to various problem-solving scenarios, such as dictionary
implementations, where fast search, insertion, and deletion operations are crucial.
5. Analyze the time and space complexity of AVL tree operations, fostering a deeper
understanding of algorithmic efficiency in dynamic data structures.

EXPERIMENT 20
AIM: Implement interpolation search.
THEORY: Interpolation search is a search algorithm used to find a target value within a
sorted array by estimating its position based on the distribution of values. Unlike binary
search, which always selects the middle element, interpolation search calculates the probable
position of the target by linearly interpolating between array elements. This approach is
particularly effective for uniformly distributed data, offering improved performance by
reducing the number of comparisons required. However, in non-uniformly distributed data or
arrays with repeated elements, interpolation search may exhibit suboptimal performance.
Mastery of interpolation search enhances understanding of search algorithms and their
application in various data retrieval tasks.
SOURCE CODE:
#include <stdio.h>

int interpolationSearch(int arr[], int n, int x) {

int low = 0;
int high = n - 1;

while (low <= high && x >= arr[low] && x <= arr[high]) {

if (low == high) {

if (arr[low] == x) {

return low;

return -1;

int pos = low + (((double)(high - low) / (arr[high] - arr[low])) * (x - arr[low]));

if (arr[pos] == x) {

return pos;

if (arr[pos] < x) {

low = pos + 1;

} else {

high = pos - 1;

return -1;

int main() {

int arr[] = {2, 4, 6, 8, 10, 12, 14, 16, 18, 20};


int n = sizeof(arr) / sizeof(arr[0]);

int x = 10;

int index = interpolationSearch(arr, n, x);

if (index != -1) {

printf("Element found at index %d\n", index);

} else {

printf("Element not found\n");

return 0;

OUTPUT:

LEARNING OUTCOME:
1. Gain a thorough understanding of the interpolation search algorithm, comprehending its
principles of estimating the target position based on data distribution.
2. Implement interpolation search effectively, demonstrating proficiency in calculating
interpolation values and refining search boundaries.
3. Analyze the efficiency of interpolation search compared to other search algorithms,
particularly in scenarios with uniformly distributed data.
4. Apply interpolation search to real-world problem-solving scenarios, such as database
searches or data retrieval tasks, showcasing its effectiveness in certain contexts.
5. Evaluate the limitations and considerations of interpolation search, fostering a deeper
understanding of its applicability and performance characteristics.
EXPERIMENT 21
AIM: Implement ternanry search.
THEORY: Ternary search is a divide-and-conquer search algorithm used to find the position
of a target value within a sorted array. It operates by recursively dividing the search interval
into three parts and determining which part contains the target. By repeatedly narrowing
down the search space, ternary search converges towards the target efficiently, typically
outperforming binary search when the array size is large and the distribution of data is
uniform. However, ternary search may exhibit suboptimal performance in certain cases, such
as when the data is not evenly distributed. Mastery of ternary search enriches understanding
of search algorithms and their role in efficient data retrieval tasks.
SOURCE CODE:

#include <stdio.h>

int ternarySearch(int arr[], int l, int r, int x) {


if (r >= l) {

int mid1 = l + (r - l) / 3;

int mid2 = r - (r - l) / 3;

if (arr[mid1] == x) {

return mid1;

if (arr[mid2] == x) {

return mid2;

if (x < arr[mid1]) {

return ternarySearch(arr, l, mid1 - 1, x);

} else if (x > arr[mid2]) {

return ternarySearch(arr, mid2 + 1, r, x);

} else {

return ternarySearch(arr, mid1 + 1, mid2 - 1, x);

return -1;

int main() {

int arr[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

int n = sizeof(arr) / sizeof(arr[0]);

int x = 5;

int index = ternarySearch(arr, 0, n - 1, x);


if (index != -1) {

printf("Element found at index %d\n", index);

} else {

printf("Element not found\n");

return 0;

OUTPUT:

LEARNING OUTCOME:
1. Develop a comprehensive understanding of the ternary search algorithm, grasping its
divide-and-conquer approach to efficiently locate a target value.
2. Implement ternary search effectively, demonstrating proficiency in recursively dividing the
search interval into three parts and determining the target's location.
3. Analyze the performance of ternary search compared to other search algorithms,
particularly in scenarios with large arrays and uniformly distributed data.
4. Apply ternary search to solve real-world problems requiring fast and efficient search
operations, showcasing its effectiveness in certain contexts.
5. Evaluate the limitations and considerations of ternary search, fostering a deeper
understanding of its applicability and performance characteristics in different scenarios.
EXPERIMENT 22
AIM: Implement selection and insertion sort.
THEORY: Selection sort is a comparison-based sorting algorithm that iteratively selects the
smallest element from the unsorted portion of the array and swaps it with the element at the
current position. This process continues until the entire array is sorted. In contrast, insertion
sort builds the sorted portion of the array one element at a time by iteratively placing each
unsorted element into its correct position among the sorted elements. Both algorithms have a
time complexity of O(n^2), making them suitable for small datasets or as a base case for
more efficient sorting algorithms. Mastery of selection and insertion sort enhances
understanding of sorting techniques and algorithmic efficiency.
SOURCE CODE:

#include <stdio.h>

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

int i, j, minIndex, temp;


for (i = 0; i < n - 1; i++) {

minIndex = i;

for (j = i + 1; j < n; j++) {

if (arr[j] < arr[minIndex]) {

minIndex = j;

temp = arr[i];

arr[i] = arr[minIndex];

arr[minIndex] = temp;

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

int i, key, j;

for (i = 1; i < n; i++) {

key = arr[i];

j = i - 1;

while (j >= 0 && arr[j] > key) {

arr[j + 1] = arr[j];

j = j - 1;

arr[j + 1] = key;

}
void printArray(int arr[], int n) {

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

printf("%d ", arr[i]);

printf("\n");

int main() {

int arr[] = {64, 25, 12, 22, 11};

int n = sizeof(arr) / sizeof(arr[0]);

printf("Original Array: ");

printArray(arr, n);

printf("Selection Sort: ");

selectionSort(arr, n);

printArray(arr, n);

printf("Insertion Sort: ");

insertionSort(arr, n);

printArray(arr, n);

return 0;

OUTPUT:
LEARNING OUTCOME:
1. Develop a comprehensive understanding of selection and insertion sort algorithms,
including their comparison-based sorting techniques.
2. Implement selection sort effectively by iteratively selecting the smallest element and
swapping it into the correct position.
3. Master insertion sort algorithms, demonstrating proficiency in iteratively building the
sorted portion of the array by inserting elements into their correct positions.
4. Analyze the time complexity of selection and insertion sort, fostering a deeper
understanding of their performance characteristics for small to moderate-sized datasets.
5. Apply selection and insertion sort techniques to sort arrays efficiently, demonstrating
algorithmic proficiency in sorting algorithms and their practical applications.

EXPERIMENT 23
AIM: Implement quick sort
THEORY: Quick sort is a widely used comparison-based sorting algorithm renowned for its
efficiency and simplicity. It employs a divide-and-conquer strategy, selecting a pivot element
to partition the array into two subarrays: elements less than the pivot and elements greater
than the pivot. These subarrays are recursively sorted, with the base case being an array of
size one or zero. Quick sort is highly efficient, with an average time complexity of O(n log n)
and a worst-case time complexity of O(n^2) when an inappropriate pivot is chosen. Mastery
of quick sort enriches understanding of efficient sorting techniques and algorithmic design
principles.
SOURCE CODE:

#include <stdio.h>

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


int temp = *a;

*a = *b;

*b = temp;

int partition(int arr[], int low, int high) {

int pivot = arr[high];

int i = (low - 1);

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

if (arr[j] <= pivot) {

i++;

swap(&arr[i], &arr[j]);

swap(&arr[i + 1], &arr[high]);

return (i + 1);

void quickSort(int arr[], int low, int high) {

if (low < high) {

int pi = partition(arr, low, high);

quickSort(arr, low, pi - 1);

quickSort(arr, pi + 1, high);

}
void printArray(int arr[], int size) {

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

printf("%d ", arr[i]);

printf("\n");

int main() {

int arr[] = {10, 7, 8, 9, 1, 5};

int n = sizeof(arr) / sizeof(arr[0]);

printf("Original Array: ");

printArray(arr, n);

quickSort(arr, 0, n - 1);

printf("Sorted Array: ");

printArray(arr, n);

return 0;

OUTPUT:

LEARNING OUTCOME:
1. Develop a comprehensive understanding of the quick sort algorithm, including its divide-
and-conquer strategy and pivot selection techniques.
2. Implement quick sort effectively, demonstrating proficiency in partitioning the array and
recursively sorting its subarrays.
3. Master the selection of pivot elements and understand their impact on quick sort's
performance, fostering algorithmic optimization skills.
4. Analyze the time complexity of quick sort, recognizing its efficiency and adaptability to
various dataset sizes and distributions.
5. Apply quick sort to sort arrays efficiently in practical scenarios, showcasing algorithmic
proficiency in sorting algorithms and their applications.

EXPERIMENT 24
AIM: Implement merge sort.
THEORY: Merge sort is a comparison-based sorting algorithm known for its stability and
efficiency. It employs a divide-and-conquer strategy, recursively splitting the array into
smaller subarrays until each subarray contains only one element. These subarrays are then
merged in a sorted manner, combining two sorted arrays into a single sorted array. Merge sort
guarantees a time complexity of O(n log n) in all cases, making it suitable for sorting large
datasets efficiently. Its stability and predictable performance contribute to its widespread use
in various applications, from sorting arrays to sorting linked lists and external sorting.
Mastery of merge sort enhances algorithmic understanding and problem-solving skills.
SOURCE CODE:

#include <stdio.h>

void merge(int arr[], int l, int m, int r) {


int i, j, k;

int n1 = m - l + 1;

int n2 = r - m;

int L[n1], R[n2];

for (i = 0; i < n1; i++)

L[i] = arr[l + i];

for (j = 0; j < n2; j++)

R[j] = arr[m + 1 + j];

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++;

while (i < n1) {

arr[k] = L[i];

i++;

k++;
}

while (j < n2) {

arr[k] = R[j];

j++;

k++;

void mergeSort(int arr[], int l, int r) {

if (l < r) {

int m = l + (r - l) / 2;

mergeSort(arr, l, m);

mergeSort(arr, m + 1, r);

merge(arr, l, m, r);

void printArray(int arr[], int size) {

for (int i = 0; i < size; i++)

printf("%d ", arr[i]);

printf("\n");

int main() {

int arr[] = {12, 11, 13, 5, 6, 7};

int arr_size = sizeof(arr) / sizeof(arr[0]);

printf("Original Array: ");


printArray(arr, arr_size);

mergeSort(arr, 0, arr_size - 1);

printf("Sorted Array: ");

printArray(arr, arr_size);

return 0;

OUTPUT:

LEARNING OUTCOME:
1. Develop a comprehensive understanding of the merge sort algorithm, grasping its divide-
and-conquer approach and merging technique.
2. Implement merge sort effectively, demonstrating proficiency in recursively splitting and
merging arrays to achieve sorting.
3. Master the concept of stability in merge sort and its significance in maintaining the relative
order of equal elements.
4. Analyze the time complexity of merge sort, recognizing its efficiency and suitability for
large datasets.
5. Apply merge sort to efficiently sort arrays in practical scenarios, showcasing algorithmic
proficiency and problem-solving skills in sorting algorithms.

EXPERIMENT 25
AIM: Implement heap sort.
THEORY: Heap sort is a comparison-based sorting algorithm known for its efficiency and
versatility. It leverages the binary heap data structure to achieve sorting in two phases: heap
construction and sorting. During heap construction, the input array is transformed into a max-
heap or min-heap, ensuring that the root node holds the maximum or minimum value,
respectively. The heap is then repeatedly modified to extract the root element, which is then
placed at the end of the sorted array. This process continues until the heap is empty, resulting
in a sorted array. Heap sort has a time complexity of O(n log n) in all cases, making it
suitable for large datasets and embedded systems with limited memory. Mastery of heap sort
enriches understanding of data structures and sorting algorithms, offering efficient solutions
to various sorting problems.
SOURCE CODE:

#include <stdio.h>
void heapify(int arr[], int n, int i) {

int largest = i;

int left = 2 * i + 1;

int right = 2 * i + 2;

if (left < n && arr[left] > arr[largest])

largest = left;

if (right < n && arr[right] > arr[largest])

largest = right;

if (largest != i) {

int temp = arr[i];

arr[i] = arr[largest];

arr[largest] = temp;

heapify(arr, n, largest);

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

for (int i = n / 2 - 1; i >= 0; i--)

heapify(arr, n, i);

for (int i = n - 1; i > 0; i--) {

int temp = arr[0];

arr[0] = arr[i];

arr[i] = temp;

heapify(arr, i, 0);
}

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

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

printf("%d ", arr[i]);

printf("\n");

}int main() {

int arr[] = {12, 11, 13, 5, 6, 7};

int n = sizeof(arr) / sizeof(arr[0]);

printf("Original Array: ");

printArray(arr, n);

heapSort(arr, n);

printf("Sorted Array: ");

printArray(arr, n);

return 0;

OUTPUT:

LEARNING OUTCOME:
1. Develop a comprehensive understanding of the heap sort algorithm, including its two-
phase approach involving heap construction and sorting.
2. Implement heap sort effectively, demonstrating proficiency in transforming an input array
into a heap and performing heap-based sorting.
3. Master the concepts of max-heap and min-heap and understand their role in heap sort's
sorting process.
4. Analyze the time complexity of heap sort, recognizing its efficiency and suitability for
sorting large datasets.
5. Apply heap sort to efficiently sort arrays in practical scenarios, showcasing algorithmic
proficiency and problem-solving skills in sorting algorithms.

EXPERIMENT 26
AIM: Implement sorting on different keys.
THEORY: Implementing sorting on different keys involves organizing data based on
multiple criteria or attributes. This approach is crucial in scenarios where sorting based on a
single key is insufficient to meet requirements. Sorting on different keys requires defining a
comparison function that considers multiple key attributes, ensuring proper ordering of
elements. Techniques such as lexicographical ordering or custom comparison functions can
be employed to achieve sorting on different keys. Mastery of sorting on different keys
enhances the ability to organize and analyze data effectively, facilitating various applications
such as database management, information retrieval, and data analysis in diverse fields.
SOURCE CODE:

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

#include <string.h>

struct Record {

int id;

char name[50];

int age;

};

int compareByID(const void *a, const void *b) {

return ((struct Record *)a)->id - ((struct Record *)b)->id;

int compareByName(const void *a, const void *b) {

return strcmp(((struct Record *)a)->name, ((struct Record *)b)->name);

int compareByAge(const void *a, const void *b) {

return ((struct Record *)a)->age - ((struct Record *)b)->age;

void printRecords(struct Record records[], int n) {

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

printf("ID: %d, Name: %s, Age: %d\n", records[i].id, records[i].name, records[i].age);

int main() {

struct Record records[] = {

{101, "John", 30},

{102, "Alice", 25},

{103, "Bob", 35},

{104, "Sarah", 28},


{105, "David", 32}

};

int n = sizeof(records) / sizeof(records[0]);

printf("Original Records:\n");

printRecords(records, n);

qsort(records, n, sizeof(struct Record), compareByID);

printf("\nSorted by ID:\n");

printRecords(records, n);

qsort(records, n, sizeof(struct Record), compareByName);

printf("\nSorted by Name:\n");

printRecords(records, n);

qsort(records, n, sizeof(struct Record), compareByAge);

printf("\nSorted by Age:\n");

printRecords(records, n);

return 0;

OUTPUT:
LEARNING OUTCOME:
1. Gain a comprehensive understanding of sorting algorithms adapted for organizing data
based on multiple criteria or attributes.
2. Implement sorting on different keys effectively, demonstrating proficiency in defining
comparison functions that consider multiple key attributes.
3. Master techniques such as lexicographical ordering and custom comparison functions to
achieve sorting on different keys.
4. Analyze the efficiency and effectiveness of sorting on different keys compared to sorting
on a single key, recognizing its importance in various applications.
5. Apply sorting on different keys to organize and analyze data in practical scenarios,
showcasing algorithmic proficiency and problem-solving skills in diverse data management
tasks.
EXPERIMENT 27
AIM: Implement external sorting.
THEORY: External sorting is a technique used to sort data sets that are too large to fit
entirely into memory. It involves splitting the data into smaller, manageable chunks, sorting
each chunk in memory, and then merging the sorted chunks to produce the final sorted
output. External sorting algorithms, such as merge sort and polyphase merge sort, efficiently
handle large data sets by minimizing the number of disk accesses and maximizing the use of
available memory. This technique is commonly employed in database systems, data
warehouses, and other applications dealing with massive datasets, ensuring efficient sorting
operations while mitigating memory constraints.
SOURCE CODE:

#include <stdio.h>

#include <stdlib.h>

void mergeFiles(FILE* f1, FILE* f2, FILE* output) {

int num1, num2;

fscanf(f1, "%d", &num1);

fscanf(f2, "%d", &num2);

while (!feof(f1) && !feof(f2)) {

if (num1 < num2) {

fprintf(output, "%d\n", num1);

fscanf(f1, "%d", &num1);

} else {

fprintf(output, "%d\n", num2);

fscanf(f2, "%d", &num2);

while (!feof(f1)) {

fprintf(output, "%d\n", num1);

fscanf(f1, "%d", &num1);

}
while (!feof(f2)) {

fprintf(output, "%d\n", num2);

fscanf(f2, "%d", &num2);

void externalSort(FILE* input, FILE* output, int chunkSize) {

int numFiles = 0;

while (!feof(input)) {

char filename[20];

sprintf(filename, "chunk%d.txt", numFiles);

FILE* chunk = fopen(filename, "w");

int* arr = (int*)malloc(chunkSize * sizeof(int));

int count = 0;

while (count < chunkSize && fscanf(input, "%d", &arr[count]) != EOF) {

count++;

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

fprintf(chunk, "%d\n", arr[i]);

fclose(chunk);

free(arr);

numFiles++;

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


char filename[20];

sprintf(filename, "chunk%d.txt", i);

FILE* chunk = fopen(filename, "r");

mergeFiles(output, chunk, output);

fclose(chunk);

remove(filename);

int main() {

printf("Sorting Successfull");

FILE* input = fopen("input.txt", "r");

FILE* output = fopen("output.txt", "w");

externalSort(input, output, 100);

fclose(input);

fclose(output);

return 0;

OUTPUT:

LEARNING OUTCOME:
1. Develop a comprehensive understanding of external sorting techniques, including
strategies for handling large datasets that exceed available memory.
2. Implement external sorting algorithms effectively, demonstrating proficiency in splitting
data into manageable chunks, sorting them in memory, and merging the sorted results.
3. Master techniques to optimize external sorting performance, such as minimizing disk
accesses and maximizing memory usage.
4. Analyze the efficiency and scalability of external sorting algorithms, recognizing their
importance in handling massive datasets in various applications.
5. Apply external sorting to efficiently sort large datasets in practical scenarios, showcasing
algorithmic proficiency and problem-solving skills in data management tasks.
EXPERIMENT 28
AIM: Implement graphs using adjacency list
THEORY: Implementing graphs using adjacency lists involves representing each vertex as a
node and storing its adjacent vertices in a linked list or array. This approach optimizes
memory usage by only storing connections that exist, making it efficient for sparse graphs.
Operations such as adding vertices and edges, as well as traversing the graph, are facilitated
by adjacency lists. This representation allows for efficient implementation of algorithms like
breadth-first search and depth-first search. Mastery of adjacency lists enhances understanding
of graph theory and graph algorithms, enabling efficient manipulation and traversal of graph
data structures in various applications, from social networks to routing algorithms.
SOURCE CODE:
#include <stdio.h>
#include <stdlib.h>

// Structure for a node in the adjacency list


struct AdjListNode {
int dest;
struct AdjListNode* next;
};

// Structure for the adjacency list


struct AdjList {
struct AdjListNode* head;
};

// Structure for the graph


struct Graph {
int V; // Number of vertices
struct AdjList* array;
};

// Function to create a new adjacency list node


struct AdjListNode* newAdjListNode(int dest) {
struct AdjListNode* newNode = (struct AdjListNode*)malloc(sizeof(struct
AdjListNode));
if (newNode == NULL) {
printf("Memory allocation failed\n");
exit(1);
}
newNode->dest = dest;
newNode->next = NULL;
return newNode;
}

// Function to create a graph with V vertices


struct Graph* createGraph(int V) {
struct Graph* graph = (struct Graph*)malloc(sizeof(struct Graph));
if (graph == NULL) {
printf("Memory allocation failed\n");
exit(1);
}
graph->V = V;

// Create an array of adjacency lists. Size of array will be V


graph->array = (struct AdjList*)malloc(V * sizeof(struct AdjList));
if (graph->array == NULL) {
printf("Memory allocation failed\n");
exit(1);
}

// Initialize each adjacency list as empty by making head as NULL


int i;
for (i = 0; i < V; ++i)
graph->array[i].head = NULL;

return graph;
}

// Function to add an edge to an undirected graph


void addEdge(struct Graph* graph, int src, int dest) {
// Add an edge from src to dest
struct AdjListNode* newNode = newAdjListNode(dest);
newNode->next = graph->array[src].head;
graph->array[src].head = newNode;

// Since the graph is undirected, add an edge from dest to src also
newNode = newAdjListNode(src);
newNode->next = graph->array[dest].head;
graph->array[dest].head = newNode;
}

// Function to print the adjacency list representation of the graph


void printGraph(struct Graph* graph) {
int v;
for (v = 0; v < graph->V; ++v) {
struct AdjListNode* pCrawl = graph->array[v].head;
printf("\n Adjacency list of vertex %d\n head ", v);
while (pCrawl) {
printf("-> %d", pCrawl->dest);
pCrawl = pCrawl->next;
}
printf("\n");
}
}

int main() {
int V = 5; // Number of vertices

// Create the graph


struct Graph* graph = createGraph(V);
// Add edges
addEdge(graph, 0, 1);
addEdge(graph, 0, 4);
addEdge(graph, 1, 2);
addEdge(graph, 1, 3);
addEdge(graph, 1, 4);
addEdge(graph, 2, 3);
addEdge(graph, 3, 4);

// Print the adjacency list representation of the graph


printGraph(graph);

return 0;
}

OUTPUT:

LEARNING OUTCOME:
1. Gain a comprehensive understanding of graph representation using adjacency lists,
comprehending the structure of nodes and their connections.
2. Implement adjacency lists effectively, demonstrating proficiency in storing vertices and
their adjacent vertices in linked lists or arrays.
3. Master operations such as adding vertices and edges to the graph, as well as traversing the
graph efficiently using adjacency lists.
4. Analyze the advantages of adjacency lists in terms of memory efficiency and ease of
implementation compared to other graph representations.
5. Apply adjacency lists to solve various graph-related problems, showcasing algorithmic
proficiency in graph theory and graph algorithms.
EXPERIMENT 29
AIM: Implement graphs using adjacency matrix
THEORY: Implementing graphs using adjacency matrices involves representing vertices as
rows and columns in a matrix, with cell values indicating edge connections. This method
offers efficient lookup for edge existence and supports weighted edges. However, it requires
more memory for sparse graphs and may be less efficient for large graphs with many edges.
Operations such as adding vertices, edges, and checking edge connectivity are
straightforward with adjacency matrices. Mastery of adjacency matrices enriches
understanding of graph theory and facilitates efficient implementation of graph algorithms
like Dijkstra's shortest path algorithm and Floyd-Warshall algorithm. It's suitable for
applications where edge connectivity queries are frequent, such as network analysis.
SOURCE CODE:
#include <stdio.h>
#include <stdlib.h>

#define MAX_VERTICES 100

// Structure for the graph


struct Graph {
int V; // Number of vertices
int** matrix; // Adjacency matrix
};

// Function to create a graph with V vertices


struct Graph* createGraph(int V) {
struct Graph* graph = (struct Graph*)malloc(sizeof(struct Graph));
if (graph == NULL) {
printf("Memory allocation failed\n");
exit(1);
}
graph->V = V;

// Allocate memory for the adjacency matrix


graph->matrix = (int**)malloc(V * sizeof(int*));
if (graph->matrix == NULL) {
printf("Memory allocation failed\n");
exit(1);
}

// Initialize the adjacency matrix with zeros


int i, j;
for (i = 0; i < V; ++i) {
graph->matrix[i] = (int*)malloc(V * sizeof(int));
if (graph->matrix[i] == NULL) {
printf("Memory allocation failed\n");
exit(1);
}
for (j = 0; j < V; ++j)
graph->matrix[i][j] = 0;
}
return graph;
}
// Function to add an edge to the graph
void addEdge(struct Graph* graph, int src, int dest) {
// Since the graph is undirected, set both src -> dest and dest -> src to 1
graph->matrix[src][dest] = 1;
graph->matrix[dest][src] = 1;
}

// Function to print the adjacency matrix representation of the graph


void printGraph(struct Graph* graph) {
int i, j;
for (i = 0; i < graph->V; ++i) {
for (j = 0; j < graph->V; ++j)
printf("%d ", graph->matrix[i][j]);
printf("\n");
}
}
// Function to free the memory allocated for the graph
void freeGraph(struct Graph* graph) {
int i;
for (i = 0; i < graph->V; ++i)
free(graph->matrix[i]);
free(graph->matrix);
free(graph);
}
int main() {
int V = 5; // Number of vertices

struct Graph* graph = createGraph(V);

// Add edges
addEdge(graph, 0, 1);
addEdge(graph, 0, 4);
addEdge(graph, 1, 2);
addEdge(graph, 1, 3);
addEdge(graph, 1, 4);
addEdge(graph, 2, 3);
addEdge(graph, 3, 4);

// Print the adjacency matrix representation of the graph


printGraph(graph);

// Free memory allocated for the graph


freeGraph(graph);

return 0;
}

OUTPUT:

LEARNING OUTCOME:
1. Develop a comprehensive understanding of graph representation using adjacency matrices,
recognizing the structure of vertices and their connections within a matrix.
2. Implement adjacency matrices effectively, demonstrating proficiency in representing edge
connections and supporting weighted edges.
3. Master operations such as adding vertices, edges, and checking edge connectivity
efficiently using adjacency matrices.
4. Analyze the advantages and limitations of adjacency matrices in terms of memory usage
and query efficiency, compared to other graph representations.
5. Apply adjacency matrices to solve various graph-related problems, showcasing algorithmic
proficiency in graph theory and graph algorithms.
EXPERIMENT 30
AIM: Implement DFS on a graph
THEORY: Depth-First Search (DFS) is a graph traversal algorithm that systematically
explores vertices and edges by visiting as far as possible along each branch before
backtracking. It can be implemented using recursion or a stack data structure. DFS starts from
a designated starting vertex and explores adjacent vertices recursively, marking visited
vertices to prevent revisits. This algorithm efficiently explores connected components and
can be adapted for various applications, including cycle detection, topological sorting, and
finding paths or connected components. Mastery of DFS enriches understanding of graph
theory and traversal algorithms, facilitating efficient exploration and analysis of graph
structures in diverse applications.
SOURCE CODE:
#include <stdio.h>
#include <stdlib.h>

#define MAX_VERTICES 100

// Structure for a node in the adjacency list


struct AdjListNode {
int dest;
struct AdjListNode* next;
};

// Structure for the adjacency list


struct AdjList {
struct AdjListNode* head;
};

// Structure for the graph


struct Graph {
int V; // Number of vertices
struct AdjList* array;
};

// Function to create a new adjacency list node


struct AdjListNode* newAdjListNode(int dest) {
struct AdjListNode* newNode = (struct AdjListNode*)malloc(sizeof(struct
AdjListNode));
if (newNode == NULL) {
printf("Memory allocation failed\n");
exit(1);
}
newNode->dest = dest;
newNode->next = NULL;
return newNode;
}

// Function to create a graph with V vertices


struct Graph* createGraph(int V) {
struct Graph* graph = (struct Graph*)malloc(sizeof(struct Graph));
if (graph == NULL) {
printf("Memory allocation failed\n");
exit(1);
}
graph->V = V;

// Create an array of adjacency lists. Size of array will be V


graph->array = (struct AdjList*)malloc(V * sizeof(struct AdjList));
if (graph->array == NULL) {
printf("Memory allocation failed\n");
exit(1);
}

// Initialize each adjacency list as empty by making head as NULL


int i;
for (i = 0; i < V; ++i)
graph->array[i].head = NULL;

return graph;
}

// Function to add an edge to an undirected graph


void addEdge(struct Graph* graph, int src, int dest) {
// Add an edge from src to dest
struct AdjListNode* newNode = newAdjListNode(dest);
newNode->next = graph->array[src].head;
graph->array[src].head = newNode;
// Since the graph is undirected, add an edge from dest to src also
newNode = newAdjListNode(src);
newNode->next = graph->array[dest].head;
graph->array[dest].head = newNode;
}

// Recursive function for DFS traversal


void DFSUtil(struct Graph* graph, int v, int visited[]) {
// Mark the current node as visited
visited[v] = 1;
printf("%d ", v);
struct AdjListNode* pCrawl = graph->array[v].head;
while (pCrawl != NULL) {
int adj = pCrawl->dest;
if (!visited[adj])
DFSUtil(graph, adj, visited);
pCrawl = pCrawl->next;
}
}

// Function to perform DFS traversal of the graph


void DFS(struct Graph* graph, int start) {
int* visited = (int*)malloc(graph->V * sizeof(int));
if (visited == NULL) {
printf("Memory allocation failed\n");
exit(1);
}

// Initialize all vertices as not visited


int i;
for (i = 0; i < graph->V; ++i)
visited[i] = 0;

// Call the recursive helper function to print DFS traversal


DFSUtil(graph, start, visited);

// Free dynamically allocated memory


free(visited);
}
int main() {
int V = 5; // Number of vertices
// Create the graph
struct Graph* graph = createGraph(V);
addEdge(graph, 0, 1);
addEdge(graph, 0, 4);
addEdge(graph, 1, 2);
addEdge(graph, 1, 3);
addEdge(graph, 1, 4);
addEdge(graph, 2, 3);
addEdge(graph, 3, 4);

printf("DFS traversal starting from vertex 0: ");


DFS(graph, 0);
printf("\n");

// Free memory allocated for the graph


free(graph);

return 0;
}

OUTPUT:

LEARNING OUTCOME:
1. Gain a deep understanding of Depth-First Search (DFS) algorithm, comprehending its
systematic exploration of vertices and edges in a graph.
2. Implement DFS effectively, demonstrating proficiency in recursive traversal or using a
stack data structure to explore vertices and mark visited nodes.
3. Master DFS applications, including cycle detection, topological sorting, and finding paths
or connected components in a graph.
4. Analyze the time and space complexity of DFS algorithm, recognizing its efficiency in
exploring connected components and solving graph-related problems.
5. Apply DFS to solve real-world problems in diverse domains, showcasing algorithmic
proficiency and problem-solving skills in graph theory.

EXPERIMENT 31
AIM: Implement BFS on a graph.
THEORY: Breadth-First Search (BFS) is a graph traversal algorithm that explores vertices
and edges by systematically visiting all neighbors of a vertex before moving to the next level.
It employs a queue data structure to maintain the order of vertices to be visited. BFS starts
from a designated starting vertex and explores adjacent vertices layer by layer, marking
visited vertices to prevent revisits. This algorithm efficiently finds shortest paths and can be
adapted for various applications, including shortest path finding, connected component
identification, and network analysis. Mastery of BFS enriches understanding of graph theory
and traversal algorithms, facilitating efficient exploration and analysis of graph structures.
SOURCE CODE:
#include <stdio.h>
#include <stdlib.h>

#define MAX_VERTICES 100


#define QUEUE_CAPACITY 100

// Structure for a node in the adjacency list


struct AdjListNode {
int dest;
struct AdjListNode* next;
};

// Structure for the adjacency list


struct AdjList {
struct AdjListNode* head;
};

// Structure for the graph


struct Graph {
int V; // Number of vertices
struct AdjList* array;
};

// Function to create a new adjacency list node


struct AdjListNode* newAdjListNode(int dest) {
struct AdjListNode* newNode = (struct AdjListNode*)malloc(sizeof(struct
AdjListNode));
if (newNode == NULL) {
printf("Memory allocation failed\n");
exit(1);
}
newNode->dest = dest;
newNode->next = NULL;
return newNode;
}

// Function to create a graph with V vertices


struct Graph* createGraph(int V) {
struct Graph* graph = (struct Graph*)malloc(sizeof(struct Graph));
if (graph == NULL) {
printf("Memory allocation failed\n");
exit(1);
}
graph->V = V;

// Create an array of adjacency lists. Size of array will be V


graph->array = (struct AdjList*)malloc(V * sizeof(struct AdjList));
if (graph->array == NULL) {
printf("Memory allocation failed\n");
exit(1);
}

// Initialize each adjacency list as empty by making head as NULL


int i;
for (i = 0; i < V; ++i)
graph->array[i].head = NULL;

return graph;
}

// Function to add an edge to an undirected graph


void addEdge(struct Graph* graph, int src, int dest) {
// Add an edge from src to dest
struct AdjListNode* newNode = newAdjListNode(dest);
newNode->next = graph->array[src].head;
graph->array[src].head = newNode;
// Since the graph is undirected, add an edge from dest to src also
newNode = newAdjListNode(src);
newNode->next = graph->array[dest].head;
graph->array[dest].head = newNode;
}

// Function to perform Breadth-First Search (BFS) traversal of the graph


void BFS(struct Graph* graph, int start) {
// Array to keep track of visited vertices
int visited[MAX_VERTICES] = {0};

// Create a queue for BFS


int queue[QUEUE_CAPACITY];
int front = 0, rear = 0;

// Mark the current node as visited and enqueue it


visited[start] = 1;
queue[rear++] = start;

while (front != rear) {


// Dequeue a vertex from queue and print it
int current = queue[front++];
printf("%d ", current);

// Get all adjacent vertices of the dequeued vertex current. If an adjacent has not been
visited, then mark it visited and enqueue it
struct AdjListNode* pCrawl = graph->array[current].head;
while (pCrawl != NULL) {
int adj = pCrawl->dest;
if (!visited[adj]) {
visited[adj] = 1;
queue[rear++] = adj;
}
pCrawl = pCrawl->next;
}
}
}
int main() {
int V = 5; // Number of vertices

// Create the graph


struct Graph* graph = createGraph(V);

// Add edges
addEdge(graph, 0, 1);
addEdge(graph, 0, 4);
addEdge(graph, 1, 2);
addEdge(graph, 1, 3);
addEdge(graph, 1, 4);
addEdge(graph, 2, 3);
addEdge(graph, 3, 4);

printf("BFS traversal starting from vertex 0: ");


BFS(graph, 0);
printf("\n");

free(graph);
return 0;
}

OUTPUT:

LEARNING OUTCOME:
1. Develop a comprehensive understanding of Breadth-First Search (BFS) algorithm,
comprehending its systematic exploration of vertices and edges in a graph.
2. Implement BFS effectively, demonstrating proficiency in traversing adjacent vertices layer
by layer using a queue data structure.
3. Master BFS applications, including shortest path finding, connected component
identification, and network analysis in a graph.
4. Analyze the time and space complexity of BFS algorithm, recognizing its efficiency in
exploring shortest paths and solving graph-related problems.
5. Apply BFS to solve real-world problems in diverse domains, showcasing algorithmic
proficiency and problem-solving skills in graph theory.

EXPERIMENT 32
AIM: Implement spanning trees using Prim's algorithm.
THEORY: Prim's algorithm is a greedy algorithm used to find the minimum spanning tree
(MST) of a connected, undirected graph. It starts by selecting an arbitrary vertex as the initial
tree and iteratively grows the tree by adding the shortest edge that connects a vertex in the
tree to a vertex outside the tree. This process continues until all vertices are included in the
MST. Prim's algorithm guarantees the construction of the minimum-weight spanning tree and
has a time complexity of O(V^2) or O(E log V) depending on the implementation. Mastery of
Prim's algorithm enriches understanding of graph theory and facilitates efficient network
design in various applications.
SOURCE CODE:
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>

#define MAX_VERTICES 100

// Structure for a node in the adjacency list


struct AdjListNode {
int dest;
int weight;
struct AdjListNode* next;
};

// Structure for the adjacency list


struct AdjList {
struct AdjListNode* head;
};

// Structure for the graph


struct Graph {
int V; // Number of vertices
struct AdjList* array;
};

// Function to create a new adjacency list node


struct AdjListNode* newAdjListNode(int dest, int weight) {
struct AdjListNode* newNode = (struct AdjListNode*)malloc(sizeof(struct
AdjListNode));
if (newNode == NULL) {
printf("Memory allocation failed\n");
exit(1);
}
newNode->dest = dest;
newNode->weight = weight;
newNode->next = NULL;
return newNode;
}

// Function to create a graph with V vertices


struct Graph* createGraph(int V) {
struct Graph* graph = (struct Graph*)malloc(sizeof(struct Graph));
if (graph == NULL) {
printf("Memory allocation failed\n");
exit(1);
}
graph->V = V;

// Create an array of adjacency lists. Size of array will be V


graph->array = (struct AdjList*)malloc(V * sizeof(struct AdjList));
if (graph->array == NULL) {
printf("Memory allocation failed\n");
exit(1);
}

// Initialize each adjacency list as empty by making head as NULL


int i;
for (i = 0; i < V; ++i)
graph->array[i].head = NULL;

return graph;
}

// Function to add an edge to the graph


void addEdge(struct Graph* graph, int src, int dest, int weight) {
// Add an edge from src to dest
struct AdjListNode* newNode = newAdjListNode(dest, weight);
newNode->next = graph->array[src].head;
graph->array[src].head = newNode;

// Since the graph is undirected, add an edge from dest to src also
newNode = newAdjListNode(src, weight);
newNode->next = graph->array[dest].head;
graph->array[dest].head = newNode;
}

// Function to find the vertex with the minimum key value, from the set of vertices not yet
included in the MST
int minKey(int key[], int mstSet[], int V) {
int min = INT_MAX, min_index;
int v;
for (v = 0; v < V; v++) {
if (mstSet[v] == 0 && key[v] < min) {
min = key[v];
min_index = v;
}
}
return min_index;
}

// Function to print the constructed MST stored in parent[]


void printMST(int parent[], struct Graph* graph) {
printf("Edge Weight\n");
int i;
for (i = 1; i < graph->V; i++)
printf("%d - %d %d \n", parent[i], i, graph->array[i].head->weight);
}

// Function to construct and print MST for a graph represented using adjacency list
representation
void primMST(struct Graph* graph) {
int parent[MAX_VERTICES]; // Array to store constructed MST
int key[MAX_VERTICES]; // Key values used to pick minimum weight edge in cut
int mstSet[MAX_VERTICES]; // To represent set of vertices not yet included in MST
// Initialize all keys as INFINITE
// Initialize all vertices as not yet included in MST
int i;
for (i = 0; i < graph->V; i++) {
key[i] = INT_MAX;
mstSet[i] = 0;
}

// Always include first vertex in MST


key[0] = 0; // Make key 0 so that this vertex is picked as first vertex
parent[0] = -1; // First node is always root of MST

// The MST will have V vertices


for (i = 0; i < graph->V - 1; i++) {
// Pick the minimum key vertex from the set of vertices not yet included in MST
int u = minKey(key, mstSet, graph->V);

// Add the picked vertex to the MST set


mstSet[u] = 1;

// Update key value and parent index of the adjacent vertices of the picked vertex
struct AdjListNode* pCrawl = graph->array[u].head;
while (pCrawl != NULL) {
int v = pCrawl->dest;
int weight = pCrawl->weight;
if (mstSet[v] == 0 && weight < key[v]) {
parent[v] = u;
key[v] = weight;
}
pCrawl = pCrawl->next;
}
}

// Print the constructed MST


printMST(parent, graph);
}
int main() {
int V = 5; // Number of vertices

struct Graph* graph = createGraph(V);


// Add edges with weights
addEdge(graph, 0, 1, 2);
addEdge(graph, 0, 3, 6);
addEdge(graph, 1, 2, 3);
addEdge(graph, 1, 3, 8);
addEdge(graph, 1, 4, 5);
addEdge(graph, 2, 4, 7);
addEdge(graph, 3, 4, 9);

// Print the MST using Prim's algorithm


printf("Minimum Spanning Tree using Prim's algorithm:\n");
prim
free(graph);
return 0;
}

OUTPUT:

LEARNING OUTCOME:
1. Develop a thorough understanding of Prim's algorithm for constructing minimum spanning
trees (MSTs), grasping its greedy approach to vertex selection and edge addition.
2. Implement Prim's algorithm effectively, demonstrating proficiency in selecting vertices
and edges to grow the spanning tree incrementally.
3. Master the concept of minimum-weight spanning trees and understand how Prim's
algorithm guarantees the construction of the optimal tree.
4. Analyze the time complexity of Prim's algorithm, recognizing its efficiency in finding
MSTs in connected, undirected graphs.
5. Apply Prim's algorithm to solve real-world network design problems, showcasing
algorithmic proficiency and problem-solving skills in graph theory and network optimization.

EXPERIMENT 33
AIM: Implement spannign trees using Krushkal's algorithm.
THEORY: Kruskal's algorithm is a greedy algorithm used to construct a minimum spanning
tree (MST) in a connected, weighted graph. It begins by sorting the edges by their weights
and then iteratively selects the shortest edge that does not form a cycle when added to the
growing forest of trees. This process continues until all vertices are included in a single tree.
Kruskal's algorithm guarantees the construction of the minimum-weight spanning tree and
has a time complexity of O(E log E) or O(E log V), depending on the implementation.
Mastery of Kruskal's algorithm enhances understanding of graph theory and facilitates
efficient network design in various applications.
SOURCE CODE:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>

#define MAX_VERTICES 100


#define MAX_EDGES 100

// Structure to represent an edge in the graph


struct Edge {
int src, dest, weight;
};

// Structure to represent a subset for union-find


struct Subset {
int parent;
int rank;
};

// Structure to represent a graph


struct Graph {
int V; // Number of vertices
int E; // Number of edges
struct Edge* edge; // Array of edges
};

// Function to create a graph with V vertices and E edges


struct Graph* createGraph(int V, int E) {
struct Graph* graph = (struct Graph*)malloc(sizeof(struct Graph));
if (graph == NULL) {
printf("Memory allocation failed\n");
exit(1);
}
graph->V = V;
graph->E = E;
graph->edge = (struct Edge*)malloc(E * sizeof(struct Edge));
if (graph->edge == NULL) {
printf("Memory allocation failed\n");
exit(1);
}
return graph;
}

// Function to find the set of an element i


int find(struct Subset subsets[], int i) {
if (subsets[i].parent != i)
subsets[i].parent = find(subsets, subsets[i].parent);
return subsets[i].parent;
}

// Function to do union of two sets


void Union(struct Subset subsets[], int x, int y) {
int xroot = find(subsets, x);
int yroot = find(subsets, y);

// Attach smaller rank tree under root of high rank tree (Union by Rank)
if (subsets[xroot].rank < subsets[yroot].rank)
subsets[xroot].parent = yroot;
else if (subsets[xroot].rank > subsets[yroot].rank)
subsets[yroot].parent = xroot;
else {
subsets[yroot].parent = xroot;
subsets[xroot].rank++;
}
}

// Function to compare two edges according to their weights


int compareEdges(const void* a, const void* b) {
struct Edge* edge1 = (struct Edge*)a;
struct Edge* edge2 = (struct Edge*)b;
return edge1->weight - edge2->weight;
}

// Function to construct MST using Kruskal's algorithm


void KruskalMST(struct Graph* graph) {
int V = graph->V;
struct Edge result[V]; // Array to store the resultant MST
int e = 0; // Index variable for result[]
int i = 0; // Index variable for sorted edges

qsort(graph->edge, graph->E, sizeof(graph->edge[0]), compareEdges);

// Allocate memory for creating V subsets


struct Subset* subsets = (struct Subset*)malloc(V * sizeof(struct Subset));
if (subsets == NULL) {
printf("Memory allocation failed\n");
exit(1);
}

// Create V subsets with single elements


for (int v = 0; v < V; v++) {
subsets[v].parent = v;
subsets[v].rank = 0;
}
// Number of edges to be taken is equal to V-1
while (e < V - 1 && i < graph->E) {
// If cycle is not formed, include this edge. Else, discard it.
struct Edge next_edge = graph->edge[i++];
int x = find(subsets, next_edge.src);
int y = find(subsets, next_edge.dest);
if (x != y) {
result[e++] = next_edge;
Union(subsets, x, y);
}
}

printf("Minimum Spanning Tree using Kruskal's algorithm:\n");


for (i = 0; i < e; i++)
printf("%d - %d %d\n", result[i].src, result[i].dest, result[i].weight);

free(subsets);
}

int main() {
int V = 5; // Number of vertices
int E = 7; // Number of edges

// Create the graph


struct Graph* graph = createGraph(V, E);

// Add edges with weights


graph->edge[0].src = 0;
graph->edge[0].dest = 1;
graph->edge[0].weight = 2;

graph->edge[1].src = 0;
graph->edge[1].dest = 3;
graph->edge[1].weight = 6;

graph->edge[2].src = 1;
graph->edge[2].dest = 2;
graph->edge[2].weight = 3;

graph->edge[3].src = 1;
graph->edge[3].dest = 3
graph->edge[3].weight = 8;

graph->edge[4].src = 1;
graph->edge[4].dest = 4;
graph->edge[4].weight = 5;
graph->edge[5].src = 2;
graph->edge[5].dest = 4;
graph->edge[5].weight = 7;

graph->edge[6].src = 3;
graph->edge[6].dest = 4;
graph->edge[6].weight = 9;

// Print the MST using Kruskal's algorithm


KruskalMST(graph);

// Free memory allocated for the graph


free(graph->edge);
free(graph);

return 0;
}

OUTPUT:

LEARNING OUTCOME:
1. Gain a comprehensive understanding of Kruskal's algorithm for constructing minimum
spanning trees (MSTs), comprehending its greedy approach to edge selection.
2. Implement Kruskal's algorithm effectively, demonstrating proficiency in sorting edges by
weight and iteratively selecting edges to grow the spanning tree.
3. Master the concept of minimum-weight spanning trees and understand how Kruskal's
algorithm ensures the construction of the optimal tree.
4. Analyze the time complexity of Kruskal's algorithm, recognizing its efficiency in finding
MSTs in connected, weighted graphs.
5. Apply Kruskal's algorithm to solve real-world network design problems, showcasing
algorithmic proficiency and problem-solving skills in graph theory and network optimization.

EXPERIMENT 34
AIM: - Implement Djisktra's algorithm.
THEORY: Dijkstra's algorithm is a graph traversal algorithm used to find the shortest paths
from a designated source vertex to all other vertices in a weighted graph with non-negative
edge weights. It operates by iteratively selecting the vertex with the shortest known distance
from the source and updating the distances to its neighboring vertices. Dijkstra's algorithm
employs a priority queue to efficiently select vertices with the smallest distances. This
algorithm guarantees the discovery of the shortest paths and has a time complexity of O((V +
E) log V). Mastery of Dijkstra's algorithm enriches understanding of graph theory and
facilitates efficient pathfinding in various applications.
SOURCE CODE:
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>

#define MAX_VERTICES 100

// Structure to represent a node in the adjacency list


struct AdjListNode {
int dest;
int weight;
struct AdjListNode* next;
};

// Structure to represent the adjacency list


struct AdjList {
struct AdjListNode* head;
};

// Structure to represent the graph


struct Graph {
int V; // Number of vertices
struct AdjList* array;
};

// Function to create a new adjacency list node


struct AdjListNode* newAdjListNode(int dest, int weight) {
struct AdjListNode* newNode = (struct AdjListNode*)malloc(sizeof(struct
AdjListNode));
if (newNode == NULL) {
printf("Memory allocation failed\n");
exit(1);
}
newNode->dest = dest;
newNode->weight = weight;
newNode->next = NULL;
return newNode;
}

// Function to create a graph with V vertices


struct Graph* createGraph(int V) {
struct Graph* graph = (struct Graph*)malloc(sizeof(struct Graph));
if (graph == NULL) {
printf("Memory allocation failed\n");
exit(1);
}
graph->V = V;

// Create an array of adjacency lists. Size of array will be V


graph->array = (struct AdjList*)malloc(V * sizeof(struct AdjList));
if (graph->array == NULL) {
printf("Memory allocation failed\n");
exit(1);
}

// Initialize each adjacency list as empty by making head as NULL


int i;
for (i = 0; i < V; ++i)
graph->array[i].head = NULL;

return graph;
}

// Function to add an edge to the graph


void addEdge(struct Graph* graph, int src, int dest, int weight) {
// Add an edge from src to dest
struct AdjListNode* newNode = newAdjListNode(dest, weight);
newNode->next = graph->array[src].head;
graph->array[src].head = newNode;

// Since the graph is directed, do not add an edge from dest to src
}

// Function to find the vertex with the minimum distance value, from the set of vertices not
yet included in the shortest path tree
int minDistance(int dist[], int sptSet[], int V) {
int min = INT_MAX, min_index;
int v;
for (v = 0; v < V; v++) {
if (sptSet[v] == 0 && dist[v] <= min) {
min = dist[v];
min_index = v;
}
}
return min_index;
}

// Function to print the shortest path from source to destination


void printPath(int parent[], int j) {
if (parent[j] == -1)
return;
printPath(parent, parent[j]);
printf("%d ", j);
}

// Function to print the shortest path array


void printSolution(int dist[], int parent[], int src, int V) {
printf("Vertex\t Distance\tPath\n");
int i;
for (i = 0; i < V; i++) {
printf("%d -> %d\t %d\t\t%d ", src, i, dist[i], src);
printPath(parent, i);
printf("\n");
}
}
// Function to implement Dijkstra's single source shortest path algorithm
void dijkstra(struct Graph* graph, int src) {
int V = graph->V;
int dist[V]; // The output array. dist[i] will hold the shortest distance from src to i

// sptSet[i] will be true if vertex i is included in the shortest path tree or shortest distance
from src to i is finalized
int sptSet[V];

// Parent array to store shortest path tree


int parent[V];

// Initialize all distances as INFINITE and sptSet[] as false


int i;
for (i = 0; i < V; i++) {
dist[i] = INT_MAX;
sptSet[i] = 0;
parent[i] = -1;
}
// Distance of source vertex from itself is always 0
dist[src] = 0;

// Find shortest path for all vertices


for (int count = 0; count < V - 1; count++) {
// Pick the minimum distance vertex from the set of vertices not yet processed. u is
always equal to src in the first iteration.
int u = minDistance(dist, sptSet, V);

// Mark the picked vertex as processed


sptSet[u] = 1;

// Update dist value of the adjacent vertices of the picked vertex


struct AdjListNode* pCrawl = graph->array[u].head;
while (pCrawl != NULL) {
int v = pCrawl->dest;
// Update dist[v] only if it is not in sptSet, there is an edge from u to v, and total
weight of path from src to v through u is smaller than current value of dist[v]
if (!sptSet[v] && dist[u] != INT_MAX && dist[u] + pCrawl->weight < dist[v]) {
dist[v] = dist[u] + pCrawl->weight;
parent[v] = u;
}
pCrawl = pCrawl->next;
}
}

// Print the constructed distance array, shortest path tree, and shortest paths from source to
all other vertices
printSolution(dist, parent, src, V);
}

int main() {
int V = 9; // Number of vertices in the graph
struct Graph* graph = createGraph(V);

// Add edges with weights


addEdge(graph, 0, 1, 4);
addEdge(graph, 0, 7, 8);
addEdge(graph, 1, 2, 8);
addEdge(graph, 1, 7, 11);
addEdge(graph, 2, 3, 7);
addEdge(graph, 2, 8, 2);
addEdge(graph, 2, 5, 4);
addEdge(graph, 3, 4, 9);
addEdge(graph, 3, 5, 14);
addEdge(graph, 4, 5, 10);
addEdge(graph, 5, 6, 2);
addEdge(graph, 6, 7, 1);
addEdge(graph, 6, 8, 6);
addEdge(graph, 7, 8, 7);

int source = 0; // Source vertex


dijkstra(graph, source);
// Free memory allocated for the graph
free(graph);

return 0;
}

OUTPUT:

LEARNING OUTCOME:
1. Develop a comprehensive understanding of Dijkstra's algorithm for finding shortest paths
in weighted graphs, including its iterative vertex selection process.
2. Implement Dijkstra's algorithm effectively, demonstrating proficiency in updating
distances to neighboring vertices and selecting vertices with the shortest known distance.
3. Master the concept of shortest paths and understand how Dijkstra's algorithm guarantees
the discovery of optimal paths in graphs with non-negative edge weights.
4. Analyze the time complexity of Dijkstra's algorithm, recognizing its efficiency in finding
shortest paths from a source vertex to all other vertices.
5. Apply Dijkstra's algorithm to solve real-world routing and pathfinding problems,
showcasing algorithmic proficiency and problem-solving skills in graph theory and network
optimization.

EXPERIMENT 35
AIM: Implement sequential file organization. Include functions for creating, opening,
reading from, and writing to a sequential file.
THEORY: Sequential file organization involves storing records in a sequential manner,
facilitating sequential access through linear traversal. To create a sequential file, one
initializes the file and defines its structure. Opening a sequential file involves establishing a
connection for reading or writing operations. Reading from the file involves sequentially
accessing records from start to end, while writing appends new records to the end. These
operations offer simplicity and efficiency for applications requiring linear data access
patterns. Mastery of sequential file operations enhances understanding of file organization
and facilitates efficient data management in various applications, such as database systems
and file processing tasks.
SOURCE CODE:
#include <stdio.h>
#include <stdlib.h>

// Function to create a sequential file


void createSequentialFile(const char *filename) {
FILE *fp = fopen(filename, "w");
if (fp == NULL) {
printf("Error creating file!\n");
return;
}
printf("File created successfully!\n");
fclose(fp);
}

// Function to open a sequential file


FILE* openSequentialFile(const char *filename, const char
*mode) {
FILE *fp = fopen(filename, mode);
if (fp == NULL) {
printf("Error opening file!\n");
return NULL;
}
return fp;
}

// Function to write to a sequential file


void writeSequentialFile(FILE *fp, const char *data) {
fprintf(fp, "%s\n", data);
}

// Function to read from a sequential file


void readSequentialFile(FILE *fp) {
char line[100]; // assuming each line in the file is at
most 100 characters long
while (fgets(line, sizeof(line), fp) != NULL) {
printf("%s", line);
}
}
int main() {
const char *filename = "35sequential_file.txt";
FILE *fp;

// Create a sequential file


createSequentialFile(filename);

// Open the file for writing


fp = openSequentialFile(filename, "w");
if (fp == NULL) {
printf("Error opening file for writing!\n");
return 1;
}

// Write data to the file


writeSequentialFile(fp, "Line 1");
writeSequentialFile(fp, "Line 2");
writeSequentialFile(fp, "Line 3");

// Close the file


fclose(fp);

// Open the file for reading


fp = openSequentialFile(filename, "r");
if (fp == NULL) {
printf("Error opening file for reading!\n");
return 1;
}

// Read data from the file


printf("Contents of the file:\n");
readSequentialFile(fp);

// Close the file


fclose(fp);

return 0;
}

OUTPUT:
LEARNING OUTCOME:
1. Develop a comprehensive understanding of sequential file organization, including its linear
storage and access patterns.
2. Implement functions for creating and opening sequential files, demonstrating proficiency
in file initialization and establishment of access connections.
3. Master reading operations from sequential files, efficiently accessing records sequentially
from start to end.
4. Demonstrate proficiency in writing operations to sequential files, appending new records to
the end of the file.
5. Apply sequential file organization to various file processing tasks, showcasing algorithmic
proficiency and problem-solving skills in data management.
EXPERIMENT 36
AIM: Implement direct file organization using fixed-length records. Include functions to add
records, delete records, and search for records by their key.
THEORY: Direct file organization with fixed-length records involves storing data in a file
where each record occupies a predetermined fixed size. Adding records to such files requires
allocating space for new records and updating file metadata accordingly. Deleting records
involves marking them as inactive or shifting subsequent records to fill the gap. Searching for
records by key involves direct access based on the record's key value, leveraging indexing or
hashing for efficient retrieval. This organization offers fast record access and is suitable for
applications requiring frequent record retrievals. Mastery of direct file organization enhances
understanding of data storage and retrieval techniques, facilitating efficient data management
in various applications.
SOURCE CODE:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define RECORD_SIZE 100 // Size of each record

// Struct to represent a record


typedef struct {
int key;
char data[RECORD_SIZE - sizeof(int)]; // data size is
adjusted for the key
} Record;

// Function to add a record to the file


void addRecord(const char *filename, const Record *record) {
FILE *fp = fopen(filename, "ab");
if (fp == NULL) {
printf("Error opening file for appending!\n");
return;
}
fwrite(record, RECORD_SIZE, 1, fp);
fclose(fp);
}

// Function to delete a record from the file by key


void deleteRecord(const char *filename, int key) {
FILE *fp = fopen(filename, "rb");
if (fp == NULL) {
printf("Error opening file for reading!\n");
return;
}

FILE *temp = fopen("temp.dat", "wb");


if (temp == NULL) {
printf("Error creating temporary file!\n");
fclose(fp);
return;
}
Record tempRecord;
int found = 0;
while (fread(&tempRecord, RECORD_SIZE, 1, fp) == 1) {
if (tempRecord.key != key) {
fwrite(&tempRecord, RECORD_SIZE, 1, temp);
} else {
found = 1;
}
}

fclose(fp);
fclose(temp);

remove(filename);
rename("temp.dat", filename);

if (!found) {
printf("Record with key %d not found!\n", key);
} else {
printf("Record with key %d deleted successfully!\n",
key);
}
}

// Function to search for a record by key


void searchRecord(const char *filename, int key) {
FILE *fp = fopen(filename, "rb");
if (fp == NULL) {
printf("Error opening file for reading!\n");
return;
}

Record record;
int found = 0;
while (fread(&record, RECORD_SIZE, 1, fp) == 1) {
if (record.key == key) {
printf("Record found:\n");
printf("Key: %d\n", record.key);
printf("Data: %s\n", record.data);
found = 1;
break;
}
}

if (!found) {
printf("Record with key %d not found!\n", key);
}

fclose(fp);
}
int main() {
const char *filename = "direct_file.dat";

// Example records
Record record1 = {1, "Data for record 1"};
Record record2 = {2, "Data for record 2"};
Record record3 = {3, "Data for record 3"};

// Add records to the file


addRecord(filename, &record1);
addRecord(filename, &record2);
addRecord(filename, &record3);

// Search for a record


searchRecord(filename, 2);

// Delete a record
deleteRecord(filename, 2);

// Search for the deleted record


searchRecord(filename, 2);

return 0;
}

OUTPUT:

LEARNING OUTCOME:
1. Develop a comprehensive understanding of direct file organization with fixed-length
records, grasping the concept of allocating predetermined space for each record.
2. Implement functions to add records to the file, demonstrating proficiency in allocating
space and updating file metadata.
3. Master record deletion operations, including marking records as inactive and managing file
space to maintain data integrity.
4. Demonstrate proficiency in searching for records by their key, employing direct access
based on indexing or hashing for efficient retrieval.
5. Apply direct file organization techniques to various data management tasks, showcasing
algorithmic proficiency and problem-solving skills in file organization and record
management.
EXPERIMENT 37
AIM: Implement indexing for a file structure. Use a B-tree data structure for indexing and
provide functions for insertion, deletion, and searching based on index keys.
THEORY: Indexing for a file structure involves creating a separate data structure, such as a
B-tree, to efficiently access records in a file. B-trees offer balanced search trees with a
variable number of children per node, providing efficient search, insertion, and deletion
operations. Insertion functions add new index entries, while deletion functions remove
existing entries while maintaining tree balance. Searching functions utilize the B-tree
structure to quickly locate records based on index keys, enhancing search efficiency.
Implementing indexing with B-trees optimizes data access and manipulation, facilitating
rapid record retrieval and management in various database and file system applications.
SOURCE CODE:
#include <stdio.h>
#include <stdlib.h>

#define MAX_KEYS 4 // Maximum number of keys in a node

// Struct representing a B-tree node


typedef struct BTreeNode {
int numKeys; // Number of keys currently in the node
int keys[MAX_KEYS]; // Array of keys
struct BTreeNode *children[MAX_KEYS + 1]; // Pointers to
child nodes
int isLeaf; // Flag indicating whether the node is a leaf
} BTreeNode;

// Struct representing a B-tree


typedef struct {
BTreeNode *root; // Pointer to the root node
} BTree;

// Function prototypes
BTreeNode* createNode(int isLeaf);
BTreeNode* search(BTreeNode *root, int key);
void insert(BTree *tree, int key);
void insertNonFull(BTreeNode *node, int key);
void delete(BTree *tree, int key);
void deleteKey(BTreeNode *node, int key);
int findKeyIndex(BTreeNode *node, int key);
void removeFromLeaf(BTreeNode *node, int index);
void removeFromNonLeaf(BTreeNode *node, int index);
void fill(BTreeNode *node, int index);
void borrowFromPrev(BTreeNode *node, int index);
void borrowFromNext(BTreeNode *node, int index);
void merge(BTreeNode *node, int index);
int getPredecessor(BTreeNode *node, int index);
int getSuccessor(BTreeNode *node, int index);
void splitChild(BTreeNode *parent, int index);

// Function to create a new B-tree node


BTreeNode* createNode(int isLeaf) {
BTreeNode* newNode =
(BTreeNode*)malloc(sizeof(BTreeNode));
if (newNode == NULL) {
printf("Memory allocation failed!\n");
exit(EXIT_FAILURE);
}
newNode->numKeys = 0;
newNode->isLeaf = isLeaf;
for (int i = 0; i < MAX_KEYS + 1; ++i) {
newNode->children[i] = NULL;
}
return newNode;
}

// Function to search for a key in the B-tree


BTreeNode* search(BTreeNode *root, int key) {
int i = 0;
while (i < root->numKeys && key > root->keys[i]) {
++i;
}
if (i < root->numKeys && key == root->keys[i]) {
return root;
}
if (root->isLeaf) {
return NULL;
} else {
return search(root->children[i], key);
}
}
// Function to insert a key into a B-tree
void insert(BTree *tree, int key) {
BTreeNode *root = tree->root;
if (root->numKeys == MAX_KEYS) {
BTreeNode *newRoot = createNode(0);
newRoot->children[0] = root;
tree->root = newRoot;
splitChild(newRoot, 0);
root = newRoot;
}
insertNonFull(root, key);
}

// Function to insert a key into a non-full B-tree node


void insertNonFull(BTreeNode *node, int key) {
int i = node->numKeys - 1;
if (node->isLeaf) {
while (i >= 0 && key < node->keys[i]) {
node->keys[i + 1] = node->keys[i];
--i;
}
node->keys[i + 1] = key;
node->numKeys++;
} else {
while (i >= 0 && key < node->keys[i]) {
--i;
}
++i;
if (node->children[i]->numKeys == MAX_KEYS) {
splitChild(node, i);
if (key > node->keys[i]) {
++i;
}
}
insertNonFull(node->children[i], key);
}
}

// Function to delete a key from the B-tree


void delete(BTree *tree, int key) {
BTreeNode *root = tree->root;
if (!root) {
printf("B-tree is empty!\n");
return;
}
deleteKey(root, key);
}

// Function to delete a key from a B-tree node


void deleteKey(BTreeNode *node, int key) {
int index = findKeyIndex(node, key);
if (index < node->numKeys && node->keys[index] == key) {
if (node->isLeaf) {
removeFromLeaf(node, index);
} else {
removeFromNonLeaf(node, index);
}
} else {
if (node->isLeaf) {
printf("Key %d not found!\n", key);
return;
}
int lastKeyIndex = (index == node->numKeys) ? index -
1 : index;
BTreeNode *child = node->children[index];
if (child->numKeys < MAX_KEYS / 2) {
fill(child, index);
}
if (lastKeyIndex >= node->numKeys && index > node-
>numKeys) {
deleteKey(node->children[index - 1], key);
} else {
deleteKey(node->children[index], key);
}
}
}

// Function to find index of key in B-tree node


int findKeyIndex(BTreeNode *node, int key) {
int index = 0;
while (index < node->numKeys && node->keys[index] < key) {
++index;
}
return index;
}

// Function to remove key from a leaf node


void removeFromLeaf(BTreeNode *node, int index) {
for (int i = index + 1; i < node->numKeys; ++i) {
node->keys[i - 1] = node->keys[i];
}
node->numKeys--;
}

// Function to remove key from a non-leaf node


void removeFromNonLeaf(BTreeNode *node, int index) {
int key = node->keys[index];
if (node->children[index]->numKeys >= MAX_KEYS / 2) {
int predecessor = getPredecessor(node, index);
node->keys[index] = predecessor;
deleteKey(node->children[index], predecessor);
} else if (node->children[index + 1]->numKeys >= MAX_KEYS
/ 2) {
int successor = getSuccessor(node, index);
node->keys[index] = successor;
deleteKey(node->children[index + 1], successor);
} else {
merge(node, index);
deleteKey(node->children[index], key);
}
}

// Function to get predecessor key of a key


int getPredecessor(BTreeNode *node, int index) {
BTreeNode *curNode = node->children[index];
while (!curNode->isLeaf) {
curNode = curNode->children[curNode->numKeys];
}
return curNode->keys[curNode->numKeys - 1];
}

// Function to get successor key of a key


int getSuccessor(BTreeNode *node, int index) {
BTreeNode *curNode = node->children[index + 1];
while (!curNode->isLeaf) {
curNode = curNode->children[0];
}
return curNode->keys[0];
}

// Function to split a full child of a B-tree node


void splitChild(BTreeNode *parent, int index) {
BTreeNode *child = parent->children[index];
BTreeNode *newNode = createNode(child->isLeaf);
newNode->numKeys = MAX_KEYS / 2 - 1;

for (int i = 0; i < MAX_KEYS / 2 - 1; ++i) {


newNode->keys[i] = child->keys[i + MAX_KEYS / 2];
}

if (!child->isLeaf) {
for (int i = 0; i < MAX_KEYS / 2; ++i) {
newNode->children[i] = child->children[i +
MAX_KEYS / 2];
}
}

child->numKeys = MAX_KEYS / 2 - 1;

for (int i = parent->numKeys; i > index; --i) {


parent->children[i + 1] = parent->children[i];
}
parent->children[index + 1] = newNode;

for (int i = parent->numKeys - 1; i >= index; --i) {


parent->keys[i + 1] = parent->keys[i];
}

parent->keys[index] = child->keys[MAX_KEYS / 2 - 1];

parent->numKeys++;
}

// Function to fill a child node that has less than the


required number of keys
void fill(BTreeNode *node, int index) {
if (index != 0 && node->children[index - 1]->numKeys >=
MAX_KEYS / 2) {
borrowFromPrev(node, index);
} else if (index != node->numKeys && node->children[index
+ 1]->numKeys >= MAX_KEYS / 2) {
borrowFromNext(node, index);
} else {
if (index != node->numKeys) {
merge(node, index);
} else {
merge(node, index - 1);
}
}
}

// Function to borrow a key from the previous child of a node


void borrowFromPrev(BTreeNode *node, int index) {
BTreeNode *child = node->children[index];
BTreeNode *sibling = node->children[index - 1];

for (int i = child->numKeys - 1; i >= 0; --i) {


child->keys[i + 1] = child->keys[i];
}

if (!child->isLeaf) {
for (int i = child->numKeys; i >= 0; --i) {
child->children[i + 1] = child->children[i];
}
}

child->keys[0] = node->keys[index - 1];

if (!child->isLeaf) {
child->children[0] = sibling->children[sibling-
>numKeys];
}
node->keys[index - 1] = sibling->keys[sibling->numKeys -
1];

child->numKeys++;
sibling->numKeys--;
}

// Function to borrow a key from the next child of a node


void borrowFromNext(BTreeNode *node, int index) {
BTreeNode *child = node->children[index];
BTreeNode *sibling = node->children[index + 1];

child->keys[child->numKeys] = node->keys[index];

if (!child->isLeaf) {
child->children[child->numKeys + 1] = sibling-
>children[0];
}

node->keys[index] = sibling->keys[0];

for (int i = 1; i < sibling->numKeys; ++i) {


sibling->keys[i - 1] = sibling->keys[i];
}

if (!sibling->isLeaf) {
for (int i = 1; i <= sibling->numKeys; ++i) {
sibling->children[i - 1] = sibling->children[i];
}
}

child->numKeys++;
sibling->numKeys--;
}

// Function to merge two nodes


void merge(BTreeNode *node, int index) {
BTreeNode *child = node->children[index];
BTreeNode *sibling = node->children[index + 1];

child->keys[MAX_KEYS / 2] = node->keys[index];

for (int i = 0; i < sibling->numKeys; ++i) {


child->keys[i + MAX_KEYS / 2 + 1] = sibling->keys[i];
}

if (!child->isLeaf) {
for (int i = 0; i <= sibling->numKeys; ++i) {
child->children[i + MAX_KEYS / 2 + 1] = sibling-
>children[i];
}
}

for (int i = index + 1; i < node->numKeys; ++i) {


node->keys[i - 1] = node->keys[i];
}

for (int i = index + 2; i <= node->numKeys; ++i) {


node->children[i - 1] = node->children[i];
}

child->numKeys += sibling->numKeys + 1;
node->numKeys--;
free(sibling);
}

// Function to create a B-tree


BTree* createBTree() {
BTree* tree = (BTree*)malloc(sizeof(BTree));
if (tree == NULL) {
printf("Memory allocation failed!\n");
exit(EXIT_FAILURE);
}
tree->root = createNode(1);
return tree;
}

// Function to display the B-tree


void displayBTree(BTreeNode *root) {
if (root != NULL) {
printf("[");
for (int i = 0; i < root->numKeys; ++i) {
printf("%d", root->keys[i]);
if (i < root->numKeys - 1) {
printf(", ");
}
}
printf("] ");
if (!root->isLeaf) {
for (int i = 0; i <= root->numKeys; ++i) {
displayBTree(root->children[i]);
}
}
}
}

int main() {
BTree* tree = createBTree();

// Insert keys into the B-tree


insert(tree, 10);
insert(tree, 20);
insert(tree, 5);
insert(tree, 6);
insert(tree, 12);
insert(tree, 30);

printf("B-tree after insertion: ");


displayBTree(tree->root);
printf("\n");

// Delete keys from the B-tree


delete(tree, 10);
delete(tree, 6);

printf("B-tree after deletion: ");


displayBTree(tree->root);
printf("\n");

free(tree);

return 0;
}

OUTPUT:
LEARNING OUTCOME:
1. Develop a thorough understanding of indexing techniques using B-trees, comprehending
their role in enhancing data access efficiency in file structures.
2. Implement B-tree indexing effectively, demonstrating proficiency in constructing and
maintaining balanced search trees for efficient record retrieval.
3. Master insertion functions, efficiently adding index entries to the B-tree while ensuring
structural integrity and balance.
4. Demonstrate proficiency in deletion functions, effectively removing index entries while
preserving the B-tree's balance and search properties.
5. Apply B-tree indexing to enable rapid searching based on index keys, showcasing
algorithmic proficiency and problem-solving skills in data management and retrieval tasks.
EXPERIMENT 38
AIM: Implement hashing for file organization. Include functions for hash table creation,
insertion, deletion, and searching using collision resolution techniques such as chaining.
THEORY: Hashing for file organization involves mapping data keys to unique addresses in
a hash table, facilitating efficient data retrieval. Functions for hash table creation initialize an
array to store key-value pairs. Insertion functions hash keys to determine storage locations
and handle collisions using chaining, appending entries to linked lists at collision points.
Deletion functions locate and remove entries from the hash table, maintaining consistency.
Searching functions hash keys to retrieve stored values, navigating linked lists for collision
resolution. Hashing with chaining optimizes data access, offering constant-time average
retrieval, ideal for applications requiring rapid search operations on large datasets.
SOURCE CODE:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define TABLE_SIZE 10

// Define a structure for a node in the hash table (for


chaining)
struct Node {
char* key;
int value;
struct Node* next;
};

// Define a structure for the hash table


struct HashTable {
struct Node* table[TABLE_SIZE];
};

// Hash function to map a string key to an index in the hash


table
int hash(char* key) {
int hash_value = 0;
int len = strlen(key);
for (int i = 0; i < len; ++i) {
hash_value += key[i];
}
return hash_value % TABLE_SIZE;
}

// Function to create a new node


struct Node* createNode(char* key, int value) {
struct Node* newNode = (struct Node*)malloc(sizeof(struct
Node));
newNode->key = strdup(key);
newNode->value = value;
newNode->next = NULL;
return newNode;
}

// Function to create a new hash table


struct HashTable* createHashTable() {
struct HashTable* hashTable = (struct
HashTable*)malloc(sizeof(struct HashTable));
for (int i = 0; i < TABLE_SIZE; ++i) {
hashTable->table[i] = NULL;
}
return hashTable;
}

// Function to insert a key-value pair into the hash table


void insert(struct HashTable* hashTable, char* key, int value)
{
int index = hash(key);
struct Node* newNode = createNode(key, value);

// Check if the index is empty


if (hashTable->table[index] == NULL) {
hashTable->table[index] = newNode;
} else {
// Collision occurred, append the new node to the end
of the list
struct Node* current = hashTable->table[index];
while (current->next != NULL) {
current = current->next;
}
current->next = newNode;
}
}

// Function to search for a key in the hash table


struct Node* search(struct HashTable* hashTable, char* key) {
int index = hash(key);
struct Node* current = hashTable->table[index];
while (current != NULL) {
if (strcmp(current->key, key) == 0) {
return current;
}
current = current->next;
}
return NULL; // Key not found
}

// Function to delete a key from the hash table


void deleteKey(struct HashTable* hashTable, char* key) {
int index = hash(key);
struct Node* current = hashTable->table[index];
struct Node* prev = NULL;

// Search for the key in the chain


while (current != NULL && strcmp(current->key, key) != 0)
{
prev = current;
current = current->next;
}

// If key is not found


if (current == NULL) {
printf("Key not found\n");
return;
}

// If the key is found at the head of the chain


if (prev == NULL) {
hashTable->table[index] = current->next;
} else {
prev->next = current->next;
}

free(current->key);
free(current);
}

// Function to display the hash table


void display(struct HashTable* hashTable) {
for (int i = 0; i < TABLE_SIZE; ++i) {
struct Node* current = hashTable->table[i];
printf("Index %d: ", i);
while (current != NULL) {
printf("(%s, %d) -> ", current->key, current-
>value);
current = current->next;
}
printf("NULL\n");
}
}

// Main function
int main() {
struct HashTable* hashTable = createHashTable();

insert(hashTable, "John", 25);


insert(hashTable, "Jane", 30);
insert(hashTable, "Doe", 40);

printf("Initial Hash Table:\n");


display(hashTable);

printf("\nSearching for 'John':\n");


struct Node* result = search(hashTable, "John");
if (result != NULL) {
printf("Value found: %d\n", result->value);
} else {
printf("Key not found\n");
}

printf("\nDeleting 'John':\n");
deleteKey(hashTable, "John");
display(hashTable);

// Free memory
for (int i = 0; i < TABLE_SIZE; ++i) {
struct Node* current = hashTable->table[i];
while (current != NULL) {
struct Node* temp = current;
current = current->next;
free(temp->key);
free(temp);
}
}
free(hashTable);

return 0;
}

OUTPUT:
LEARNING OUTCOME:
1. Gain a comprehensive understanding of hashing techniques for file organization, including
the creation of hash tables to efficiently store key-value pairs.
2. Implement hash table creation functions effectively, demonstrating proficiency in
initializing arrays and setting up storage structures.
3. Master insertion functions, efficiently hashing keys and resolving collisions using chaining
to append entries to linked lists.
4. Demonstrate proficiency in deletion functions, effectively locating and removing entries
from the hash table while maintaining data integrity.
5. Apply searching functions to hash keys and retrieve stored values, showcasing algorithmic
proficiency and problem-solving skills in data management and retrieval tasks.
EXPERIMENT 39
AIM: Implement linear probing collision resolution technique in C for a hash table. Write a
program that demonstrates insertion, deletion, and searching operations using linear probing.
THEORY: Linear probing is a collision resolution technique used in hash tables to handle
collisions by probing sequential locations in the table until an empty slot is found. In C, a
hash table with linear probing can be implemented using an array and appropriate hash
functions. Insertion involves hashing the key and probing linearly until an empty slot is
found. Deletion locates the key's slot and marks it as deleted. Searching follows a similar
process, probing linearly until the key is found or an empty slot is encountered. This program
demonstrates efficient insertion, deletion, and searching operations using linear probing for
collision resolution.
SOURCE CODE:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define TABLE_SIZE 10

// Define a structure for a hash table entry


struct HashEntry {
char* key;
int value;
int deleted; // Flag to mark deleted entries
};

// Define a structure for the hash table


struct HashTable {
struct HashEntry* table[TABLE_SIZE];
};

// Hash function to map a string key to an index in the hash


table
int hash(char* key) {
int hash_value = 0;
int len = strlen(key);
for (int i = 0; i < len; ++i) {
hash_value += key[i];
}
return hash_value % TABLE_SIZE;
}

// Function to create a new hash table


struct HashTable* createHashTable() {
struct HashTable* hashTable = (struct
HashTable*)malloc(sizeof(struct HashTable));
for (int i = 0; i < TABLE_SIZE; ++i) {
hashTable->table[i] = NULL;
}
return hashTable;
}

// Function to create a new hash entry


struct HashEntry* createHashEntry(char* key, int value) {
struct HashEntry* entry = (struct
HashEntry*)malloc(sizeof(struct HashEntry));
entry->key = strdup(key);
entry->value = value;
entry->deleted = 0;
return entry;
}

// Function to insert a key-value pair into the hash table


using linear probing
void insert(struct HashTable* hashTable, char* key, int value)
{
int index = hash(key);

// Linear probing to find an empty slot


while (hashTable->table[index] != NULL && hashTable-
>table[index]->deleted == 0) {
index = (index + 1) % TABLE_SIZE;
}

// Insert the entry


hashTable->table[index] = createHashEntry(key, value);
}

// Function to search for a key in the hash table


int search(struct HashTable* hashTable, char* key) {
int index = hash(key);

// Linear probing to find the key


while (hashTable->table[index] != NULL) {
if (strcmp(hashTable->table[index]->key, key) == 0 &&
hashTable->table[index]->deleted == 0) {
return index;
}
index = (index + 1) % TABLE_SIZE;
}

// Key not found


return -1;
}

// Function to delete a key from the hash table


void deleteKey(struct HashTable* hashTable, char* key) {
int index = search(hashTable, key);
if (index != -1) {
free(hashTable->table[index]->key);
free(hashTable->table[index]);
hashTable->table[index] = NULL; // Mark as deleted
printf("Key '%s' deleted successfully.\n", key);
} else {
printf("Key '%s' not found.\n", key);
}
}

// Function to display the hash table


void display(struct HashTable* hashTable) {
for (int i = 0; i < TABLE_SIZE; ++i) {
if (hashTable->table[i] != NULL && hashTable-
>table[i]->deleted == 0) {
printf("Index %d: (%s, %d)\n", i, hashTable-
>table[i]->key, hashTable->table[i]->value);
} else {
printf("Index %d: Empty\n", i);
}
}
}

// Main function
int main() {
struct HashTable* hashTable = createHashTable();

insert(hashTable, "John", 25);


insert(hashTable, "Jane", 30);
insert(hashTable, "Doe", 40);

printf("Initial Hash Table:\n");


display(hashTable);

printf("\nSearching for 'John':\n");


int index = search(hashTable, "John");
if (index != -1) {
printf("Value found: %d\n", hashTable->table[index]-
>value);
} else {
printf("Key not found\n");
}

printf("\nDeleting 'John':\n");
deleteKey(hashTable, "John");
display(hashTable);

// Free memory
for (int i = 0; i < TABLE_SIZE; ++i) {
if (hashTable->table[i] != NULL) {
free(hashTable->table[i]->key);
free(hashTable->table[i]);
}
}
free(hashTable);

return 0;
}

OUTPUT:
LEARNING OUTCOME:
1. Develop a comprehensive understanding of linear probing as a collision resolution
technique in hash tables, grasping its sequential probing strategy to handle collisions
efficiently.
2. Implement linear probing collision resolution technique in C, demonstrating proficiency in
inserting, deleting, and searching for elements in a hash table.
3. Master the process of hashing keys and probing linearly until an empty slot is found for
insertion, ensuring data integrity and efficient storage.
4. Demonstrate proficiency in locating and marking deleted slots during deletion operations,
maintaining the structure of the hash table.
5. Apply linear probing to efficiently resolve collisions and demonstrate insertion, deletion,
and searching operations in a hash table, showcasing algorithmic proficiency in data
management and retrieval tasks.
EXPERIMENT 40
AIM: Implement separate chaining collision resolution technique for hashing. Include
functions for hash table creation, insertion, deletion, and searching using separate chaining.
THEORY: Separate chaining is a collision resolution technique in hashing that involves
creating a linked list for each bucket in the hash table to handle collisions. Functions for hash
table creation initialize an array of linked lists. Insertion functions hash keys to determine
bucket locations and append elements to the corresponding linked list. Deletion functions
locate and remove elements from the linked lists while maintaining data integrity. Searching
functions hash keys to retrieve elements and traverse the appropriate linked list to find the
desired element. Separate chaining offers flexibility and efficiency in handling collisions,
ensuring optimal performance in hash table operations.
SOURCE CODE:
#include <stdio.h>
#include <stdlib.h>

#define TABLE_SIZE 10

// Structure for a node in the hash table


typedef struct Node {
int key;
int value;
struct Node *next;
} Node;

// Structure for the hash table


typedef struct {
Node *table[TABLE_SIZE];
} HashTable;

// Function to create a new node


Node* createNode(int key, int value) {
Node *newNode = (Node*)malloc(sizeof(Node));
if (newNode == NULL) {
printf("Memory allocation failed!\n");
exit(EXIT_FAILURE);
}
newNode->key = key;
newNode->value = value;
newNode->next = NULL;
return newNode;
}

// Function to create a new hash table


HashTable* createHashTable() {
HashTable *hashTable =
(HashTable*)malloc(sizeof(HashTable));
if (hashTable == NULL) {
printf("Memory allocation failed!\n");
exit(EXIT_FAILURE);
}
for (int i = 0; i < TABLE_SIZE; ++i) {
hashTable->table[i] = NULL;
}
return hashTable;
}

// Function to compute the hash value for a key


int hash(int key) {
return key % TABLE_SIZE;
}

// Function to insert a key-value pair into the hash table


void insert(HashTable *hashTable, int key, int value) {
int index = hash(key);
Node *newNode = createNode(key, value);
if (hashTable->table[index] == NULL) {
hashTable->table[index] = newNode;
} else {
Node *current = hashTable->table[index];
while (current->next != NULL) {
current = current->next;
}
current->next = newNode;
}
}

// Function to search for a key in the hash table


Node* search(HashTable *hashTable, int key) {
int index = hash(key);
Node *current = hashTable->table[index];
while (current != NULL) {
if (current->key == key) {
return current;
}
current = current->next;
}
return NULL;
}

// Function to delete a key from the hash table


void delete(HashTable *hashTable, int key) {
int index = hash(key);
Node *current = hashTable->table[index];
Node *prev = NULL;
while (current != NULL && current->key != key) {
prev = current;
current = current->next;
}
if (current == NULL) {
printf("Key %d not found!\n", key);
return;
}
if (prev == NULL) {
hashTable->table[index] = current->next;
} else {
prev->next = current->next;
}
free(current);
}

// Function to display the hash table


void displayHashTable(HashTable *hashTable) {
for (int i = 0; i < TABLE_SIZE; ++i) {
printf("Index %d: ", i);
Node *current = hashTable->table[i];
while (current != NULL) {
printf("(%d, %d) ", current->key, current->value);
current = current->next;
}
printf("\n");
}
}

int main() {
HashTable *hashTable = createHashTable();

// Insert some key-value pairs


insert(hashTable, 10, 100);
insert(hashTable, 20, 200);
insert(hashTable, 5, 50);
insert(hashTable, 15, 150);

// Display the hash table


printf("Hash table after insertion:\n");
displayHashTable(hashTable);

// Search for a key


int searchKey = 5;
Node *result = search(hashTable, searchKey);
if (result != NULL) {
printf("Key %d found with value %d.\n", searchKey,
result->value);
} else {
printf("Key %d not found.\n", searchKey);
}

// Delete a key
int deleteKey = 20;
delete(hashTable, deleteKey);
printf("Hash table after deletion of key %d:\n",
deleteKey);
displayHashTable(hashTable);
// Free memory
for (int i = 0; i < TABLE_SIZE; ++i) {
Node *current = hashTable->table[i];
while (current != NULL) {
Node *temp = current;
current = current->next;
free(temp);
}
}
free(hashTable);

return 0;
}

OUTPUT:

LEARNING OUTCOME:
1. Gain a comprehensive understanding of separate chaining as a collision resolution
technique in hashing, comprehending its use of linked lists to manage collisions efficiently.
2. Implement separate chaining collision resolution technique in a hash table, demonstrating
proficiency in creating a hash table with an array of linked lists.
3. Master insertion functions, efficiently hashing keys and appending elements to the
corresponding linked list to handle collisions.
4. Demonstrate proficiency in deletion functions, effectively locating and removing elements
from linked lists while preserving data integrity.
5. Apply searching functions to hash keys and retrieve elements from the appropriate linked
list, showcasing algorithmic proficiency in data management and retrieval tasks using
separate chaining.

EXPERIMENT 41
AIM: - Implement rehashing technique for handling collisions in hashing. Include functions
for hash table creation, insertion, deletion, and searching with rehashing when the load factor
exceeds a specified threshold.
THEORY: Rehashing is a collision resolution technique in hashing that involves
dynamically resizing the hash table and redistributing elements when the load factor exceeds
a specified threshold. Functions for hash table creation initialize an array with an initial
capacity. Insertion functions calculate the load factor and trigger rehashing when it exceeds
the threshold, resizing the table and redistributing elements. Deletion functions update the
load factor and may trigger rehashing if it falls below a specified threshold. Searching
functions hash keys and locate elements in the resized hash table. Rehashing ensures optimal
performance by maintaining an appropriate load factor and minimizing collisions in the hash
table.
SOURCE CODE:
#include <stdio.h>
#include <stdlib.h>

#define INITIAL_CAPACITY 10
#define LOAD_FACTOR_THRESHOLD 0.7

// Structure for a node in the hash table


typedef struct Node {
int key;
int value;
struct Node *next;
} Node;

// Structure for the hash table


typedef struct {
Node **table;
int capacity;
int size;
} HashTable;

// Function to create a new node


Node* createNode(int key, int value) {
Node *newNode = (Node*)malloc(sizeof(Node));
if (newNode == NULL) {
printf("Memory allocation failed!\n");
exit(EXIT_FAILURE);
}
newNode->key = key;
newNode->value = value;
newNode->next = NULL;
return newNode;
}

// Function to create a new hash table


HashTable* createHashTable() {
HashTable *hashTable =
(HashTable*)malloc(sizeof(HashTable));
if (hashTable == NULL) {
printf("Memory allocation failed!\n");
exit(EXIT_FAILURE);
}
hashTable->capacity = INITIAL_CAPACITY;
hashTable->table = (Node**)malloc(hashTable->capacity *
sizeof(Node*));
if (hashTable->table == NULL) {
printf("Memory allocation failed!\n");
exit(EXIT_FAILURE);
}
hashTable->size = 0;
for (int i = 0; i < hashTable->capacity; ++i) {
hashTable->table[i] = NULL;
}
return hashTable;
}
// Function to compute the hash value for a key
int hash(int key, int capacity) {
return key % capacity;
}

// Function to insert a key-value pair into the hash table


void insert(HashTable *hashTable, int key, int value) {
if ((double)hashTable->size / hashTable->capacity >
LOAD_FACTOR_THRESHOLD) {
// Rehashing if load factor exceeds the threshold
int newCapacity = hashTable->capacity * 2;
Node **newTable = (Node**)malloc(newCapacity *
sizeof(Node*));
if (newTable == NULL) {
printf("Memory allocation failed!\n");
exit(EXIT_FAILURE);
}
for (int i = 0; i < newCapacity; ++i) {
newTable[i] = NULL;
}
for (int i = 0; i < hashTable->capacity; ++i) {
Node *current = hashTable->table[i];
while (current != NULL) {
Node *next = current->next;
int newIndex = hash(current->key,
newCapacity);
current->next = newTable[newIndex];
newTable[newIndex] = current;
current = next;
}
}
free(hashTable->table);
hashTable->table = newTable;
hashTable->capacity = newCapacity;
}
int index = hash(key, hashTable->capacity);
Node *newNode = createNode(key, value);
newNode->next = hashTable->table[index];
hashTable->table[index] = newNode;
hashTable->size++;
}

// Function to search for a key in the hash table


Node* search(HashTable *hashTable, int key) {
int index = hash(key, hashTable->capacity);
Node *current = hashTable->table[index];
while (current != NULL) {
if (current->key == key) {
return current;
}
current = current->next;
}
return NULL;
}

// Function to delete a key from the hash table


void delete(HashTable *hashTable, int key) {
int index = hash(key, hashTable->capacity);
Node *current = hashTable->table[index];
Node *prev = NULL;
while (current != NULL && current->key != key) {
prev = current;
current = current->next;
}
if (current == NULL) {
printf("Key %d not found!\n", key);
return;
}
if (prev == NULL) {
hashTable->table[index] = current->next;
} else {
prev->next = current->next;
}
free(current);
hashTable->size--;
}

// Function to display the hash table


void displayHashTable(HashTable *hashTable) {
for (int i = 0; i < hashTable->capacity; ++i) {
printf("Index %d: ", i);
Node *current = hashTable->table[i];
while (current != NULL) {
printf("(%d, %d) ", current->key, current->value);
current = current->next;
}
printf("\n");
}
}

int main() {
HashTable *hashTable = createHashTable();

// Insert some key-value pairs


insert(hashTable, 10, 100);
insert(hashTable, 20, 200);
insert(hashTable, 5, 50);
insert(hashTable, 15, 150);

// Display the hash table


printf("Hash table after insertion:\n");
displayHashTable(hashTable);
// Search for a key
int searchKey = 5;
Node *result = search(hashTable, searchKey);
if (result != NULL) {
printf("Key %d found with value %d.\n", searchKey,
result->value);
} else {
printf("Key %d not found.\n", searchKey);
}

// Delete a key
int deleteKey = 20;
delete(hashTable, deleteKey);
printf("Hash table after deletion of key %d:\n",
deleteKey);
displayHashTable(hashTable);

// Free memory
for (int i = 0; i < hashTable->capacity; ++i) {
Node *current = hashTable->table[i];
while (current != NULL) {
Node *temp = current;
current = current->next;
free(temp);
}
}
free(hashTable->table);
free(hashTable);

return 0;
}

OUTPUT:
LEARNING OUTCOME:
1. Develop a comprehensive understanding of rehashing as a technique for handling
collisions in hashing, recognizing its role in dynamically resizing the hash table.
2. Implement rehashing effectively, demonstrating proficiency in creating hash tables with
functions for insertion, deletion, and searching.
3. Master insertion functions, efficiently monitoring the load factor and triggering rehashing
when it exceeds a specified threshold to maintain optimal performance.
4. Demonstrate proficiency in deletion functions, updating the load factor and potentially
triggering rehashing to ensure balanced data distribution.
5. Apply searching functions to locate elements in the hash table, showcasing algorithmic
proficiency and problem-solving skills in data management and retrieval tasks with
rehashing.

You might also like