Algo Lab Report 1
Algo Lab Report 1
Algo Lab Report 1
Sample input-output:
Enter size of array :7
Original array: 64 34 25 12 11 22 90
Sorted array: 11 12 22 25 34 64 90
Remarks:
Quick Sort is a highly efficient sorting algorithm that sorts arrays by dividing them into sub-arrays and recursively sorting those sub-arrays. It's widely used in practice due to its average-case time complexity
of O(n log n). However, its performance can degrade to O(n^2) if the pivot selection is not well-balanced. Quick Sort is a staple in the field of algorithms and is often used as a benchmark for comparing the
efficiency of other sorting algorithms.
5.Problem Name:Sorting an Array using Merge Sort
Analysis:Merge Sort is a widely used sorting algorithm known for its efficiency and stability. It follows the divide-and-conquer strategy by dividing the array into two halves, recursively sorting each half,
and then merging the sorted halves back together. Merge Sort has a consistent time complexity of O(n log n), making it efficient for sorting large datasets.
Algorithm:
procedure mergeSort(A : list of sortable items, low, high)
if low < high
mid = (low + high) / 2
mergeSort(A, low, mid)
mergeSort(A, mid + 1, high)
merge(A, low, mid, high)
end procedure
procedure merge(A : list of sortable items, low, mid, high)
n1 = mid - low + 1
n2 = high - mid
create temporary arrays L[0...n1] and R[0...n2]
for i = 0 to n1 - 1
L[i] = A[low + i]
for j = 0 to n2 - 1
R[j] = A[mid + 1 + j]
i=0
j=0
k = low
while i < n1 and j < n2
if L[i] <= R[j]
A[k] = L[i]
i=i+1
else
A[k] = R[j]
j=j+1
k=k+1
while i < n1
A[k] = L[i]
i=i+1
k=k+1
while j < n2
A[k] = R[j]
j=j+1
k=k+1
end procedure
Source code:
#include<bits/stdc++.h>
using namespace std;
#include <iostream>
void merge(int arr[], int low, int mid, int high)
{
int n1 = mid - low + 1;
int n2 = high - mid;
int L[n1], R[n2];
for (int i = 0; i < n1; ++i)
{
L[i] = arr[low + i];
}
for (int j = 0; j < n2; ++j)
{
R[j] = arr[mid + 1 + j];
}
int i = 0, j = 0, k = low;
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 low, int high)
{
if (low < high)
{
int mid = low + (high - low) / 2;
mergeSort(arr, low, mid);
mergeSort(arr, mid + 1, high);
merge(arr, low, mid, high);
}
}
int main()
{
int n;
cout<<"Enter size of array :";
cin>>n;
int arr[n+5];
cout << "Original array: ";
for (int i = 0; i < n; ++i)
{
cin>>arr[i];
}
mergeSort(arr, 0, n - 1);
cout << "Sorted array: ";
for (int i = 0; i < n; ++i)
{
cout << arr[i] << " ";
}
return 0;
}
Sample input-output:
Enter size of array :8
Original array: 12 34 62 54 12 33 85 10
Sorted array: 10 12 12 33 34 54 62 85
Remarks:
Merge Sort efficiently sorts arrays by dividing them into sub-arrays, sorting the sub-arrays, and then merging them back together. It has a consistent time complexity of O(n log n), making it one of the most
efficient sorting algorithms. Merge Sort is often used in situations where stability and consistent performance are required. It's also used as the basis for other sorting algorithms, such as Tim Sort.
6.Problem Name:sorting an array using radix sort.
Analysis:
Radix Sort is a non-comparative sorting algorithm that sorts integers by processing individual digits. It starts from the least significant digit (LSB) and works its way to the most significant digit (MSB),
repeatedly using a stable sorting algorithm (usually counting sort) to sort the elements based on the current digit.
Algorithm:
RADIX_SORT(A, d)
// A is an array to be sorted
// k is the number of digits in the largest element in A
for i ← 1 to k do
Apply a stable sort (such as counting sort) on digit i of array A
End
Source Code:
#include <iostream>
using namespace std;
int getMax(int arr[], int n) {
int max = arr[0];
for (int i = 1; i < n; i++) {
if (arr[i] > max)
max = arr[i]; }
return max;}
void countingSort(int arr[], int n, int exp) {
int output[n];
int count[10] = {0};
for (int i = 0; i < n; i++)
count[(arr[i] / exp) % 10]++;
for (int i = 1; i < 10; i++)
count[i] += count[i - 1];
for (int i = n - 1; i >= 0; i--) {
output[count[(arr[i] / exp) % 10] - 1] = arr[i];
count[(arr[i] / exp) % 10]--;}
for (int i = 0; i < n; i++)
arr[i] = output[i];}
void radixSort(int arr[], int n) {
int max = getMax(arr, n);
for (int exp = 1; max / exp > 0; exp *= 10)
countingSort(arr, n, exp);}
void printArr(int a[], int n) {
for (int i = 0; i < n; i++)
cout << a[i] << " ";
cout << endl;}
int main() {
int n;
cout << "Enter the number of elements: ";
cin >> n;
int a[n];
cout << "Enter the elements:" << endl;
for (int i = 0; i < n; i++)
cin >> a[i];
cout << "Before sorting: ";
printArr(a, n);
radixSort(a, n);
cout << "After sorting: ";
printArr(a, n);
return 0;}
Sample Input:
6
170 45 75 90 802 24
Sample Output:
Before sorting: 170 45 75 90 802 24
After sorting: 24 45 75 90 170 802
Remarks:Radix Sort has a time complexity of O(d * (n + k)), where d is the number of digits and k is the range of input. It can be efficient for larger datasets when the range of input is not significantly large.
7.Problem: Sorting with Heap Sort
Analysis:
Heap Sort is a comparison-based sorting algorithm that utilizes the properties of a binary heap. A heap is an advanced tree-based data structure used primarily for sorting and implementing priority queues.
They are complete binary trees that have the following features:
Every level is filled except the leaf nodes (nodes without children are called leaves).
Every node has a maximum of 2 children.
All the nodes are as far left as possible, this means that every child is to the left of his parent.
Algorithm:
HeapSort(arr)
BuildMaxHeap(arr)
for i = length(arr) down to 2
swap arr[1] with arr[i]
heap_size[arr] = heap_size[arr] - 1
MaxHeapify(arr, 1)
End
BuildMaxHeap(arr)
heap_size(arr) = length(arr)
for i = length(arr) / 2 down to 1
MaxHeapify(arr, i)
End
MaxHeapify(arr, i)
L = left(i)
R = right(i)
if L ≤ heap_size[arr] and arr[L] > arr[i]
largest = L
else
largest = i
if R ≤ heap_size[arr] and arr[R] > arr[largest]
largest = R
if largest ≠ i
swap arr[i] with arr[largest]
MaxHeapify(arr, largest)
End
Source Code:
#include<iostream>
using namespace std;
void MaxHeapify(int A[], int n, int i) {
int largest = i;
int l = 2 * i;
int r = 2 * i + 1;
if (l <= n && A[l] > A[largest]) {
largest = l;}
if (r <= n && A[r] > A[largest]) {
largest = r; }
if (largest != i) {
swap(A[largest], A[i]);
MaxHeapify(A, n, largest);}}
void heapSort(int A[], int n) {
for (int i = n / 2; i >= 1; i--)
MaxHeapify(A, n, i);
for (int i = n; i >1; i--) {
swap(A[1], A[i]);
MaxHeapify(A, i - 1, 1); }}
void printArray(int A[], int n) {
for (int i = 1; i <= n; ++i)
cout << A[i] << " ";
cout << "\n";}
int main() {
int n;
cout << "Enter the number of elements: ";
cin >> n;
int A[n + 1];
cout << "Enter " << n << " elements:\n";
for (int i = 1; i <= n; i++)
cin >> A[i];
cout << "Original array:\n";
printArray(A, n);
heapSort(A, n);
cout << "Sorted array:\n";
printArray(A, n);
return 0; }
Sample Input:
4
35 21 95 17
Sample Output:
Before sorting: 35 21 95 17
After sorting: 17 21 35 95
Remarks: Heap Sort has a consistent time complexity of O(n log n), making it efficient for most scenarios. It is an in-place sorting algorithm that doesn't require additional memory space. Heaps generally
take more time to compute.
8.Problem Name: Solving the Tower of Hanoi Puzzle
Analysis:
The Tower of Hanoi is a classic problem in computer science and mathematics. It involves moving a stack of discs from one peg to another, with the constraint that only one disc can be moved at a time, and
a larger disc cannot be placed on top of a smaller disc. The problem can be solved recursively, and the number of moves required follows the pattern 2^n - 1, where n is the number of discs.
Algorithm (Pseudo Code):
procedure towerOfHanoi(n, source, auxiliary, destination)
if n > 0
towerOfHanoi(n-1, source, destination, auxiliary)
move disc from source to destination
towerOfHanoi(n-1, auxiliary, source, destination)
end procedure
Source Code (C++):
#include <iostream>
using namespace std;
void towerOfHanoi(int n, char source, char auxiliary, char destination)
{
if (n > 0)
{
towerOfHanoi(n - 1, source, destination, auxiliary);
std::cout << "Move disc " << n << " from " << source << " to " << destination << std::endl;
towerOfHanoi(n - 1, auxiliary, source, destination);
}
}
int main()
{
int n = 3; // Number of discs
towerOfHanoi(n, 'A', 'B', 'C');
return 0;
}
Sample Input/Output:
Input: Number of discs = 3
Output:
Move disc 1 from A to C
Move disc 2 from A to B
Move disc 1 from C to B
Move disc 3 from A to C
Move disc 1 from B to A
Move disc 2 from B to C
Move disc 1 from A to C
Remarks:
The Tower of Hanoi problem is a classic example of recursive problem-solving. The algorithm uses the concept of breaking a larger problem into smaller sub-problems and solving them recursively. The
number of moves required to solve the Tower of Hanoi puzzle for n discs is 2^n - 1, making it an interesting problem for exploring the power of recursion in algorithms.
9.Problem Name: Traversing a Graph using Breadth-First Search
Analysis:
Breadth-First Search (BFS) is a graph traversal algorithm that explores all the vertices of a graph in breadth-first order, i.e., it visits all the vertices at distance 1 from the starting vertex, then all the vertices at
distance 2, and so on. BFS is often used to find the shortest path between two nodes in an unweighted graph.
Algorithm (Pseudo Code):
procedure BFS(graph, start)
create a queue Q
enqueue start into Q
mark start as visited
while Q is not empty
current = dequeue from Q
process current vertex
for each neighbor of current
if neighbor is not visited
mark neighbor as visited
enqueue neighbor into Qend procedure
Source Code :
#include<bits/stdc++.h>
using namespace std;
#include <iostream>
#include <list>
#include <queue>
class Graph
{
int vertices;
std::list<int> *adjacencyList;
public:
Graph(int v);
void addEdge(int v, int w);
void BFS(int start);
};
Graph::Graph(int v)
{
vertices = v;
adjacencyList = new std::list<int>[v];
}
void Graph::addEdge(int v, int w)
{
adjacencyList[v].push_back(w);
}
void Graph::BFS(int start)
{
std::vector<bool> visited(vertices, false);
std::queue<int> q;
visited[start] = true;
q.push(start);
while (!q.empty())
{
int current = q.front();
q.pop();
std::cout << current << " ";
for (int neighbor : adjacencyList[current])
{
if (!visited[neighbor])
{
visited[neighbor] = true;
q.push(neighbor);
}
}
}
}
int main()
{
cout<<"Number of edges :";
int n;
cin>>n;
Graph g(n);
cout<<"enter edges:\n";
for(int i=1;i<n;i++)
{
int a,b;
cin>>a>>b;
g.addEdge(a,b);
}
std::cout << "BFS traversal starting from vertex 0: ";
g.BFS(0);
return 0;
}
Sample Input/Output:
Number of edges :7
enter edges:
01
02
13
14
25
26
BFS traversal starting from vertex 0: 0 1 2 3 4 5 6
Remarks: Breadth-First Search is a fundamental graph traversal algorithm that ensures all vertices at distance d from the starting vertex are visited before moving to vertices at distance d+1. It's widely used
in shortest path algorithms and finding connected components. BFS is particularly effective for unweighted graphs and guarantees the shortest path when all edges have the same weight.
10.Problem name: Implement Depth-First Search (DFS)
Analysis:Depth-First Search is a graph traversal algorithm that explores as far as possible along each branch before backtracking. It uses a stack (or recursion) to keep track of the vertices to be explored and
explores one branch as deeply as possible before moving to the next branch.
Algorithm:
Step 1: SET STATUS = 1 (ready state) for each node in G
Step 2:Push the starting node A on the stack and set its STATUS = 2 (waiting state)
Step 3: Repeat Steps 4 and 5 until STACK is empty
Step 4:Pop the top node N. Process it and set its STATUS = 3 (processed state)
Step 5: Push on the stack all the neighbors of N that are in the ready state (whose STATUS = 1) and set their STATUS = 2 (waiting state)
[END OF LOOP]
Step 6: EXIT
Source Code:
#include <iostream>
#include <vector>
#include <stack>
using namespace std;
void dfs(vector<vector<int>>& graph, int start_vertex) {
int vertices = graph.size();
vector<bool> visited(vertices, false);
stack<int> s;
s.push(start_vertex);
visited[start_vertex] = true;
while (!s.empty()) {
int current_vertex = s.top();
s.pop();
cout << current_vertex << " ";
for (int neighbor : graph[current_vertex]) {
if (!visited[neighbor]) {
s.push(neighbor);
visited[neighbor] = true; }}}}
int main() {
int vertices, edges;
cout << "Enter the number of vertices: ";
cin >> vertices;
cout << "Enter the number of edges: ";
cin >> edges;
vector<vector<int>> graph(vertices);
cout << "Enter the edges (vertex pairs):" << endl;
for (int i = 0; i < edges; i++) {
int u, v;
cin >> u >> v;
graph[u].push_back(v);
graph[v].push_back(u); }
int start_vertex;
cout << "Enter the start vertex: ";
cin >> start_vertex;
cout << "DFS traversal starting from vertex " << start_vertex << ": ";
dfs(graph, start_vertex);
cout << endl;
return 0;}
Sample Input:
5
6
01
02
13
14
24
34
0
Sample Output:
DFS traversal starting from vertex 0: 0 2 4 1 3
Remarks:The DFS algorithm explores the depth of the graph before backtracking. The order of traversal depends on the order in which neighbors are pushed onto the stack.
The time complexity of DFS is O(V + E), where V is the number of vertices and E is the number of edges.
11.Problem Name:Searching for an Element in an Array using Linear Search
Analysis:
Linear Search is a simple searching algorithm that sequentially checks each element of an array to find the desired element. It works for both sorted and unsorted arrays. However, it's not very efficient for
large datasets, as it has a time complexity of O(n), where n is the number of elements in the array.
Algorithm:
procedure linearSearch(arr, target)
for each element in arr
if element equals target
return index of element
return -1 (element not found)
end procedure
Source Code (C++):
#include<bits/stdc++.h>
using namespace std;
#include <iostream>
#include <vector>
int linearSearch(const std::vector<int>& arr, int target)
{
for (int i = 0; i < arr.size(); ++i)
{
if (arr[i] == target)
{
return i;
}
}
return -1; // Element not found
}
int main()
{
int n;
cout<<"Enter size of array :";
cin>>n;
vector<int>arr(n+5,0);
cout << "Original array: ";
for (int i = 0; i <n; ++i)
{
cin>>arr[i];
}
int target;
cout<<" enter target element:";
cin>>target;
int index = linearSearch(arr, target);
if (index != -1)
{
std::cout << "Element " << target << " found at index " << index+1 << std::endl;
}
else
{
std::cout << "Element " << target << " not found in the array" << std::endl;
}
return 0;
}
Sample Input/Output:
Enter size of array :9
Original array: 12 45 67 89 34 56 23 90 11
enter target element:56
Element 56 found at index 6
Remarks:
Linear Search is a basic searching algorithm that's easy to understand and implement. It's suitable for small arrays or situations where the data is not sorted. However, for large datasets, more efficient
algorithms like Binary Search (for sorted arrays) or Hashing are preferred, as they have better average and worst-case time complexities.
12.Problem Name:Implement Matrix Multiplication
Analysis:
Matrix multiplication is the process of multiplying two matrices to obtain a new matrix. It involves taking the dot product of rows and columns of the matrices to compute the elements of the resulting matrix.
Algorithm:
MATRIX MULTIPLICATION(A, B)
Step 1: Initialize result matrix C with dimensions rows_A x cols_B
Step 2: For each row i in A
a. For each column j in B
i. Initialize sum = 0
ii. For k = 0 to cols_A - 1
A. sum += A[i][k] * B[k][j]
iii. Set C[i][j] = sum
[END OF LOOP]
Step 3: Return matrix C
Source Code:
#include<bits/stdc++.h>
#include <iostream>
#include <vector>
using namespace std;
vector<vector<int>> matrixMultiplication(const vector<vector<int>>& A, const vector<vector<int>>& B)
{
int rows_A = A.size();
int cols_A = A[0].size();
int cols_B = B[0].size();
vector<vector<int>> C(rows_A, vector<int>(cols_B, 0));
for (int i = 0; i < rows_A; i++)
{
for (int j = 0; j < cols_B; j++)
{
int sum = 0;
for (int k = 0; k < cols_A; k++)
{
sum += A[i][k] * B[k][j];
}
C[i][j] = sum;
}
}
return C;
}
int main()
{
int rows_A, cols_A, rows_B, cols_B;
cout << "Enter the dimensions of matrix A (rows cols): ";
cin >> rows_A >> cols_A;
cout << "Enter the dimensions of matrix B (rows cols): ";
cin >> rows_B >> cols_B;
if (cols_A != rows_B)
{
cout << "Matrix multiplication is not possible due to incompatible dimensions." << endl;
return 1;
}
vector<vector<int>> A(rows_A, vector<int>(cols_A));
vector<vector<int>> B(rows_B, vector<int>(cols_B));
cout << "Enter the elements of matrix A:" << endl;
for (int i = 0; i < rows_A; i++)
for (int j = 0; j < cols_A; j++)
cin >> A[i][j];
cout << "Enter the elements of matrix B:" << endl;
for (int i = 0; i < rows_B; i++)
for (int j = 0; j < cols_B; j++)
cin >> B[i][j];
vector<vector<int>> result = matrixMultiplication(A, B);
cout << "Resultant matrix C:" << endl;
for (int i = 0; i < rows_A; i++)
{
for (int j = 0; j < cols_B; j++)
{
cout << result[i][j] << " ";
}
cout << endl;
}
return 0;
}
Sample Input:
32
24
46
68
23
135
246
Sample Output:
Resultant matrix C:
10 20 30
20 40 60
30 60 90
Remarks:Matrix multiplication is a fundamental operation in linear algebra. It's important to ensure that the number of columns in the first matrix matches the number of rows in the second matrix for the
operation to be valid.
13.Problem Name:Analysis Binary Search with an algorithm and sample code.
Analysis:
Binary Search is defined as a searching algorithm used in a sorted array by repeatedly dividing the search interval in half. The idea of binary search is to use the information that the array is sorted and reduce
the time complexity to O(log N).
In this algorithm,
Divide the search space into two halves by finding the middle index “mid”.
Compare the middle element of the search space with the key.
If the key is found at middle element, the process is terminated.
If the key is not found at middle element, choose which half will be used as the next search space.
If the key is smaller than the middle element, then the left side is used for next search.
If the key is larger than the middle element, then the right side is used for next search.
This process is continued until the key is found or the total search space is exhausted.
Algorithm:
BINARY SEARCH[A,N,KEY]
Step 1: begin
Step 2: [Initilization]
Lb=1; ub=n;
Step 3: [Search for the ITEM]
Repeat through step 4,while Lower bound is less than Upper Bound.
Step 4: [Obtain the index of middle value]
MID=(lb+ub)/2
Step 5: [Compare to search for ITEM]
If Key<A[MID] then
Ub=MID-1
Other wise if Key >A[MID] then
Lb=MID+1
Otherwise write “Match Found”
Return Middle.
Step 6: [Unsuccessful Search]
write “Match Not Found”
Step 7: Stop.
Source Code:
#include <iostream>
using namespace std;
int binarySearch(int arr[], int target, int low, int high) {
while (low <= high) {
int mid = (low + high) / 2;
if (arr[mid] == target)
return mid;
else if (arr[mid] < target)
low = mid + 1;
else
high = mid - 1; }
return -1; // Target not found}
int main() {
int n;
cout << "Enter the number of elements: ";
cin >> n;
int arr[n];
cout << "Enter the sorted elements:" << endl;
for (int i = 0; i < n; i++)
cin >> arr[i];
int target;
cout << "Enter the target element: ";
cin >> target;
int result = binarySearch(arr, target, 0, n - 1);
if (result != -1)
cout << "Target element found at index " << result << endl;
else
cout << "Target element not found in the array." << endl;
return 0;}
Input & Output :
Enter the number of elements: 5
Enter elements: 1 10 15 20 45
Target : 10
Element found at 1 index