DAA LAB
DAA LAB
Group A
Assignment No: 1
Title of the Assignment: Write a program non-recursive and recursive program to calculate
Fibonacci numbers and analyze their time and space complexity.
Objective of the Assignment: Students should be able to perform non-recursive and recursive
programs to calculate Fibonacci numbers and analyze their time and space complexity.
Prerequisite:
1. Basic of Python or Java Programming
2. Concept of Recursive and Non-recursive functions
3. Execution flow of calculate Fibonacci numbers
4. Basic of Time and Space complexity
---------------------------------------------------------------------------------------------------------------
Contents for Theory:
1. Introduction to Fibonacci numbers
2. Time and Space complexity
● The Fibonacci series is the sequence of numbers (also called Fibonacci numbers), where
every number is the sum of the preceding two numbers, such that the first two terms are '0' and '1'.
● In some older versions of the series, the term '0' might be omitted. A Fibonacci series can thus
be given as, 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, . . . It can be thus be observed that every term can be
calculated by adding the two terms before it.
● Given the first term, F0 and second term, F1 as '0' and '1', the third term here can be given as,
F2 = 0 + 1 = 1
Similarly,
F3 = 1 + 1 = 2
F4 = 2 + 1 = 3
Fn = Fn-1+Fn-2
Here, the sequence is defined using two different parts, such as kick-off and recursive relation.
It is noted that the sequence starts with 0 rather than 1. So, F5 should be the 6th term of the sequence.
Examples:
Input : n = 2
Output : 1
Input : n = 9
Output : 34
Fn Fibonacci Number
0 0
1 1
2 1
3 2
4 3
5 5
6 8
7 13
8 21
9 34
Next, we’ll iterate through array positions 2 to n-1. At each position i, we store the sum of the two
preceding array values in F[i].
Finally, we return the value of F[n-1], giving us the number at position n in the sequence.
n1, n2 = 0, 1
count = 0
if nterms <= 0:
elif nterms == 1:
print(n1)
else:
print("Fibonacci sequence:")
print(n1)
nth = n1 + n2
# update values
n1 = n2
n2 = nth
count += 1
Output
Fibonacci sequence:
● The time complexity of the Fibonacci series is T(N) i.e, linear. We have to find the sum of two terms
and it is repeated n times depending on the value of n.
● The space complexity of the Fibonacci series using dynamic programming is O(1).
● The time complexity of the above code is T(N) i.e, linear. We have to find the sum of two terms and it
is repeated n times depending on the value of n.
Let’s start by defining F(n) as the function that returns the value of Fn.
To evaluate F(n) for n > 1, we can reduce our problem into two smaller problems of the
same kind: F(n-1) and F(n-2). We can further reduce F(n-1) and F(n-2) to F((n-1)-1) and
F((n-1)-2); and F((n-2)-1) and F((n-2)-2), respectively.
If we repeat this reduction, we’ll eventually reach our known base cases and, thereby, obtain
a solution to F(n).
Employing this logic, our algorithm for F(n) will have two steps:
def recur_fibo(n):
if n <= 1:
return n
else:
return(recur_fibo(n-1) + recur_fibo(n-2))
nterms = 7
if nterms <= 0:
else:
print("Fibonacci sequence:")
for i in range(nterms):
print(recur_fibo(i))
Output
Fibonacci sequence:
● The Space complexity of the above code is O(N) for a recursive series.
The Fibonacci series finds application in different fields in our day-to-day lives. The different
patterns found in a varied number of fields from nature, to music, and to the human body follow the
Fibonacci series. Some of the applications of the series are given as,
● It is used in the grouping of numbers and used to study different other special mathematical
sequences.
● It finds application in Coding (computer algorithms, distributed systems, etc). For example,
Fibonacci series are important in the computational run-time analysis of Euclid's algorithm, used for
determining the GCF of two integers.
● It is applied in numerous fields of science like quantum mechanics, cryptography, etc.
● In finance market trading, Fibonacci retracement levels are widely used in technical analysis.
Conclusion- In this way we have explored Concept of Fibonacci series using recursive and non
recursive method and also learn time and space complexity
Assignment Question
Reference link
● https://www.scaler.com/topics/fibonacci-series-in-c/
● https://www.baeldung.com/cs/fibonacci-computational-complexity
4 4 4 4 4 20
Group A
Assignment No: 2
Title of the Assignment: Write a program to implement Huffman Encoding using a greedy strategy.
Objective of the Assignment: Students should be able to understand and solve Huffman Encoding
using greedy method
Prerequisite:
1. Basic of Python or Java Programming
2. Concept of Greedy method
3. Huffman Encoding concept
---------------------------------------------------------------------------------------------------------------
Contents for Theory:
1. Greedy Method
2. Huffman Encoding
3. Example solved using huffman encoding
---------------------------------------------------------------------------------------------------------------
● This algorithm can perform better than other algorithms (but, not in all cases).
● As mentioned earlier, the greedy algorithm doesn't always produce the optimal solution. This is
the major disadvantage of the algorithm
● For example, suppose we want to find the longest path in the graph below from root to leaf.
Greedy Algorithm
2. At each step, an item is added to the solution set until a solution is reached.
Huffman Encoding
● Huffman Coding is a technique of compressing data to reduce its size without losing any of the
details. It was first developed by David Huffman.
● Huffman Coding is generally useful to compress the data in which there are frequently occurring
characters.
● 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-
● Each character occupies 8 bits. There are a total of 15 characters in the above string. Thus, a total of
8 * 15 = 120 bits are required to send this string.
● Using the Huffman Coding technique, we can compress the string to a smaller size.
● Huffman coding first creates a tree using the frequencies of the character and then generates code for
each character.
● Once the data is encoded, it has to be decoded. Decoding is done using the same tree.
● Huffman Coding prevents any ambiguity in the decoding process using the concept of prefix code
ie. a code associated with a character should not be present in the prefix of any other code. The tree
created above helps in maintaining the property.
● Huffman coding is done with the help of the following steps.
1. Calculate the frequency of each character in the string.
2. Sort the characters in increasing order of the frequency. These are stored in a priority queue Q.
4. Create an empty node z. Assign the minimum frequency to the left child of z and assign the
second minimum frequency to the right child of z. Set the value of the z as the sum of the above two
minimum frequencies.
5. Remove these two minimum frequencies from Q and add the sum into the list of frequencies (*
denote the internal nodes in the figure above).
8. For each non-leaf node, assign 0 to the left edge and 1 to the right edge
For sending the above string over a network, we have to send the tree as well as the above
compressed-code. The total size is given by the table below.
Without encoding, the total size of the string was 120 bits. After encoding the size is reduced to 32
+ 15 + 28 = 75.
Example:
A file contains the following characters with the frequencies as shown. If Huffman Coding is used for data
compression, determine-
After assigning weight to all the edges, the modified Huffman Tree is-
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
Time Complexity-
Code :-
Output
Conclusion- In this way we have explored Concept ofHuffman Encoding using greedy method
Assignment Question
Reference link
● https://towardsdatascience.com/huffman-encoding-python-implementation-8448c3654328
● https://www.programiz.com/dsa/huffman-coding#cpp-code
● https://www.gatevidyalay.com/tag/huffman-coding-example-ppt/
4 4 4 4 4 20
Group A
Assignment No: 3
Title of the Assignment: Write a program to solve a fractional Knapsack problem using a greedy
method.
Objective of the Assignment: Students should be able to understand and solve fractional Knapsack
problems using a greedy method.
Prerequisite:
1. Basic of Python or Java Programming
2. Concept of Greedy method
3. fractional Knapsack problem
---------------------------------------------------------------------------------------------------------------
Contents for Theory:
1. Greedy Method
2. Fractional Knapsack problem
3. Example solved using fractional Knapsack problem
---------------------------------------------------------------------------------------------------------------
● This algorithm can perform better than other algorithms (but, not in all cases).
● As mentioned earlier, the greedy algorithm doesn't always produce the optimal solution. This is
the major disadvantage of the algorithm
● For example, suppose we want to find the longest path in the graph below from root to leaf.
Greedy Algorithm
2. At each step, an item is added to the solution set until a solution is reached.
Knapsack Problem
● The value or profit obtained by putting the items into the knapsack is maximum.
● And the weight limit of the knapsack does not exceed.
possible.
● It is solved using the Greedy Method.
Step-02:
Arrange all the items in decreasing order of their value / weight ratio.
Step-03:
Start putting the items into the knapsack beginning from the item with the highest ratio.
Problem-
For the given set of items and knapsack capacity = 60 kg, find the optimal solution for the fractional
knapsack problem making use of greedy approach.
Now,
= 160 + (20/22) x 77
= 160 + 70
= 230 units
Time Complexity-
● The main time taking step is the sorting of all items in decreasing order of their value / weight ratio.
● If the items are already arranged in the required order, then while loop takes O(n) time.
● The average time complexity of Quick Sort is O(nlogn).
● Therefore, total time taken including the sort is O(nlogn).
Code:-
class Item:
def init (self, value, weight):
self.value = value
self.weight = weight
# Result(value in Knapsack)
finalvalue = 0.0
# Driver Code
if name == " main ":
W = 50
arr = [Item(60, 10), Item(100, 20), Item(120, 30)]
# Function call
max_val = fractionalKnapsack(W, arr)
print(max_val)
Output
Maximum value we can obtain = 24
Conclusion-In this way we have explored Concept of Fractional Knapsack using greedy method
Assignment Question
Reference link
● https://www.gatevidyalay.com/fractional-knapsack-problem-using-greedy-approach/
4 4 4 4 4 20
Group A
Assignment No: 4
Title of the Assignment: Write a program to solve a 0-1 Knapsack problem using dynamic
programming or branch and bound strategy.
Objective of the Assignment: Students should be able to understand and solve 0-1 Knapsack
problem using dynamic programming
Prerequisite:
1. Basic of Python or Java Programming
2. Concept of Dynamic Programming
3. 0/1 Knapsack problem
---------------------------------------------------------------------------------------------------------------
● Dynamic Programming algorithm solves each sub-problem just once and then saves its answer in a
table, thereby avoiding the work of re-computing the answer every time.
● Two main properties of a problem suggest that the given problem can be solved using Dynamic
Programming. These properties are overlapping sub-problems and optimal substructure.
● Dynamic Programming also combines solutions to sub-problems. It is mainly used where the
solution of one sub-problem is needed repeatedly. The computed solutions are stored in a table, so
that these don’t have to be re-computed. Hence, this technique is needed where overlapping sub-
problem exists.
● For example, Binary Search does not have overlapping sub-problem. Whereas recursive program of
Fibonacci numbers have many overlapping sub-problems.
Knapsack Problem
● The value or profit obtained by putting the items into the knapsack is maximum.
● And the weight limit of the knapsack does not exceed.
0/1 knapsack problem is solved using dynamic programming in the following steps-
Step-01:
● Draw a table say ‘T’ with (n+1) number of rows and (w+1) number of columns.
● Fill all the boxes of 0th row and 0th column with zeroes as shown-
Step-02:
Start filling the table row wise top to bottom from left to right.
Here, T(i , j) = maximum value of the selected items if we can take items 1 to i and have weight restrictions
of j.
Step-03:
● To identify the items that must be put into the knapsack to obtain that maximum profit,
● Consider the last column of the table.
● Start scanning the entries from bottom to top.
● On encountering an entry whose value is not same as the value stored in the entry immediately
above it, mark the row label of that entry.
● After all the entries are scanned, the marked labels represent the items that must be put into the
knapsack
Problem-.
For the given set of items and knapsack capacity = 5 kg, find the optimal solution for the 0/1 knapsack
problem making use of a dynamic programming approach.
Solution-
Given
● Knapsack capacity (w) = 5 kg
● Number of items (n) = 4
Step-01:
● Draw a table say ‘T’ with (n+1) = 4 + 1 = 5 number of rows and (w+1) = 5 + 1 = 6 number of columns.
● Fill all the boxes of 0th row and 0th column with 0.
Step-02:
Start filling the table row wise top to bottom from left to right using the formula-
After all the entries are computed and filled in the table, we get the following table-
● The last entry represents the maximum possible value that can be put into the knapsack.
● So, maximum possible value that can be put into the knapsack = 7.
Following Step-04,
Time Complexity-
● Each entry of the table requires constant time θ(1) for its computation.
● It takes θ(nw) time to fill (n+1)(w+1) table entries.
● It takes θ(n) time for tracing the solution since tracing process traces the n rows.
● Thus, overall θ(nw) time is taken to solve 0/1 knapsack problem using dynamic programming
Code :-
# code
if wt[i-1] <= w:
# Driver code
W = 50
n = len(val)
Output
220
Conclusion-In this way we have explored Concept of 0/1 Knapsack using Dynamic approch
Assignment Question
Reference link
● https://www.gatevidyalay.com/0-1-knapsack-problem-using-dynamic-programming-appr
oach/
● https://www.youtube.com/watch?v=mMhC9vuA-70
● https://www.tutorialspoint.com/design_and_analysis_of_algorithms/design_and_analysi
s_of_algorithms_fractional_knapsack.htm
4 4 4 4 4 20
Group A
Assignment No: 5
Title of the Assignment: Design n-Queens matrix having first Queen placed. Use backtracking to
place remaining Queens to generate the final n-queen’s matrix.
Objective of the Assignment: Students should be able to understand and solve n-Queen
Problem,and understand basics of Backtracking
Prerequisite:
1. Basic of Python or Java Programming
2. Concept of backtracking method
3. N-Queen Problem
---------------------------------------------------------------------------------------------------------------
Contents for Theory:
1. Introduction to Backtracking
2. N-Queen Problem
---------------------------------------------------------------------------------------------------------------
Introduction to Backtracking
● Many problems are difficult to solve algorithmically. Backtracking makes it possible to solve at
least some large instances of difficult combinatorial problems.
What is backtracking?
Backtracking is finding the solution of a problem whereby the solution depends on the previous steps taken.
For example, in a maze problem, the solution depends on all the steps you take one-by-one. If any of those
steps is wrong, then it will not lead us to the solution. In a maze problem, we first choose a path and continue
moving along it. But once we understand that the particular path is incorrect, then we just come back and
change it. This is what backtracking basically is.
In backtracking, we first take a step and then we see if this step taken is correct or not i.e., whether it will give
a correct answer or not. And if it doesn’t, then we just come back and change our first step. In general, this is
accomplished by recursion. Thus, in backtracking, we first start with a partial sub-solution of the problem
(which may or may not lead us to the solution) and then check if we can proceed further with this sub-solution
or not. If not, then we just come back and change it.
Applications of Backtracking:
● N Queens Problem
● Sum of subsets problem
● Graph coloring
● Hamiltonian cycles.
One of the most common examples of the backtracking is to arrange N queens on an NxN chessboard such
that no queen can strike down any other queen. A queen can attack horizontally, vertically, or diagonally. The
solution to this problem is also attempted in a similar way. We first place the first queen anywhere arbitrarily
and then place the next queen in any of the safe places. We continue this process until the number of unplaced
queens becomes zero (a solution is found) or no safe place is left. If no safe place is left, then we change the
position of the previously placed queen.
N-Queens Problem:
A classic combinational problem is to place n queens on a n*n chess board so that no two attack, i.,e no
N Queen problem is the classical Example of backtracking. N-Queen problem is defined as,
“given N x N chess board, arrange N queens in such a way that no two queens attack each other
by being in the same row, column or diagonal”.
● For N = 1, this is a trivial case. For N = 2 and N = 3, a solution is not possible. So we start with N = 4
and we will generalize it for N queens.
Algorithm
a) If the queen can be placed safely in this row then mark this [row, column] as part of the
solution and recursively check if placing queen here leads to a solution.
b) If placing the queen in [row, column] leads to a solution then return true.
c) If placing queen doesn't lead to a solution then unmark this [row, column] (Backtrack) and go
to step (a) to try other rows.
4) If all rows have been tried and nothing worked,return false to trigger backtracking.
4- Queen Problem
Problem 1 : Given 4 x 4 chessboard, arrange four queens in a way, such that no two queens attack each other.
That is, no two queens are placed in the same row, column, or diagonal.
● We have to arrange four queens, Q1, Q2, Q3 and Q4 in 4 x 4 chess board. We will put with queen in
ith row. Let us start with position (1, 1). Q1 is the only queen, so there is no issue. partial solution is
<1>
● We cannot place Q2 at positions (2, 1) or (2, 2). Position (2, 3) is acceptable. the partial solution is <1,
3>.
● Next, Q3 cannot be placed in position (3, 1) as Q1 attacks her. And it cannot be placed at (3, 2), (3, 3)
or (3, 4) as Q2 attacks her. There is no way to put Q3 in the third row. Hence, the algorithm backtracks
and goes back to the previous solution and readjusts the position of queen Q2. Q2 is moved from
positions (2, 3) to
(2, 4). Partial solution is <1, 4>
● Now, Q3 can be placed at position (3, 2). Partial solution is <1, 4, 3>.
● Queen Q4 cannot be placed anywhere in row four. So again, backtrack to the previous solution and
readjust the position of Q3. Q3 cannot be placed on (3, 3) or(3, 4). So the algorithm backtracks even
further.
● All possible choices for Q2 are already explored, hence the algorithm goes back to partial solution
<1> and moves the queen Q1 from (1, 1) to (1, 2). And this process continues until a solution is found.
All possible solutions for 4-queen are shown in fig (a) & fig. (b)
Fig. (d) describes the backtracking sequence for the 4-queen problem.
The solution of the 4-queen problem can be seen as four tuples (x1, x2, x3, x4), where xi represents the
column number of queen Qi. Two possible solutions for the 4-queen problem are (2, 4, 1, 3) and (3, 1, 4,
2).
Explanation :
The above picture shows an NxN chessboard and we have to place N queens on it. So, we will start by
placing the first queen.
Now, the second step is to place the second queen in a safe position and then the third queen.
Now, you can see that there is no safe place where we can put the last queen. So, we will just change the
position of the previous queen. And this is backtracking.
Also, there is no other position where we can place the third queen so we will go back one more step and
change the position of the second queen.
And now we will place the third queen again in a safe position until we find a solution.
We will continue this process and finally, we will get the solution as shown below.
We need to check if a cell (i, j) is under attack or not. For that, we will pass these two in our function along
with the chessboard and its size - IS-ATTACK(i, j, board, N).
If there is a queen in a cell of the chessboard, then its value will be 1, otherwise, 0.
The cell (i,j) will be under attack in three condition - if there is any other queen in row i, if there is any other
queen in the column j or if there is any queen in the diagonals.
We are already proceeding row-wise, so we know that all the rows above the current row(i) are filled but not
the current row and thus, there is no need to check for row i.
We can check for the column j by changing k from 1 to i-1 in board[k][j] because only the rows from 1 to i-1
are filled.
for k in 1 to i-1
if board[k][j]==1
return TRUE
Now, we need to check for the diagonal. We know that all the rows below the row i are empty, so we need to
check only for the diagonal elements which above the row i.
If we are on the cell (i, j), then decreasing the value of i and increasing the value of j will make us traverse
over the diagonal on the right side, above the row i.
k = i-1
l = j+1
if board[k][l] == 1
return TRUE
k=k-1
l=l+1
Also if we reduce both the values of i and j of cell (i, j) by 1, we will traverse over the left diagonal, above the
row i.
k = i-1
l = j-1
if board[k][l] == 1
return TRUE
k=k-1
l=l-1
At last, we will return false as it will be return true is not returned by the above statements and the cell (i,j) is
safe.
IS-ATTACK(i, j, board, N)
for k in 1 to i-1
if board[k][j]==1
return TRUE
k = i-1
l = j+1
if board[k][l] == 1
return TRUE
k=k+1
l=l+1
k = i-1
l = j-1
if board[k][l] == 1
return TRUE
k=k-1
l=l-1
return FALSE
Now, let's write the real code involving backtracking to solve the N Queen problem.
Our function will take the row, number of queens, size of the board and the board itself - N-QUEEN(row, n, N,
board).
If the number of queens is 0, then we have already placed all the queens.
if n==0
return TRUE
Otherwise, we will iterate over each cell of the board in the row passed to the function and for each cell, we will
check if we can place the queen in that cell or not. We can't place the queen in a cell if it is under attack.
for j in 1 to N
if !IS-ATTACK(row, j, board, N)
board[row][j] = 1
After placing the queen in the cell, we will check if we are able to place the next queen with this arrangement or
not. If not, then we will choose a different position for the current queen.
for j in 1 to N
...
return TRUE
board[row][j] = 0
if N-QUEEN(row+1, n-1, N, board) - We are placing the rest of the queens with the current arrangement. Also,
since all the rows up to 'row' are occupied, so we will start from 'row+1'. If this returns true, then we are successful
in placing all the queen, if not, then we have to change the position of our current queen. So, we are leaving the
current cell board[row][j] = 0 and then iteration will find another place for the queen and this is backtracking.
Take a note that we have already covered the base case - if n==0 → return TRUE. It means when all queens will
be placed correctly, then N-QUEEN(row, 0, N, board) will be called and this will return true.
At last, if true is not returned, then we didn't find any way, so we will return false.
N-QUEEN(row, n, N, board)
...
return FALSE
N-QUEEN(row, n, N, board)
if n==0
return TRUE
for j in 1 to N
if !IS-ATTACK(row, j, board, N)
board[row][j] = 1
return TRUE
return FALSE
Code :-
# Python3 program to solve N Queen
# Problem using backtracking
global N
N=4
def printSolution(board):
for i in range(N):
for j in range(N):
print(board[i][j], end = " ")
print()
return True
if isSafe(board, i, col):
board[i][col] = 1
if solveNQUtil(board, 0) == False:
print ("Solution does not exist")
return False
printSolution(board)
return True
# Driver Code
solveNQ()
Output:-
Conclusion- In this way we have explored Concept of Backtracking method and solve n-Queen
problem using backtracking method
Assignment Question
Reference link
● https://www.codesdope.com/blog/article/backtracking-explanation-and-n-queens-problem/
● https://www.codesdope.com/course/algorithms-backtracking/\
● https://codecrucks.com/n-queen-problem/
●
Assignment No : 6
MINI PROJECT 1
Theory :-
Multiplication of matrix does take time surely. Time complexity of matrix multiplication is O(n^3)
using normal matrix multiplication. And Strassen algorithm improves it and its time complexity is
O(n^(2.8074)).
But, Is there any way to improve the performance of matrix multiplication using the normal
method.
Multi-threading can be done to improve it. In multi-threading, instead of utilizing a single core of
your processor, we utilizes all or more core to solve the problem.
We create different threads, each thread evaluating some part of matrix multiplication.
Depending upon the number of cores your processor has, you can create the number of threads
required. Although you can create as many threads as you need, a better way is to create each
thread for one core.
In second approach,we create a separate thread for each element in resultant matrix. Using
pthread_exit() we return computed value from each thread which is collected by pthread_join().
This approach does not make use of any global variables.
Code :-
// CPP Program to multiply two matrix using pthreads
#include <bits/stdc++.h>
using namespace std;
int matA[MAX][MAX];
int matB[MAX][MAX];
int matC[MAX][MAX];
int step_i = 0;
// Driver Code
int main()
{
// Generating random values in matA and matB
for (int i = 0; i < MAX; i++) {
for (int j = 0; j < MAX; j++) {
matA[i][j] = rand() % 10;
matB[i][j] = rand() % 10;
}
// Displaying matA
cout << endl
<< "Matrix A" << endl;
for (int i = 0; i < MAX; i++) {
for (int j = 0; j < MAX; j++)
cout << matA[i][j] << " ";
cout << endl;
}
// Displaying matB
cout << endl
<< "Matrix B" << endl;
for (int i = 0; i < MAX; i++) {
for (int j = 0; j < MAX; j++)
cout << matB[i][j] << " ";
cout << endl;
}