DAA Lab Maual 1.2
DAA Lab Maual 1.2
DAA Lab Maual 1.2
The objective of this lab is to teach students various algorithm design techniques and to explain
them the importance of design techniques in context of applying those techniques in similar
problems. Students will gain practical knowledge by writing and executing programs in C and
C++ using various design techniques like Divide and Conquer, Greedy, Dynamic Programming,
Backtracking, Branch and Bound and Randomization.
OUTCOMES:
Upon the completion of Design and Analysis of Algorithms practical course, the student will be
able to:
1. Design and analyze the time and space efficiency of the various algorithm design
techniques.
2. Identity the appropriate algorithm design technique for given problem.
3. Understand the applications of algorithm design techniques.
4. Choose the appropriate algorithm design method for a specified application.
5. Understand which algorithm design technique to use in different scenarios.
6. Understand and apply fundamental algorithmic problems including Tree traversals,
Graph traversals.
7. Compare different implementations of algorithm design technique and to recognize the
advantages and disadvantages of them.
8. Compare between different algorithm design techniques. Pick an appropriate one for a
design situation.
18CSC204J – DAA 1
COURSE LEARNING RATIONALE
The purpose of learning this course is to:
CLR-2: Analyze various algorithm design techniques to solve real time problems in polynomial time
CLR-4: Utilize back tracking and branch and bound paradigms to solve exponential time problems
CLR-5: Analyze the need of approximation and randomization algorithms, utilize the importance Non
polynomial algorithms
CLR-6: Construct algorithms that are efficient in space and time complexities
CLO-1: Apply efficient algorithms to reduce space and time complexity of both recurrent and non-
recurrent relations
CLO-3 : Apply greedy and dynamic programming types techniques to solve polynomial time problems
CLO-4 : Create exponential problems using backtracking and branch and bound approaches
CLO-5 : Interpret various approximation algorithms and interpret solutions to evaluate P type, NP Type,
NPC, NP Hard problems
CLO-6 : Create algorithms that are efficient in space and time complexities by using divide conquer,
greedy, backtracking technique
18CSC204J – DAA 2
SRM INSTITUTE OF SCIENCE AND
TECHNOLOGY
SCHOOL OF COMPUTING
design strategy that you have learnt. Compare and contrast with its
18CSC204J – DAA 3
WEEK 1 LINEAR SEARCH
Searching:
Searching is the algorithmic process of finding a particular item in a collection of items. If the
value is present in the array, then the searching is said to be successful and the searching process
gives the location of that value in the array. However, if the value is not present in the array, the
searching process displays an appropriate message and in this case searching is said to be
unsuccessful.
There are two popular methods for searching an array elements: linear search and binary search.
Linear Search:
Linear search, also called as sequential search, is a very simple method used for searching an
array for a particular value. It works by comparing the value to be searched with every element
of the array one by one in a sequence until a match is found. Linear search is mostly used to
search an unordered list of elements (array in which data elements are not sorted).
Pseudocode:
LINEAR_SEARCH(A, N, VAL)
Step 1: [INITIALIZE] SET POS = -1
Step 2: [INITIALIZE] SET I = 1
Step 3: Repeat Step 4 while I<=N Step 4:
IF A[I] = VAL
SET POS = I PRINT POS
Go to Step 6
[END OF IF]
SET I = I + 1
[END OF LOOP]
Step 5: IF POS = –1
PRINT "VALUE IS NOT PRESENT IN THE ARRAY"
[END OF IF]
Step 6: EXIT
18CSC204J – DAA 4
Sorting:
Sorting means arranging the elements of an array so that they are placed in some relevant
order which may be either ascending or descending. That is, if A is an array, then the
elements of A are arranged in a sorted order (ascending order) in such a way that A [0] < A
[1] < A [2] ……. < A [ N].
For example, if we have an array that is declared and initialized as
int A[] = {21, 34, 11, 9, 1, 0, 22};
Then the sorted array (ascending order) can be given as:
A[] = {0, 1, 9, 11, 21, 22, 34;
2.a.Bubble sort
Bubble sort is a very simple method that sorts the array elements by repeatedly moving the
largest element to the highest index position of the array segment (in case of arranging
elements in ascending order). In bubble sorting, consecutive adjacent pairs of elements in
the array are compared with each other. If the element at the lower index is greater than the
element at the higher index, the two elements are interchanged so that the element is placed
before the bigger one. This process will continue till the list of unsorted elements exhausts.
Pseudocode:
BUBBLE_SORT(A, N)
Pseudocode:
INSERTION-SORT (ARR, N)
18CSC204J – DAA 5
Step 3: SET J = K – 1
Step 4: Repeat while TEMP <= ARR[J]
SET ARR[J + 1] = ARR[J]
SET J = J – 1
[END OF INNER LOOP]
Step 5: SET ARR[J + 1] = TEMP
[END OF LOOP]
Step 6: EXIT
18CSC204J – DAA 6
Merge Sort is a Divide and Conquer algorithm. It divides the input array into two halves, calls
itself for the two halves, and then it merges the two sorted halves. The merge () function is
used for merging two halves. The merge(arr, l, m, r) is a key process that assumes that arr[l..m]
and arr[m+1..r] are sorted and merges the two sorted sub-arrays into one.
Pseudocode :
Step 1: Start
Step 2: Declare an array and left, right, mid variable
Step 3: Perform merge function.
mergesort(array,left,right)
mergesort (array, left, right)
if left > right
return
mid= (left+right)/2
mergesort(array, left, mid)
mergesort(array, mid+1, right)
merge(array, left, mid, right)
Step 4: Stop
Program:
18CSC204J – DAA 8
// Copy the remaining elements of
// R[], if there are any
while (j < n2)
{
arr[k] = R[j];
j++;
k++;
}
}
merge(arr, l, m, r);
}
}
// UTILITY FUNCTIONS
// Function to print an array
void printArray(int A[], int size)
{
int i;
for (i = 0; i < size; i++)
printf("%d ", A[i]);
printf("\n");
}
// Driver code
int main()
{
int arr[] = {12, 11, 13, 5, 6, 7};
int arr_size = sizeof(arr) / sizeof(arr[0]);
18CSC204J – DAA 9
printf("Given array is \n");
printArray(arr, arr_size);
18CSC204J – DAA 10
4.a. Aim:
Implement a divide and conquer algorithm to perform Quicksort in an array
Procedure:
QuickSort is a Divide and Conquer algorithm. It picks an element as a pivot and partitions the
given array around the picked pivot. There are many different versions of quickSort that pick
pivot in different ways.
Always pick the first element as a pivot.
Always pick the last element as a pivot (implemented below)
Pick a random element as a pivot.
Pick median as the pivot.
The key process in quickSort is a partition(). The target of partitions is, given an array and an
element x of an array as the pivot, put x at its correct position in a sorted array and put all
smaller elements (smaller than x) before x, and put all greater elements (greater than x) after x.
All this should be done in linear time.
Example:
Algorithm:
18CSC204J – DAA 11
pi = partition(arr, low, high);
quickSort(arr, low, pi – 1); // Before pi
quickSort(arr, pi + 1, high); // After pi
}
}
/* This function takes last element as pivot, places the pivot element at its correct position in
sorted array, and places all smaller (smaller than pivot) to left of pivot and all greater elements
to right of pivot */
partition (arr[], low, high)
{
// pivot (Element to be placed at right position)
pivot = arr[high];
i = (low – 1) // Index of smaller element and indicates the
// right position of pivot found so far
for (j = low; j <= high- 1; j++){
// If current element is smaller than the pivot
if (arr[j] < pivot){
i++; // increment index of smaller element
swap arr[i] and arr[j]
}
}
swap arr[i + 1] and arr[high])
return (i + 1)
}
Code:
18CSC204J – DAA 12
/* This function takes last element as pivot, places the pivot element at its correct position
in sorted array, and places all smaller (smaller than pivot) to left of pivot and all greater
elements to right of pivot */
int partition(int arr[], int low, int high)
{
int pivot = arr[high]; // pivot
int i = (low- 1);
// Index of smaller element and indicate the right position of pivot found so far
18CSC204J – DAA 13
void printArray(int arr[], int size)
{
int i;
for (i = 0; i < size; i++)
cout << arr[i] << " ";
cout << endl;
}
// Driver Code
int main()
{
int arr[] = { 10, 7, 8, 9, 1, 5 };
int n = sizeof(arr) / sizeof(arr[0]);
quickSort(arr, 0, n - 1);
cout << "Sorted array: \n";
printArray(arr, n);
return 0;
}
Sorted array:
1 5 7 8 9 10
Algorithm Analysis:
The worst case occurs when the partition process always picks the greatest or smallest element
as the pivot. If we consider the above partition strategy where the last element is always picked
as a pivot, the worst case would occur when the array is already sorted in increasing or
decreasing order. Following is recurrence for the worst case.
T(n) = T(0) + T(n-1) + (n) which is equivalent to T(n) = T(n-1) + (n)
The solution to the above recurrence is O(n2).
Best Case:
18CSC204J – DAA 14
The best case occurs when the partition process always picks the middle element as the pivot.
The following is recurrence for the best case.
T(n) = 2T(n/2) + (n)
The solution for the above recurrence is O(nLogn).
4.b.Aim:
Implement a divide and conquer algorithm to perform Binary search in an
array
Procedure:
Algorithm:
2. Recursive Method (The recursive method follows the divide and conquer approach)
binarySearch(arr, x, low, high)
if low > high
return False
18CSC204J – DAA 15
else
mid = (low + high) / 2
if x == arr[mid]
return mid
Code:
#include <bits/stdc++.h>
// otherwise -1
if (r >= l) {
int mid = l + (r - l) / 2;
// itself
if (arr[mid] == x)
return mid;
18CSC204J – DAA 16
// If element is smaller than mid, then
if (arr[mid] > x)
return -1;
int main(void)
int x = 10;
(result == -1)
return 0;
Input: arr[] = {10, 20, 30, 50, 60, 80, 110, 130, 140, 170}, x = 110
Output: 6
Explanation: Element x is present at index 6.
Input: arr[] = {10, 20, 30, 40, 60, 110, 120, 130, 170}, x = 175
Output: -1
18CSC204J – DAA 17
Explanation: Element x is not present in arr[].
Algorithm Analysis:
18CSC204J – DAA 18
5.a.Aim:
To implement a divide and conquer algorithm to perform strassen matrix
multiplication.
Procedure:
Given two square matrices A and B of size n x n each, find their multiplication matrix.
Following is simple Divide and Conquer method to multiply two square matrices.
1. Divide matrices A and B in 4 sub-matrices of size N/2 x N/2 as shown in the below diagram.
2. Calculate following values recursively. ae + bg, af + bh, ce + dg and cf + dh.
Code:
#include <bits/stdc++.h>
using namespace std;
#define ROW_1 4
#define COL_1 4
#define ROW_2 4
#define COL_2 4
18CSC204J – DAA 19
cout << endl;
return;
}
vector<vector<int> >
multiply_matrix(vector<vector<int> > matrix_A,
vector<vector<int> > matrix_B)
{
int col_1 = matrix_A[0].size();
int row_1 = matrix_A.size();
int col_2 = matrix_B[0].size();
int row_2 = matrix_B.size();
if (col_1 != row_2) {
cout << "\nError: The number of columns in Matrix "
"A must be equal to the number of rows in "
"Matrix B\n";
return {};
}
if (col_1 == 1)
result_matrix[0][0]
= matrix_A[0][0] * matrix_B[0][0];
else {
int split_index = col_1 / 2;
row_vector);
vector<vector<int> > result_matrix_01(split_index,
18CSC204J – DAA 20
row_vector);
vector<vector<int> > result_matrix_10(split_index,
row_vector);
vector<vector<int> > result_matrix_11(split_index,
row_vector);
add_matrix(multiply_matrix(a00, b00),
multiply_matrix(a01, b10),
result_matrix_00, split_index);
add_matrix(multiply_matrix(a00, b01),
multiply_matrix(a01, b11),
result_matrix_01, split_index);
add_matrix(multiply_matrix(a10, b00),
multiply_matrix(a11, b10),
result_matrix_10, split_index);
add_matrix(multiply_matrix(a10, b01),
multiply_matrix(a11, b11),
result_matrix_11, split_index);
18CSC204J – DAA 21
for (auto j = 0; j < split_index; j++) {
result_matrix[i][j]
= result_matrix_00[i][j];
result_matrix[i][j + split_index]
= result_matrix_01[i][j];
result_matrix[split_index + i][j]
= result_matrix_10[i][j];
result_matrix[i + split_index]
[j + split_index]
= result_matrix_11[i][j];
}
result_matrix_00.clear();
result_matrix_01.clear();
result_matrix_10.clear();
result_matrix_11.clear();
a00.clear();
a01.clear();
a10.clear();
a11.clear();
b00.clear();
b01.clear();
b10.clear();
b11.clear();
}
return result_matrix;
}
int main()
{
vector<vector<int> > matrix_A = { { 1, 1, 1, 1 },
{ 2, 2, 2, 2 },
{ 3, 3, 3, 3 },
{ 2, 2, 2, 2 } };
18CSC204J – DAA 22
print("Result Array", result_matrix, 0, 0, ROW_1 - 1,
COL_2 - 1);
}
Array B =>
1 1 1 1
2 2 2 2
3 3 3 3
2 2 2 2
18CSC204J – DAA 23
WEEK 6 FINDING MAXIMUM AND MINIMUM IN AN ARRAY,
CONVEX HULL PROBLEM
6.a. Aim:
Implement a divide and conquer algorithm to find a maximum and minimum
element in an array
Procedure:
Given an array of size N. The task is to find the maximum and the minimum element of
the array using the minimum number of comparisons.
Algorithm:
Pair MaxMin(array, array_size)
if array_size = 1
return element as both max and min
else if arry_size = 2
one comparison to determine max and min
return that pair
else /* array_size > 2 */
recur for max and min of left half
recur for max and min of right half
one comparison determines true max of the two candidates
one comparison determines true min of the two candidates
return the pair of max and min
Code:
return minmax;
}
// Driver code
int main()
{
int arr[] = { 1000, 11, 445, 1, 330, 3000 };
int arr_size = 6;
18CSC204J – DAA 25
cout << "Maximum element is " << minmax.max;
return 0;
}
Sample input and output:
Input: arr[] = {3, 5, 4, 1, 9}
Output: Minimum element is: 1
Maximum element is: 9
Input: arr[] = {22, 14, 8, 17, 35, 3}
Output: Minimum element is: 3
Maximum element is: 35
Algorithm analysis:
T(n) = T(floor(n/2)) + T(ceil(n/2)) + 2
T(2) = 1
T(1) = 0
If n is a power of 2, then we can write T(n) as:
T(n) = 2T(n/2) + 2
After solving the above recursion, we get
T(n) = 3n/2 -2
Thus, the approach does 3n/2 -2 comparisons if n is a power of 2. And it does more than 3n/2 -2
comparisons if n is not a power of 2.
18CSC204J – DAA 26
6.b. Aim:
To implement a divide and conquer algorithm to construct a convex hull.
Procedure:
A convex hull is the smallest convex polygon containing all the given points.
Input is an array of points specified by their x and y coordinates. The output is the convex hull of
this set of points.
Algorithm:
The QuickHull algorithm is a Divide and Conquer algorithm similar to QuickSort. Let a[0…n-1]
be the input array of points. Following are the steps for finding the convex hull of these points.
1. Find the point with minimum x-coordinate lets say, min_x and similarly the point with
maximum x-coordinate, max_x.
2. Make a line joining these two points, say L. This line will divide the whole set into two parts.
Take both the parts one by one and proceed further.
3. For a part, find the point P with maximum distance from the line L. P forms a triangle with
the points min_x, max_x. It is clear that the points residing inside this triangle can never be
the part of convex hull.
4. The above step divides the problem into two sub-problems (solved recursively). Now the line
joining the points P and min_x and the line joining the points P and max_x are new lines and
the points residing outside the triangle is the set of points. Repeat point no. 3 till there no
point left with the line. Add the end points of this point to the convex hull.
Code:
// C++ program to implement Quick Hull algorithm
// to find convex hull.
#include<bits/stdc++.h>
using namespace std;
18CSC204J – DAA 27
// joining points p1 and p2.
int findSide(iPair p1, iPair p2, iPair p)
{
int val = (p.second - p1.second) * (p2.first - p1.first) -
(p2.second - p1.second) * (p.first - p1.first);
if (val > 0)
return 1;
if (val < 0)
return -1;
return 0;
}
// End points of line L are p1 and p2. side can have value
// 1 or -1 specifying each of the parts made by the line L
void quickHull(iPair a[], int n, iPair p1, iPair p2, int side)
{
int ind = -1;
int max_dist = 0;
18CSC204J – DAA 28
return;
}
// Recursively find convex hull points on one side of line joining a[min_x] and a[max_x]
quickHull(a, n, a[min_x], a[max_x], 1);
// Recursively find convex hull points on other side of line joining a[min_x] and
a[max_x]
quickHull(a, n, a[min_x], a[max_x], -1);
// Driver code
int main()
{
18CSC204J – DAA 29
iPair a[] = {{0, 3}, {1, 1}, {2, 2}, {4, 4},
{0, 0}, {1, 2}, {3, 1}, {3, 3}};
int n = sizeof(a)/sizeof(a[0]);
printHull(a, n);
return 0;
}
Sample input and output:
Input : points[] = {(0, 0), (0, 4), (-4, 0), (5, 0),
(0, -6), (1, 0)};
Output : (-4, 0), (5, 0), (0, -6), (0, 4)
Input : points[] = {{0, 3}, {1, 1}, {2, 2}, {4, 4},
{0, 0}, {1, 2}, {3, 1}, {3, 3}};
Output : The points in convex hull are:
(0, 0) (0, 3) (3, 1) (4, 4)
18CSC204J – DAA 30
WEEK 7 HUFFMAN CODING, KNAPSACK USING GREEDY
7.a. Aim :
To implement a greedy algorithm to perform encoding mechanism using huffman code
method
Procedure:
Huffman Coding-
Huffman Coding is a famous Greedy Algorithm.
It is used for the lossless compression of data.
It uses variable length encoding.
It assigns variable length code to all the characters.
The code length of a character depends on how frequently it occurs in the given text.
The character which occurs most frequently gets the smallest code.
The character which occurs least frequently gets the largest code.
It is also known as Huffman Encoding.
Prefix Rule-
Huffman Coding implements a rule known as a prefix rule.
This is to prevent the ambiguities while decoding.
It ensures that the code assigned to any character is not a prefix of the code assigned to any
other character.
Major Steps in Huffman Coding-
There are two major steps in Huffman Coding-
1. Building a Huffman Tree from the input characters.
2. Assigning code to the characters by traversing the Huffman Tree.
Huffman Tree-
The steps involved in the construction of Huffman Tree are as follows-
Step-01:
Create a leaf node for each character of the text.
Leaf node of a character contains the occurring frequency of that character.
Step-02:
Arrange all the nodes in increasing order of their frequency value.
Step-03:
18CSC204J – DAA 31
Considering the first two nodes having minimum frequency,
Create a new internal node.
The frequency of this new node is the sum of frequency of those two nodes.
Make the first node as a left child and the other node as a right child of the newly
created node.
Step-04:
Keep repeating Step-02 and Step-03 until all the nodes form a single tree.
The tree finally obtained is the desired Huffman Tree.
Time Complexity-
Solution-
First let us construct the Huffman Tree.
18CSC204J – DAA 32
Huffman Tree is constructed in the following steps-
Step-01:
Step-02:
Step-03:
Step-04:
18CSC204J – DAA 33
Step-05:
18CSC204J – DAA 34
Step-06:
18CSC204J – DAA 35
Step-07:
18CSC204J – DAA 36
18CSC204J – DAA 37
Now,
We assign weight to all the edges of the constructed Huffman Tree.
Let us assign weight ‘0’ to the left edges and weight ‘1’ to the right edges.
Rule
If you assign weight ‘0’ to the left edges, then assign weight ‘1’ to the right edges.
If you assign weight ‘1’ to the left edges, then assign weight ‘0’ to the right edges.
Any of the above two conventions may be followed.
But follow the same convention at the time of decoding that is adopted at the time of encoding.
After assigning weight to all the edges, the modified Huffman Tree is-
18CSC204J – DAA 38
Now, let us answer each part of the given problem one by one-
To write Huffman Code for any character, traverse the Huffman Tree from root node to the
leaf node of that character.
Following this rule, the Huffman Code for each character is-
a = 111
e = 10
i = 00
o = 11001
u = 1101
s = 01
t = 11000
#include <stdio.h>
#include <stdlib.h>
#define MAX_TREE_HT 50
struct MinHNode {
char item;
unsigned freq;
18CSC204J – DAA 39
struct MinHNode *left, *right;
};
struct MinHeap {
unsigned size;
unsigned capacity;
struct MinHNode **array;
};
// Create nodes
struct MinHNode *newNode(char item, unsigned freq) {
struct MinHNode *temp = (struct MinHNode *)malloc(sizeof(struct MinHNode));
return temp;
}
minHeap->size = 0;
minHeap->capacity = capacity;
// Function to swap
void swapMinHNode(struct MinHNode **a, struct MinHNode **b) {
struct MinHNode *t = *a;
*a = *b;
*b = t;
}
// Heapify
void minHeapify(struct MinHeap *minHeap, int idx) {
int smallest = idx;
int left = 2 * idx + 1;
int right = 2 * idx + 2;
18CSC204J – DAA 40
if (left < minHeap->size && minHeap->array[left]->freq < minHeap->array[smallest]-
>freq)
smallest = left;
if (smallest != idx) {
swapMinHNode(&minHeap->array[smallest], &minHeap->array[idx]);
minHeapify(minHeap, smallest);
}
}
// Check if size if 1
int checkSizeOne(struct MinHeap *minHeap) {
return (minHeap->size == 1);
}
// Extract min
struct MinHNode *extractMin(struct MinHeap *minHeap) {
struct MinHNode *temp = minHeap->array[0];
minHeap->array[0] = minHeap->array[minHeap->size - 1];
--minHeap->size;
minHeapify(minHeap, 0);
return temp;
}
// Insertion function
void insertMinHeap(struct MinHeap *minHeap, struct MinHNode *minHeapNode) {
++minHeap->size;
int i = minHeap->size - 1;
18CSC204J – DAA 41
for (i = (n - 1) / 2; i >= 0; --i)
minHeapify(minHeap, i);
}
minHeap->size = size;
buildMinHeap(minHeap);
return minHeap;
}
while (!checkSizeOne(minHeap)) {
left = extractMin(minHeap);
right = extractMin(minHeap);
top->left = left;
top->right = right;
insertMinHeap(minHeap, top);
}
return extractMin(minHeap);
}
18CSC204J – DAA 42
}
if (isLeaf(root)) {
printf(" %c | ", root->item);
printArray(arr, top);
}
}
// Wrapper function
void HuffmanCodes(char item[], int freq[], int size) {
struct MinHNode *root = buildHuffmanTree(item, freq, size);
printf("\n");
}
int main() {
char arr[] = {'A', 'B', 'C', 'D'};
int freq[] = {5, 1, 6, 3};
18CSC204J – DAA 43
7.b. Aim:
To implement a greedy algorithm to implement knapsack method
Procedure:
3. It derives its name from the problem faced by someone who is constrained by a fixed-
size knapsack and must fill it with the most useful items.
Program:
# include<stdio.h>
18CSC204J – DAA 44
for (i = 0; i < n; i++) {
if (weight[i] > u)
break;
else {
x[i] = 1.0;
tp = tp + profit[i];
u = u - weight[i];
}
}
if (i < n)
x[i] = u / weight[i];
tp = tp + (x[i] * profit[i]);
int main() {
float weight[20], profit[20], capacity;
int num, i, j;
float ratio[20], temp;
temp = weight[j];
weight[j] = weight[i];
weight[i] = temp;
temp = profit[j];
profit[j] = profit[i];
profit[i] = temp;
}
}
}
Sorting of n items (or objects) in decreasing order of the ratio Pj/Wj takes O (n log n)
time. Since this is the lower bound for any comparison-based sorting algorithm.
Therefore, the total time including sort is O(n log n).
18CSC204J – DAA 46
8.a. Aim:
To implement a greedy algorithm to perform Tree Traversals
Procedure:
Traversal is a process to visit all the nodes of a tree and may print their values too. Because, all
nodes are connected via edges (links) we always start from the root (head) node. That is, we
cannot randomly access a node in a tree. There are three ways which we use to traverse a tree −
In-order Traversal
Pre-order Traversal
Post-order Traversal
Generally, we traverse a tree to search or locate a given item or key in the tree or to print all the
values it contains.
In-order Traversal
In this traversal method, the left subtree is visited first, then the root and later the right sub-tree.
We should always remember that every node may represent a subtree itself.
If a binary tree is traversed in-order, the output will produce sorted key values in an ascending
order.
We start from A, and following in-order traversal, we move to its left subtree B. B is also
traversed in-order. The process goes on until all the nodes are visited. The output of inorder
traversal of this tree will be −
D→B→E→A→F→C→G
18CSC204J – DAA 47
Algorithm
Until all nodes are traversed −
Step 1 − Recursively traverse left subtree.
Step 2 − Visit root node.
Step 3 − Recursively traverse right subtree.
Pre-order Traversal
In this traversal method, the root node is visited first, then the left subtree and finally the right
subtree.
We start from A, and following pre-order traversal, we first visit A itself and then move to its left
subtree B. B is also traversed pre-order. The process goes on until all the nodes are visited. The
output of pre-order traversal of this tree will be −
A→B→D→E→C→F→G
Algorithm
Until all nodes are traversed −
Step 1 − Visit root node.
Step 2 − Recursively traverse left subtree.
Step 3 − Recursively traverse right subtree.
Post-order Traversal
In this traversal method, the root node is visited last, hence the name. First we traverse the left
subtree, then the right subtree and finally the root node.
18CSC204J – DAA 48
We start from A, and following Post-order traversal, we first visit the left subtree B. B is also
traversed post-order. The process goes on until all the nodes are visited. The output of post-order
traversal of this tree will be −
D→E→B→F→G→C→A
Algorithm
Until all nodes are traversed −
Step 1 − Recursively traverse left subtree.
Step 2 − Recursively traverse right subtree.
Step 3 − Visit root node.
#include <stdio.h>
#include <stdlib.h>
struct node {
int data;
tempNode->data = data;
tempNode->leftChild = NULL;
tempNode->rightChild = NULL;
18CSC204J – DAA 49
//if tree is empty
if(root == NULL) {
root = tempNode;
} else {
current = root;
parent = NULL;
while(1) {
parent = current;
while(current->data != data) {
if(current != NULL)
printf("%d ",current->data);
18CSC204J – DAA 50
current = current->rightChild;
}
//not found
if(current == NULL) {
return NULL;
}
}
return current;
}
int main() {
int i;
int array[7] = { 27, 14, 35, 10, 19, 31, 42 };
i = 31;
struct node * temp = search(i);
if(temp != NULL) {
printf("[%d] Element found.", temp->data);
18CSC204J – DAA 51
printf("\n");
}else {
printf("[ x ] Element not found (%d).\n", i);
}
i = 15;
temp = search(i);
if(temp != NULL) {
printf("[%d] Element found.", temp->data);
printf("\n");
}else {
printf("[ x ] Element not found (%d).\n", i);
}
return 0;
}
Output
Visiting elements: 27 35 [31] Element found.
Visiting elements: 27 14 19 [ x ] Element not found (15).
Preorder traversal: 27 14 10 19 35 31 42
Inorder traversal: 10 14 19 27 31 35 42
Post order traversal: 10 19 14 31 42 35 27
18CSC204J – DAA 52
8.b. Aim:
Implement a greedy algorithm to find minimum spanning tree using kruskal’s
algorithm.
Procedure:
Kruskal algorithm is used to generate a minimum spanning tree for a given graph. But, what
exactly is a minimum spanning tree? A minimum spanning tree is a subset of a graph with the
same number of vertices as the graph and edges equal to the number of vertices -1. It also has a
minimal cost for the sum of all edge weights in a spanning tree.
Kruskal’s algorithm sorts all the edges in increasing order of their edge weights and keeps
adding nodes to the tree only if the chosen edge does not form any cycle. Also, it picks the edge
with a minimum cost at first and the edge with a maximum cost at last. Hence, you can say that
the Kruskal algorithm makes a locally optimal choice, intending to find the global optimal
solution. That is why it is called a Greedy Algorithm.
Step 3: Check if the new edge creates a cycle or loop in a spanning tree.
Step 4: If it doesn’t form the cycle, then include that edge in MST. Otherwise, discard it.
Using the steps mentioned above, you will generate a minimum spanning tree structure. So,
now have a look at an example to understand this process better.
The graph G(V, E) given below contains 6 vertices and 12 edges. And you will create a
minimum spanning tree T(V’, E’) for G(V, E) such that the number of vertices in T will be 6
and edges will be 5 (6-1).
18CSC204J – DAA 53
If you observe this graph, you’ll find two looping edges connecting the same node to itself again.
And you know that the tree structure can never include a loop or parallel edge. Hence, primarily
you will need to remove these edges from the graph structure.
The next step that you will proceed with is arranging all edges in a sorted list by their edge
weights.
18CSC204J – DAA 54
Source Vertex Destination Vertex
E F 2
F D 2
B C 3
C F 3
C D 4
B F 5
B D 6
A B 7
A C 8
18CSC204J – DAA 55
After this step, you will include edges in the MST such that the included edge would not form a
cycle in your tree structure. The first edge that you will pick is edge EF, as it has a minimum
edge weight that is 2.
Add edge BC and edge CF to the spanning tree as it does not generate any loop.
Next up is edge CD. This edge generates the loop in Your tree structure. Thus, you will discard
this edge.
18CSC204J – DAA 56
Following edge CD, you have edge BF. This edge also creates the loop; hence you will discard
it.
Next up is edge BD. This edge also formulates a loop, so you will discard it as well.
18CSC204J – DAA 57
Next on your sorted list is edge AB. This edge does not generate any cycle, so you need not
include it in the MST structure. By including this node, it will include 5 edges in the MST, so
you don’t have to traverse any further in the sorted list. The final structure of your MST is
represented in the image below:
The summation of all the edge weights in MST T(V’, E’) is equal to 17, which is the least
possible edge weight for any possible spanning tree structure for this particular graph.
18CSC204J – DAA 58
The C program to implement Kruskal’s algorithm using above mentioned strategy is as follows:
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
//structure that denotes a weighted edge
struct Edge {
int source, destination, weight;
};
//structure that denotes a weighted, undirected and connected graph
struct Graph {
int Node, E;
struct Edge* edge;
};
//allocates memory for storing graph with V vertices and E edges
struct Graph* GenerateGraph(int Node, int E)
{
struct Graph* graph = (struct Graph*)(malloc(sizeof(struct Graph)));
graph->Node = Node;
graph->E = E;
graph->edge = (struct Edge*)malloc(sizeof( struct Edge));
return graph;
}
//subset for Union-Find
struct tree_maintainance_set {
int parent;
int rank;
};
//finds the set of chosen element i using path compression
int find_DisjointSet(struct tree_maintainance_set subsets[], int i)
{
//find root and make root as parent of i
if (subsets[i].parent != i)
subsets[i].parent
= find_DisjointSet(subsets, subsets[i].parent);
18CSC204J – DAA 59
return subsets[i].parent;
}
//Creates the Union of two sets
void Union_DisjointSet(struct tree_maintainance_set subsets[], int x, int y)
{
int xroot = find_DisjointSet(subsets, x);
int yroot = find_DisjointSet(subsets, y);
//connecting tree with lowest rank to the tree with highest rank
if (subsets[xroot].rank < subsets[yroot].rank)
subsets[xroot].parent = yroot;
else if (subsets[xroot].rank > subsets[yroot].rank)
subsets[yroot].parent = xroot;
//if ranks are same, arbitrarily increase the rank of one node
else
{
subsets[yroot].parent = xroot;
subsets[xroot].rank++;
}
}
//function to compare edges using qsort() in C programming
int myComp(const void* a, const void* b)
{
struct Edge* a1 = (struct Edge*)a;
struct Edge* b1 = (struct Edge*)b;
return a1->weight > b1->weight;
}
//function to construct MST using Kruskal’s approach
void KruskalMST(struct Graph* graph)
{
int Node = graph->Node;
struct Edge
result[Node];
int e = 0;
int i = 0;
//sorting all edges
qsort(graph->edge, graph->E, sizeof(graph->edge[0]),
18CSC204J – DAA 60
myComp);
//memory allocation for V subsets
struct tree_maintainance_set* subsets
= (struct tree_maintainance_set*)malloc(Node * sizeof(struct tree_maintainance_set));
//V subsets containing only one element
for (int v = 0; v < Node; ++v) {
subsets[v].parent = v;
subsets[v].rank = 0;
}
//Edge traversal limit: V-1
while (e < Node - 1 && i < graph->E) {
struct Edge next_edge = graph->edge[i++];
int x = find_DisjointSet(subsets, next_edge.source);
int y = find_DisjointSet(subsets, next_edge.destination);
if (x != y) {
result[e++] = next_edge;
Union_DisjointSet(subsets, x, y);
}
}
//printing MST
printf(
"Edges created in MST are as below: \n");
int minimumCost = 0;
for (i = 0; i < e; ++i)
{
printf("%d -- %d == %d\n", result[i].source,
result[i].destination, result[i].weight);
minimumCost += result[i].weight;
}
printf("The Cost for created MST is : %d",minimumCost);
return;
}
int main()
{
int Node = 4;
int E = 6;
18CSC204J – DAA 61
struct Graph* graph = GenerateGraph(Node, E);
//Creating graph with manual value insertion
// add edge 0-1
graph->edge[0].source = 0;
graph->edge[0].destination = 1;
graph->edge[0].weight = 2;
}
// add edge 0-2
graph->edge[1].source = 0;
graph->edge[1].destination = 2;
graph->edge[1].weight = 4;
// add edge 0-3
graph->edge[2].source = 0;
graph->edge[2].destination = 3;
graph->edge[2].weight = 4;
// add edge 1-3
graph->edge[3].source = 1;
graph->edge[3].destination = 3;
graph->edge[3].weight = 3;
// add edge 2-3
graph->edge[4].source = 2;
graph->edge[4].destination = 3;
graph->edge[4].weight = 1;
// add edge 1-2
graph->edge[5].source = 1;
graph->edge[5].destination = 2;
graph->edge[5].weight = 2;
KruskalMST(graph);
return 0;
}
Output:
18CSC204J – DAA 62
You can verify this output’s accuracy by comparing it with the MST structure shown above. The
overall cost for this MST is 5.
The time complexity of this algorithm is O(E log E) or O(E log V), where E is a number of edges
and V is a number of vertices.
18CSC204J – DAA 63
WEEK 9 LONGEST COMMON SUBSEQUENCE
9.Aim:
To implement a dynamic algorithm to find longest common subsequence
Procedure:
The longest common subsequence (LCS) is defined as the longest subsequence that is
common to all the given sequences, provided that the elements of the subsequence are
not required to occupy consecutive positions within the original sequences.
If S1 and S2 are the two given sequences then, Z is the common subsequence
of S1 and S2 if Z is a subsequence of both S1 and S2. Furthermore, Z must be a strictly
increasing sequence of the indices of both S1 and S2.
In a strictly increasing sequence, the indices of the elements chosen from the original
sequences must be in ascending order in Z.
If
S1 = {B, C, D, A, A, C, D}
Then, {A, D, B} cannot be a subsequence of S1 as the order of the elements is not the
same (ie. not strictly increasing sequence).
If
18CSC204J – DAA 64
S1 = {B, C, D, A, A, C, D}
S2 = {A, C, D, B, A, C}
Then, common subsequences are {B, C}, {C, D, A, C}, {D, A, C}, {A, A, C}, {A, C}, {C, D}, ...
Among these subsequences, {C, D, A, C} is the longest common subsequence. We are
going to find this longest common subsequence using dynamic programming.
Using Dynamic Programming to find the LCS
Let us take two sequences:
Second Sequence
The following steps are followed for finding the longest common subsequence.
Initialise a table
2. Fill each cell of the table using the following logic.
18CSC204J – DAA 65
3. If the character correspoding to the current row and current column are
matching, then fill the current cell by adding one to the diagonal element. Point
an arrow to the diagonal cell.
4. Else take the maximum value from the previous column and previous row
element for filling the current cell. Point an arrow to the cell with maximum
value. If they are equal, point to any of them.
18CSC204J – DAA 66
common subsequence. The bottom right
corner is the length of the LCS
7. In order to find the longest common subsequence, start from the last element
and follow the direction of the arrow. The elements corresponding to () symbol
form the longest common subsequence.
18CSC204J – DAA 67
LCS
In the above dynamic algorithm, the results obtained from each comparison between
elements of X and the elements of Y are stored in a table so that they can be used in
future computations.
So, the time taken by a dynamic approach is the time taken to fill the table (ie.
O(mn)). Whereas, the recursion algorithm has the complexity of 2 max(m, n) .
X.label = X
Y.label = Y
LCS[0][] = 0
LCS[][0] = 0
18CSC204J – DAA 68
Start from LCS[1][1]
If X[i] = Y[j]
Else
#include <stdio.h>
#include <string.h>
int i, j, m, n, LCS_table[20][20];
char S1[20] = "ACADB", S2[20] = "CBDA", b[20][20];
void lcsAlgo() {
m = strlen(S1);
n = strlen(S2);
18CSC204J – DAA 69
} else {
LCS_table[i][j] = LCS_table[i][j - 1];
}
}
int i = m, j = n;
while (i > 0 && j > 0) {
if (S1[i - 1] == S2[j - 1]) {
lcsAlgo[index - 1] = S1[i - 1];
i--;
j--;
index--;
}
int main() {
lcsAlgo();
printf("\n");
}
Time Complexity
Worst case time complexity: O(n*m)
Average case time complexity: O(n*m)
Best case time complexity: O(n*m)
Space complexity: O(n*m)
18CSC204J – DAA 70
Since we are using two for loops for both the strings ,therefore the time complexity of
finding the longest common subsequence using dynamic programming approach
is O(n * m) where n and m are the lengths of the strings.
18CSC204J – DAA 71
WEEK 10 N QUEEN’s PROBLEM
N-Queen
Place (k, i)
{
For j ← 1 to k - 1
do if (x [j] = i)
or (Abs x [j]) - i) = (Abs (j - k))
then return false;
return true;
}
N - Queens (k, n)
{
For i ← 1 to n
do if Place (k, i) then
{
x [k] ← i;
if (k ==n) then
write (x [1....n));
else
N - Queens (k + 1, n);
}
}
It is used in VLSI testing, traffic control, parallel memory storage schemes, and
deadlock prevention .
18CSC204J – DAA 72
WEEK 11 TRAVELLING SALESMAN PROBLEM
Travelling Salesman Problem is based on a real-life scenario, where a salesman from a company
must start from his own city and visit all the assigned cities exactly once and return to his home
till the end of the day. The exact problem statement goes like this,
"Given a set of cities and distance between every pair of cities, the problem is to find the shortest
possible route that visits every city exactly once and returns to the starting point.
Pseudo Code:
reduce_row():
/// row[i] is the minimum value for the ith row
for (int i = 0; i < N; i++)
for (int j = 0; j < N; j++)
if (matrix_reduced[i][j] < row[i])
row[i] = matrix_reduced[i][j];
reduce_cloumn():
fill_n(col, N, INF);
18CSC204J – DAA 73
/// reducing the column
int col[N];
reduce_column(matrix_reduced, col);
return cost;
}
Application of TSP:
It is used in network design, and transportation route design. The objective is to minimize
the distance.
18CSC204J – DAA 74
WEEK 12 BFS AND DFS IMPLEMENTATION WITH ARRAY
1. DFS
The Depth First Search (DFS) is an algorithm for traversing or searching tree or graph data
structures which uses the idea of backtracking. It explores all the nodes by going forward if
possible or uses backtracking. DFS can be implemented with recursion to do the traversal or
implemented iteratively with a stack.
Pseudo Code:
2. BFS
Breadth First Search (BFS) is an algorithm for traversing or searching tree or graph data
structures. It explores all the nodes at the present depth before moving on to the nodes at the next
depth level. Breadth-first search is a graph traversal algorithm that starts traversing the graph
from the root node and explores all the neighboring nodes. Then, it selects the nearest node and
18CSC204J – DAA 75
explores all the unexplored nodes. While using BFS for traversal, any node in the graph can be
considered as the root node.
Pseudo Code:
18CSC204J – DAA 76
WEEK 13 RANDOMIZED QUICK SORT
Randomized quicksort solves this problem by first randomly shuffling the values
in the array, and then running quicksort, using the first value as the pivot every
time. Now, shuffling might produce a sorted array, and this pivot choice would
then result in a slow O(n^2) quicksort. But, shuffling almost never produces sorted
data. In fact, since you can re-arrange n values in n! ways (n factorial), the
probability that one re-arrangement gives you sorted data is 1/n!, which is
effectively zero for n bigger than 10.
Pseudo Code:
In an operating system, particularly a real-time one, you may wish to schedule tasks by
their priorities and some other properties such as relative importance, expected execution
time, etc.
18CSC204J – DAA 77
WEEK 14 STRING MATCHING ALGORITHMS
Rabin-Karp algorithm is an algorithm used for searching/matching patterns in the text using a
hash function. Unlike Naive string-matching algorithm, it does not travel through every
character in the initial phase rather it filters the characters that do not match and then performs
the comparison. A sequence of characters is taken and checked for the possibility of the
presence of the required string. If the possibility is found then, character matching is
performed.
Pseudo Code:
n = t.length
m = p.length
h = dm-1 mod q
p=0
t0 = 0
for i = 1 to m
p = (dp + p[i]) mod q
t0 = (dt0 + t[i]) mod q
for s = 0 to n - m
if p = ts
if p[1.....m] = t[s + 1..... s + m]
print "pattern found at position" s
If s < n-m
ts + 1 = (d (ts - t[s + 1]h) + t[s + m + 1]) mod q
Applications:
String matching strategies or algorithms provide key role in various real-world
problems or applications. A few of its imperative applications are Spell Checkers, Spam
Filters, Intrusion Detection System, Search Engines, Plagiarism Detection,
Bioinformatics, Digital Forensics and Information Retrieval Systems.
18CSC204J – DAA 78
Implement any problem statement with two different algorithm design
WEEK 15 strategy that you have learnt.
Compare and contrast with its time complexity analysis.
Submit the same as report.
18CSC204J – DAA 79