0% found this document useful (0 votes)
28 views36 pages

Daa Lab Manual 24

The document is a laboratory manual for the Design and Analysis of Algorithms course at Anna University, detailing various experiments and algorithms. It includes practical implementations of recursive and non-recursive algorithms, matrix multiplication using Strassen’s method, and topological sorting. The manual serves as a guide for students to understand and execute algorithms in a structured manner.

Uploaded by

nncecse 2017
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)
28 views36 pages

Daa Lab Manual 24

The document is a laboratory manual for the Design and Analysis of Algorithms course at Anna University, detailing various experiments and algorithms. It includes practical implementations of recursive and non-recursive algorithms, matrix multiplication using Strassen’s method, and topological sorting. The manual serves as a guide for students to understand and execute algorithms in a structured manner.

Uploaded by

nncecse 2017
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/ 36

lOMoARcPSD|30021711

DAA LAB Manual 24

Design and analysis of algorithms (Anna University)

Scan to open on Studocu

Studocu is not sponsored or endorsed by any college or university


Downloaded by nncecse 2017 ([email protected])
lOMoARcPSD|30021711

ANNAI MIRA COLLEGE OF ENGINEERING AND TECHNOLOGY


NH-46, Chennai-Bengaluru National Highways, Arappakkam,
Ranipet-632517, Tamil Nadu, India
Telephone: 04172-292925 Fax: 04172-292926
Email:[email protected]/[email protected] Web: www.amcet.in

DEPARTMENT OF ARTIFICIAL INTELLIGENCE AND DATA SCIENCE

AD3351 –DESIGN AND ANALYSIS OF ALGORITHMS


LABORATORY

Name : …………………………………….

Register Number : …………………………………….

Year & Branch : …………………………………….

Semester : ……………………………………

Academic Year : ………………………….................

Downloaded by nncecse 2017 ([email protected])


lOMoARcPSD|30021711

ANNAI MIRA COLLEGE OF ENGINEERING AND TECHNOLOGY


NH-46, Chennai-Bengaluru National Highways, Arappakkam,
Ranipet-632517, Tamil Nadu, India
Telephone: 04172-292925 Fax: 04172-292926
Email:[email protected]/[email protected] Web: www.amcet.in

DEPARTMENT OF ARTIFICIAL INTELLIGENCE AND DATA SCIENCE

CERTIFICATE
This is to certify that the bonafide record of the practical work done by
………….……………………..……. Register Number …………….……………… of II year
B.Tech(Artificial Intelligence and Data Science) submitted for the B.Tech Degree
Practical examination (III Semester) in AD3351 – DESIGN AND ANALYSIS OF
ALGORITHMS LABORATORY during the academic year 2023 -2024.

Submitted for the practical examination held on ------------------

Staff in –Charge Head of the Department

Internal Examiner External Examiner

Downloaded by nncecse 2017 ([email protected])


lOMoARcPSD|30021711

TABLE OF CONTENTS

Ex. Page Staff


No. Date Name of the Experiment Marks
No. Sign

Implement recursive and non-recursive


1 algorithms and study the order of growth from 01
. log2n to n!

Divide and Conquer - Strassen’s Matrix


2 03
Multiplication
.

3 Decrease and Conquer - Topological Sorting 05


.

4 Transform and Conquer - Heap Sort 08


.

Dynamic programming - Coin change Problem,


5 Warshall’s and Floyd‘s algorithms, Knapsack 11
. Problem

Greedy Technique – Dijkstra’s algorithm,


6 14
Huffman Trees and codes
.

7 Iterative improvement - Simplex Method 18


.

Backtracking – N-Queen problem, Subset Sum


8 23
Problem
.

Branch and Bound - Traveling Salesman


9 25
Problem
.

Downloaded by nncecse 2017 ([email protected])


lOMoARcPSD|30021711

EX. NO. : 01 IMPLEMENT RECURSIVE AND NON-RECURSIVE ALGORITHMS


AND STUDY THE ORDER OF GROWTHFROM LOG2 N TO N!
DATE :

AIM:
To Implement Recursive and Non-Recursive Algorithms and Study the Order of
Growth from Log2 N To N!

ALGORITHM FOR RECURSIVE AND NON-RECURSIVE:

STEP 1: Start the program.


STEP 2: Implement recursive and non-recursive algorithms using log2n to n!
STEP 3: Compute Factorial n fact (n-1), Fibonacci series using recursive algorithm.
STEP 4: Compute the insertion sort using non recursive algorithm.
STEP 5: End the program.

PROGRAM:
Program to compute Log2 n
# Python 3 program to find log(n) using Recursion
def Log2n(n):
if n > 1:
return 1 + Log2n(n / 2)
else:
return 0
# Driver code
n = 32
print(Log2n(n))

OUTPUT:
5

# Python3 program to find log(n) using Inbuilt


# Function of math library
import math
if __name__ == "__main__":
n = 32
print(int(math.log(n, 2)))

OUTPUT:
5

# Python program to find log(n) on arbitrary base using Recursion


def Logn(n, r):
return 1 + Logn(n / r, r) if n >= r else 0
# Driver code
4

Downloaded by nncecse 2017 ([email protected])


lOMoARcPSD|30021711

n = 256
r=3
print(Logn(n, r))

OUTPUT:
5

import math
def Logn(n, r):
return math.log(n) / math.log(r)
n = 256
r=3
print(int(Logn(n, r)))

OUTPUT:
5

def factorial(n):
return_value = 1
for i in range(2, n + 1):
return_value *= i
return return_value
result = factorial(4)
print(result)

OUTPUT:
24

import math
def fact(n):
if n == 1:
return 0
else:
return fact(n - 1) + math.log(n)
N=3
print(fact(N))

OUTPUT:
1.791759

RESULT:

Thus the program have been executed using Recursive and Non-Recursive Algorithm.

Downloaded by nncecse 2017 ([email protected])


lOMoARcPSD|30021711

EX. NO. : 02 DIVIDE AND CONQUER - STRASSEN’S MATRIX


DATE : MULTIPLICATION

AIM:
To multiply two square matrices using Divide and Conquer - Strassen’s Matrix
Multiplication method.

ALGORITHM FOR STRASSEN’S MATRIX:

Step 1: Base case If the size of the matrices is 1x1, then the product is simply the product
of the two elements.
Step 2: Divide and conquer
Divide each of the matrices A and B into four submatrices of equal size:
a | b
c | d

e | f
g | h
Step 3: Recursively compute seven submatrices
Recursively compute the following seven submatrices:
p1 = strassen(a, (f - h))
p2 = strassen((a + b), h)
p3 = strassen((c + d), e)
p4 = strassen(d, (g - e))
p5 = strassen((a + d), (e + h))
p6 = strassen((b - d), (g + h))
p7 = strassen((a - c), (e + f))
Step 4: Combine submatrices
Compute the four submatrices of the result matrix C:
c11 = p5 + p4 - p2 + p6
c12 = p1 + p2
c21 = p3 + p4
c22 = p1 + p5 - p3 - p7
Step 5: Combine submatrices into final result matrix
Combine the four submatrices into the final result matrix C:
C = [[c11, c12],
[c21, c22]]
Here are some additional notes about the Strassen algorithm:
• The Strassen algorithm is a more efficient way to multiply matrices than the traditional
method, especially for large matrices.
• However, the Strassen algorithm is also more complex than the traditional method, and it
can be more prone to errors.
• For small matrices, the traditional method is usually faster and more accurate.
• The Strassen algorithm is best used for large matrices where speed is more important
than accuracy.

Downloaded by nncecse 2017 ([email protected])


lOMoARcPSD|30021711

PROGRAM:

OUTPUT:

Time Complexity of above method is O(N3).


import numpy as np

def split(matrix):

"""
Splits a given matrix into quarters.
Input: nxn matrix
Output: tuple containing 4 n/2 x n/2 matrices corresponding to a, b, c, d
"""

row, col = matrix.shape


row2, col2 = row // 2, col // 2
return matrix[:row2, :col2], matrix[:row2, col2:], matrix[row2:, :col2], matrix[row2:, col2:]

def strassen(x, y):

"""
Computes matrix product by the divide and conquer approach, recursively.
Input: nxn matrices x and y
Output: nxn matrix, product of x and y
"""

# Base case when the size of matrices is 1x1


if len(x) == 1:
return x * y

# Splitting the matrices into quadrants. This will be done recursively.


a, b, c, d = split(x)
e, f, g, h = split(y)

# Computing the 7 products, recursively (p1, p2...p7)


p1 = strassen(a, f - h)
p2 = strassen(a + b, h)
p3 = strassen(c + d, e)
p4 = strassen(d, g - e)
p5 = strassen(a + d, e + h)
p6 = strassen(b - d, g + h)
p7 = strassen(a - c, e + f)

# Computing the values of the 4 quadrants of the final matrix c


c11 = p5 + p4 - p2 + p6
c12 = p1 + p2
7

Downloaded by nncecse 2017 ([email protected])


lOMoARcPSD|30021711

c21 = p3 + p4
c22 = p1 + p5 - p3 - p7

# Combining the 4 quadrants into a single matrix by stacking horizontally and vertically.
c = np.vstack((np.hstack((c11, c12)), np.hstack((c21, c22))))
return c

# Example usage:

# Define matrices A and B


A = np.array([[1, 1, 1, 1],
[2, 2, 2, 2],
[3, 3, 3, 3],
[2, 2, 2, 2]])

B = np.array([[1, 1, 1, 1],
[2, 2, 2, 2],
[3, 3, 3, 3],
[2, 2, 2, 2]])

# Compute the result matrix


result = strassen(A, B)

# Display the matrices and the result


print("Array A =>")
print(A)
print("\nArray B =>")
print(B)
print("\nResult Array =>")
print(result)

Downloaded by nncecse 2017 ([email protected])


lOMoARcPSD|30021711

OUTPUT:

Array A =>
[ [1 1 1 1]
[2 2 2 2]
[3 3 3 3]
[2 2 2 2] ]

Array B =>
[ [1 1 1 1]
[2 2 2 2]
[3 3 3 3]
[2 2 2 2] ]

Result Array =>


Array B =>
[[ 8 8 8 8]
[16 16 16 16]
[24 24 24 24]
[16 16 16 16]]

RESULT:
Thus the programs have been executed using Divide and Conquer - Strassen’s
Matrix Multiplication.

Downloaded by nncecse 2017 ([email protected])


lOMoARcPSD|30021711

EX. NO. : 03
DIVIDE AND CONQUER –TOPOLOGICAL SORT
DATE :

AIM:
To implement Decrease and Conquer Recursive and Non-Recursive
Topological Sort for the given numbers.

ALGORITHM FOR TOPOLOGICAL SORT:

Step 1: Initialize an empty list to store the topological ordering.


Step 2: Find a node with no incoming edges (in-degree of 0). If there are multiple
such nodes, you can choose any.
Step 3: Add the selected node to the topological ordering list and remove it from the
graph.
Step 4: Update the in-degrees of the remaining nodes by decrementing the in-
degrees of their neighbors (since we removed a node with incoming edges).
Step 5: Repeat steps 2-4 until all nodes are included in the topological ordering.

➢ If, at any point, you can't find a node with in-degree 0 and the graph is not
empty, it means there is a cycle in the graph, and topological sort is not
possible.
➢ This algorithm runs in O(V + E) time, where V is the number of vertices and E
is the number of edges in the graph.

PROGRAM:
i) RECURSIVE TOPOLOGICAL SORT:
#Python program to print topological sorting of a DAG

from collections import defaultdict


# Class to represent a graph
class Graph:
def __init__(self, vertices):
self.graph = defaultdict(list) # dictionary containing adjacency List
self.V = vertices # No. of vertices

# function to add an edge to graph


def addEdge(self, u, v):
self.graph[u].append(v)

# A recursive function used by topologicalSort


def topologicalSortUtil(self, v, visited, stack):
# Mark the current node as visited.
visited[v] = True

10

Downloaded by nncecse 2017 ([email protected])


lOMoARcPSD|30021711

# Recur for all the vertices adjacent to this vertex


for i in self.graph[v]:
if visited[i] == False:
self.topologicalSortUtil(i, visited, stack)
# Push current vertex to stack which stores result
stack.insert(0, v)

# The function to do Topological Sort. It uses recursive topologicalSortUtil()


def topologicalSort(self):
# Mark all the vertices as not visited
visited = [False] * self.V
stack = []

‘’’Call the recursive helper function to store Topological Sort starting


from all vertices one by one’’’
for i in range(self.V):
if visited[i] == False:
self.topologicalSortUtil(i, visited, stack)
# Print contents of stack
print(stack)

g = Graph(6)
g.addEdge(5, 2)
g.addEdge(5, 0)
g.addEdge(4, 0)
g.addEdge(4, 1)
g.addEdge(2, 3)
g.addEdge(3, 1)

print("Following is a Topological Sort of the given graph:")


g.topologicalSort()

OUTPUT:
Following is a Topological Sort of
the given graph[5, 4, 2, 3, 1, 0]

Ii) NON RECURSIVE TOPOLOGICAL SORT:


#Python program to print topological sorting of a DAG

from collections import defaultdict


# Class to represent a graph
class Graph:
def __init__(self, vertices):
self.graph = defaultdict(list) # dictionary containing adjacency List
self.V = vertices # No. of vertices

11

Downloaded by nncecse 2017 ([email protected])


lOMoARcPSD|30021711

# function to add an edge to the graph


def addEdge(self, u, v):
self.graph[u].append(v)

# neighbors lazy generator given key


def neighbor_gen(self, v):
for k in self.graph[v]:
yield k

# non-recursive topological sort


def nonRecursiveTopologicalSort(self):
# Mark all the vertices as not visited
visited = [False] * self.V
# Result stack
stack = []

for i in range(self.V):
if not visited[i]:
working_stack = [(i, self.neighbor_gen(i))]
continue_flag = True

while working_stack:
v, gen = working_stack[-1]
visited[v] = True
working_stack.pop()
continue_flag = True

while continue_flag:
next_neighbor = next(gen, None)

if next_neighbor is None:
continue_flag = False
stack.append(v)
elif not visited[next_neighbor]:
working_stack.append((v, gen))
working_stack.append((next_neighbor,
self.neighbor_gen(next_neighbor)))
continue_flag = False
# Print contents of the stack in reverse
print("The following is a Topological Sort of the given graph:")
print(stack[::-1])

g = Graph(6)
g.addEdge(5, 2)
g.addEdge(5, 0)
g.addEdge(4, 0)
g.addEdge(4, 1)
g.addEdge(2, 3)
g.addEdge(3, 1)
g.nonRecursiveTopologicalSort()

12

Downloaded by nncecse 2017 ([email protected])


lOMoARcPSD|30021711

OUTPUT:

The following is a Topological Sort


of the given graph [5, 4, 2, 3, 1, 0]

RESULT:
Thus the programs have been executed using Decrease and Conquer
Recursive and Non-Recursive Topological Sort.

13

Downloaded by nncecse 2017 ([email protected])


lOMoARcPSD|30021711

EX. NO. : 04
TRANSFORM AND CONQUER- HEAP SORT
DATE :

AIM:
To sort given numbers in to ascending order using Transform and Conquer – Heap
sort method.

ALGORITHM FOR HEAP SORT:

Step 1: Build Max Heap: Convert the array into a max heap (a complete
binary tree where each node is greater than or equal to its children).
Step 2: Heapify: Starting from the last non-leaf node and moving up to
the root, heapify the array by ensuring that each subtree is a max heap.
Step 3: Swap and Heapify Down: Swap the root (maximum element) with
the last element, reduce the heap size by 1, and heapify the root.
Step 4: Repeat: Repeat steps 3 until the heap size is greater than 1.

➢ This process sorts the array in ascending order.


➢ The time complexity of Heap Sort is O(n log n) for the worst, average, and best cases.

PROGRAM:

#(i) Transform and Conquer-Heapsort use to sort numbers get from user.
def heapsort(alist):
build_max_heap(alist)
for i in range(len(alist) - 1, 0, -1):
alist[0], alist[i] = alist[i], alist[0]
max_heapify(alist, index=0, size=i)

def parent(i):
return (i - 1) // 2

def left(i):
return 2 * i + 1

def right(i):
return 2 * i + 2

def build_max_heap(alist):
length = len(alist)
start = parent(length - 1)
while start >= 0:

14

Downloaded by nncecse 2017 ([email protected])


lOMoARcPSD|30021711

max_heapify(alist, index=start, size=length)


start = start - 1
def max_heapify(alist, index, size):
l = left(index)
r = right(index)
if l < size and alist[l] > alist[index]:
largest = l
else:
largest = index
if r < size and alist[r] > alist[largest]:
largest = r

if largest != index:
alist[largest], alist[index] = alist[index], alist[largest]
max_heapify(alist, largest, size)

alist = input('Enter the list of numbers: ').split()


alist = [int(x) for x in alist]
heapsort(alist)
print('Sorted list:', end=' ')
print(alist)

OUTPUT:
Case 1:
Enter the list of numbers: 3 2 2 1 0 -2 5 7
Sorted list: [-2, 0, 1, 2, 2, 3, 5, 7]
Case 2:
Enter the list of numbers: 1 4 3 2 5
Sorted list: [1, 2, 3, 4, 5]

#(ii) Transform and Conquer-Heapsort use to sort the given numbers.


def heapify(arr, n, i):
largest = i # Initialize largest as root
l = 2 * i + 1 # left = 2*i + 1
r = 2 * i + 2 # right = 2*i + 2

# See if left child of root exists and is greater than root


if l < n and arr[i] < arr[l]:
largest = l

# See if right child of root exists and is greater than root


if r < n and arr[largest] < arr[r]:

15

Downloaded by nncecse 2017 ([email protected])


lOMoARcPSD|30021711

largest = r

# Change root, if needed


if largest != i:
arr[i], arr[largest] = arr[largest], arr[i] # swap
# Heapify the root.
heapify(arr, n, largest)

# The main function to sort an array of given size


def heapSort(arr):
n = len(arr)

# Build a max-heap.
# Since the last parent will be at ((n//2)-1), we can start at that location.
for i in range(n // 2 - 1, -1, -1):
heapify(arr, n, i)

# One by one extract elements


for i in range(n - 1, 0, -1):
arr[i], arr[0] = arr[0], arr[i] # swap
heapify(arr, i, 0)

# Driver code to test above


arr = [12, 11, 13, 5, 6, 7]
heapSort(arr)
n = len(arr)
print('Sorted array is:')
for i in range(n):
print(arr[i])

OUTPUT:
Sorted array is
5
6
7
11
12
13

RESULT:
Thus the programs have been executed using Transform and Conquer- Heap Sort.

16

Downloaded by nncecse 2017 ([email protected])


lOMoARcPSD|30021711

EX. NO. : 05 DYNAMIC PROGRAMMING – COIN CHANGE


PROBLEM, WARSHALL’S AND FLOYD’S ALGORITHM,
DATE : KNAPSACK PROBLEM

AIM:
To implement Coin Change Problem, Warshall’s and Floyd’s Algorithm &
Knapsack Problem in Dynamic Programming.

ALGORITHM FOR DYNAMIC PROGRAMMING:

Step 1: Define the problem: Clearly define the problem and the objective you want to optimize.
Step 2: Identify subproblems: Break down the main problem into smaller overlapping
subproblems. Subproblems should have optimal substructure, meaning the optimal
solution to the main problem can be constructed from optimal solutions to its
subproblems.
Step 3: Formulate a recursive relation: Express the solution to a subproblem in terms of
solutions to smaller subproblems. This recursive relation will guide the construction of the
dynamic programming algorithm.
Step 4: Memorization or bottom-up approach: Choose between a top-down approach with
memorization (storing solutions to subproblems in a data structure like a table or cache)
or a bottom-up approach where you solve the subproblems in a specific order, starting
from the smallest ones.
Step 5: Define base cases: Identify the base cases, which are the smallest subproblems that can
be solved directly without further decomposition.
Step 6: Build the solution: Either recursively build the solution using memorization or
iteratively solve subproblems in a bottom-up manner, using the recursive relation.
Step 7: Optimize: If possible, optimize the algorithm further by eliminating redundant
computations or unnecessary storage.

PROGRAM:
i) DYNAMIC PROGRAMMING:
def kProfit(W, N, wt, pr, dp):
# Base Condition
if N == 0 or W == 0:
return 0

# If subproblem is previously solved, then return it.


if dp[N][W] is not None:
return dp[N][W]

if wt[N - 1] <= W:
dp[N][W] = max(pr[N - 1] + kProfit(W - wt[N - 1], N - 1, wt, pr, dp), kProfit(W, N - 1, wt, pr, dp))
else:
dp[N][W] = kProfit(W, N - 1, wt, pr, dp)
17

Downloaded by nncecse 2017 ([email protected])


lOMoARcPSD|30021711

return dp[N][W]

if __name__ == '__main__':
W = 11
wt = [1, 2, 5, 6, 7]
pr = [1, 6, 18, 22, 28]
N = len(pr)

# Define DP array
dp = [[None] * (W + 1) for _ in range(N + 1)]

# Call for kProfit to calculate max profit


maxProfit = kProfit(W, N, wt, pr, dp)
print('Maximum Profit is:', maxProfit)

OUTPUT:
Maximum Profit is : 40

ii) COIN CHANGE PROBLEM:


def count(S, m, n):
# Create a table to store the count of solutions
table = [[0 for x in range(m)] for x in range(n + 1)]

# Initialize table[0][i] to 1 for all i, as there's one way to make the sum 0 (no coins used).
for i in range(m):
table[0][i] = 1

# Fill the table in a bottom-up manner


for i in range(1, n + 1):
for j in range(m):
# Count of solutions including S[j]
x = table[i - S[j]][j] if i - S[j] >= 0 else 0

# Count of solutions excluding S[j]


y = table[i][j - 1] if j >= 1 else 0

# Total count
table[i][j] = x + y

return table[n][m - 1]

# Driver program to test the function


arr = [1, 2, 3]
m = len(arr)
n=4

18

Downloaded by nncecse 2017 ([email protected])


lOMoARcPSD|30021711

print("TOTAL NO. OF COINS =",count(arr, m, n))

OUTPUT:
TOTAL NO. OF COINS = 4

iii) WARSHALL’S AND FLOYD’S ALGORITHM:


nV = 4
INF = 999

# Algorithm
def floyd(G):
dist = list(map(lambda p: list(map(lambda q: q, p)), G))

# Adding vertices individually


for r in range(nV):
for p in range(nV):
for q in range(nV):
dist[p][q] = min(dist[p][q], dist[p][r] + dist[r][q])

sol(dist)

# Printing the output


def sol(dist):
for p in range(nV):
for q in range(nV):
if dist[p][q] == INF:
print("INF", end=" ")
else:
print(dist[p][q], end=" ")
print(" ")

G = [[0, 5, INF, INF],


[50, 0, 15, 5],
[30, INF, 0, 15],
[15, INF, 5, 0]]

floyd(G)

OUTPUT:

0 5 15 10
20 0 10 5
30 35 0 15
15 20 5 0

19

Downloaded by nncecse 2017 ([email protected])


lOMoARcPSD|30021711

iv) KANPSACK PROBLEM:


def knapSack(W, wt, val, n):
K = [[0 for x in range(W + 1)] for x in range(n + 1)]

for i in range(n + 1):


for w in range(W + 1):
if i == 0 or w == 0:
K[i][w] = 0
elif wt[i-1] <= w:
K[i][w] = max(val[i-1] + K[i-1][w-wt[i-1]], K[i-1][w])
else:
K[i][w] = K[i-1][w]

return K[n][W]

# Driver program to test above function


val = [60, 100, 120]
wt = [10, 20, 30]
W = 50
n = len(val)
print(knapSack(W, wt, val, n))

OUTPUT:
220

RESULT:
Thus the programs have been executed using Dynamic Programming – Coin
Change Problem, Warshall’s and Floyd’s Algorithm & Knapsack Problem.

20

Downloaded by nncecse 2017 ([email protected])


lOMoARcPSD|30021711

EX. NO. : 06 GREEDY TECHNIQUES - DIJKSTRA’S ALGORITHM’S,


DATE : HUFFMAN TREES AND CODES

AIM:
To implement Dijkstra’s Algorithm, Huffman Trees and Codes using Greedy
Technique.

ALGORITHM FOR GREEDY TECHNIQUES:

Step 1: Initialization: Start with an empty solution.


Step 2: Selection: Choose the best available option at the current stage based on a greedy
criterion.
Step 3: Feasibility Check: Ensure that the chosen option doesn't violate any constraints or
requirements.
Step 4: Update Solution: Add the chosen option to the solution.
Step 5: Repeat: Repeat steps 2-4 until the solution is complete or a termination condition is met.

Greedy algorithms are often used for optimization problems like minimum spanning trees,
Huffman coding, and interval scheduling. However, they may not always guarantee the globally
optimal solution. It's crucial to demonstrate that the greedy choice is safe and leads to an optimal
solution.

PROGRAM:
i) GREEDY TECHNIQUES:
activities = [
["A1", 0, 6],
["A2", 3, 4],
["A3", 1, 2],
["A4", 5, 8],
["A5", 5, 7],
["A6", 8, 9]
]

# Function to solve the Activity Selection Problem


def printMaxActivities(activities):
activities.sort(key=lambda x: x[2])
i=0
firstA = activities[i][0]
print(firstA)

for j in range(len(activities)):
if activities[j][1] >= activities[i][2]:
print(activities[j][0])
i=j

21

Downloaded by nncecse 2017 ([email protected])


lOMoARcPSD|30021711

printMaxActivities(activities)

OUTPUT:

A3
A2
A5
A6

ii) DIJISTRA’S ALGORITHM:


‘’’Python program for Dijkstra's single source shortest path algorithm. The program is for
adjacency matrix representation of the program’’’

class Graph:
def __init__(self, vertices):
self.V = vertices
self.graph = [[0 for column in range(vertices)] for row in range(vertices)]

def printSolution(self, dist):


print("Vertex \t Distance from Source")
for node in range(self.V):
print(node, "\t\t", dist[node])

def minDistance(self, dist, sptSet):


min_val = float('inf')
min_index = -1

for v in range(self.V):
if dist[v] < min_val and not sptSet[v]:
min_val = dist[v]
min_index = v

return min_index

def dijkstra(self, src):


dist = [float('inf')] * self.V
dist[src] = 0
sptSet = [False] * self.V

for _ in range(self.V):
u = self.minDistance(dist, sptSet)
sptSet[u] = True

for v in range(self.V):
if (
not sptSet[v]
22

Downloaded by nncecse 2017 ([email protected])


lOMoARcPSD|30021711

and self.graph[u][v] > 0


and dist[v] > dist[u] + self.graph[u][v]
):
dist[v] = dist[u] + self.graph[u][v]

self.printSolution(dist)

# Driver program
g = Graph(9)

g.graph = [
[0, 4, 0, 0, 0, 0, 0, 8, 0],
[4, 0, 8, 0, 0, 0, 0, 11, 0],
[0, 8, 0, 7, 0, 4, 0, 0, 2],
[0, 0, 7, 0, 9, 14, 0, 0, 0],
[0, 0, 0, 9, 0, 10, 0, 0, 0],
[0, 0, 4, 14, 10, 0, 2, 0, 0],
[0, 0, 0, 0, 0, 2, 0, 1, 6],
[8, 11, 0, 0, 0, 0, 1, 0, 7],
[0, 0, 2, 0, 0, 0, 6, 7, 0],
]

g.dijkstra(0)

OUTPUT:

Vertex Distance from Source


0 0
1 4
2 12
3 19
4 21
5 11
6 9
7 8
8 14

iii) HUFFMAN TREES AND CODES:


from collections import Counter
class NodeTree(object):
def __init__(self, left=None, right=None):
self.left = left
self.right = right

def children(self):
return self.left, self.right
23

Downloaded by nncecse 2017 ([email protected])


lOMoARcPSD|30021711

def __str__(self):
return f'{self.left}, {self.right}'
def huffman_code_tree(node, binString=''):

'''Function to find Huffman Code'''

if type(node) is str:
return {node: binString}

(l, r) = node.children()
d = dict()
d.update(huffman_code_tree(l, binString + '0'))
d.update(huffman_code_tree(r, binString + '1'))
return d

def make_tree(nodes):

‘’’Function to make a tree


:param nodes: Nodes
:return: Root of the tree’’’

while len(nodes) > 1:


(key1, c1) = nodes[-1]
(key2, c2) = nodes[-2]
nodes = nodes[:-2]
node = NodeTree(key1, key2)
nodes.append((node, c1 + c2))
nodes = sorted(nodes, key=lambda x: x[1], reverse=True)
return nodes[0][0]

if __name__ == '__main__':
string = 'BCAADDDCCACACAC'
freq = dict(Counter(string))
freq = sorted(freq.items(), key=lambda x: x[1], reverse=True)
node = make_tree(freq)
encoding = huffman_code_tree(node)

for char, code in encoding.items():


print(f'{char} : {code}')

24

Downloaded by nncecse 2017 ([email protected])


lOMoARcPSD|30021711

OUTPUT:

C:0
B : 100
D : 101
A : 11

RESULT:
Thus the programs have been executed using Greedy Techniques- Dijkstra’s
Algorithm, Huffman Trees and Codes.

25

Downloaded by nncecse 2017 ([email protected])


lOMoARcPSD|30021711

EX. NO. : 07
ITERATIVE IMPROVEMENT - SIMPLEX METHOD
DATE :

AIM:
To implement Iterative Improvement - Simplex Method to find optimal solution.

ALGORITHM FOR ITERATIVE IMPROVEMENT:

Step 1: Start with an initial solution or guess.


Step 2: Measure the quality of the current solution using a predefined metric or objective
function.
Iterate:
• Make small changes to the solution.
• Evaluate the new solution.
• If the new solution is better, accept it as the current solution; otherwise, discard the
changes.
• Repeat steps a-c until a stopping criterion is met (e.g., a certain number of iterations or
reaching a satisfactory solution).

This process aims to incrementally enhance the solution by iteratively making adjustments
and evaluating their impact. It's commonly used in optimization problems or situations where
finding the best solution involves refining an initial guess.

PROGRAM:

import numpy as np

def print_table(table, iteration, basis, z_value):


print(f"Iteration: {iteration}")
print("B\tCB\tXB\ty1\ty2\ty3\ty4")
for row in table:
print("\t".join(map(str, row)))
print(f"rel profit: {table[-1, 1:-1].tolist()},\n")
print(f"value of Z at optimality: {z_value}")
print(f"Final Basis: {basis}")
print("Simplex Finished...\n")

def simplex_algorithm(table):
m, n = table.shape
iteration = 0
basis = []

while True:
iteration += 1

26

Downloaded by nncecse 2017 ([email protected])


lOMoARcPSD|30021711

print_table(table, iteration, basis, table[-1, -1])


# Check for optimality
if np.all(table[-1, 1:-1] <= 0):
print("All profits are <= 0, optimality reached")
break

# Find the pivot column (most negative coefficient)


pivot_col = np.argmin(table[-1, 1:-1]) + 1

# Find the pivot row (minimum ratio)


ratios = table[:-1, -1] / table[:-1, pivot_col]
pivot_row = np.argmin(ratios)

basis.append(f'x{pivot_col}')
pivot_element = table[pivot_row, pivot_col]

# Perform pivot operation


table[pivot_row, :] /= pivot_element
for i in range(m):
if i != pivot_row:
table[i, :] -= table[i, pivot_col] * table[pivot_row, :]

return basis

# Example input (initial tableau)


tableau = np.array([
[0, 12, -1, 1, 1, 0, 0],
[0, 30, 1, 1, 0, 1, 0],
[0, 90, 2, 5, 0, 0, 1],
[0, 0, 0, 0, 0, 0, 0],
[0, 0, -4, -6, 0, 0, 0]
], dtype=float)

basis = simplex_algorithm(tableau)

27

Downloaded by nncecse 2017 ([email protected])


lOMoARcPSD|30021711

OUTPUT:
Iteration: 1
B CB XB y1 y2 y3 y4
0.0 12.0 -1.0 1.0 1.0 0.0 0.0
0.0 30.0 1.0 1.0 0.0 1.0 0.0
0.0 90.0 2.0 5.0 0.0 0.0 1.0
0.0 0.0 0.0 0.0 0.0 0.0 0.0
0.0 0.0 -4.0 -6.0 0.0 0.0 0.0

rel profit: [0.0, -4.0, -6.0, 0.0, 0.0],

value of Z at optimality: 0.0


Final Basis: [ ]
Simplex Finished...

All profits are <= 0, optimality reached

RESULT:
Thus the program has been executed using Iterative Improvement - Simplex
Method.

28

Downloaded by nncecse 2017 ([email protected])


lOMoARcPSD|30021711

EX. NO. : 08 BACKTRACKING – N-QUEEN PROBLEM, SUBSET SUM


DATE : PROBLEM

AIM:
To implement N-Queen Problem and Subset Sum Problem using Backtracking
method.

ALGORITHM FOR BACKTRACKING:

Step 1: Define the Problem: Clearly define the problem and establish the constraints.
Step 2: Create a Solution Space: Identify a representation for potential solutions
and create a solution space.
Step 3: Initialize: Set up an initial solution or starting point.
Step 4: Explore: Systematically explore the solution space. At each step, make a choice
and move forward.
Step 5: Constraint Check: At each choice, check if it satisfies the problem constraints.
If not, backtrack and explore other options.
Step 6: Terminate or Output: If a solution is found, output it. Otherwise, backtrack
until all possibilities are exhausted.
Step 7: Recursive Structure: Implement the backtracking algorithm using recursion.
The recursive calls represent different choices in the solution space.
❖ Generic example in pseudocode:
function backtrack(candidate):
if candidate is a solution:
output(candidate)
return

for next_candidate in generate_candidates(candidate):


if is_valid(next_candidate):
make_choice(next_candidate)
backtrack(next_candidate)
undo_choice(next_candidate)

❖ This pseudocode can be adapted and extended based on the specific problem you're
solving.
❖ Remember to implement the functions is_valid, generate_candidates, make_choice,
and undo_choice according to the requirements of your problem.
❖ For a concrete example, you might want to explore the N-Queens problem using
backtracking.

29

Downloaded by nncecse 2017 ([email protected])


lOMoARcPSD|30021711

PROGRAM:
i) N-QUEEN PROBLEM

def is_attack(i, j, board, N):


# Checking for column j
for k in range(1, i):
if board[k][j] == 1:
return True

# Checking upper right diagonal


k, l = i - 1, j + 1
while k >= 1 and l <= N:
if board[k][l] == 1:
return True
k -= 1
l += 1

# Checking upper left diagonal


k, l = i - 1, j - 1
while k >= 1 and l >= 1:
if board[k][l] == 1:
return True
k -= 1
l -= 1
return False

def n_queen(row, n, N, board):


if n == 0:
return True

for j in range(1, N + 1):


if not is_attack(row, j, board, N):
board[row][j] = 1
if n_queen(row + 1, n - 1, N, board):
return True
board[row][j] = 0 # Backtracking
return False

30

Downloaded by nncecse 2017 ([email protected])


lOMoARcPSD|30021711

if __name__ == '__main__':
N = 4 # Change N as needed
board = [[0] * (N + 1) for _ in range(N + 1)]
if n_queen(1, N, N, board):
for i in range(1, N + 1):
print(board[i][1:])
else:
print("No solution exists.")

OUTPUT:

[0, 1, 0, 0]
[0, 0, 0, 1]
[1, 0, 0, 0]
[0, 0, 1, 0]

ii) A RECURSIVE SOLUTION FOR SUBSET SUM PROBLEM


#Returns true if there is a subset of set[] with a sum equal to the given sum
def isSubsetSum(arr, n, target_sum):
# Base Cases
if target_sum == 0:
return True
if n == 0 and target_sum != 0:
return False

# If the last element is greater than the sum, ignore it


if arr[n - 1] > target_sum:
return isSubsetSum(arr, n - 1, target_sum)

# Check if the sum can be obtained by either including or excluding the last element.
return isSubsetSum(arr, n - 1, target_sum) or isSubsetSum(arr, n - 1, target_sum - arr[n - 1])

# Driver program to test the function


arr = [3, 34, 4, 12, 5, 2]
target_sum = 9
n = len(arr)

if isSubsetSum(arr, n, target_sum):
print("Found a subset with the given sum")
else:
print("No subset with the given sum")

31

Downloaded by nncecse 2017 ([email protected])


lOMoARcPSD|30021711

OUTPUT:
Found a subset with given sum

RESULT:

Thus the programs have been executed using Backtracking – N-Queen Problem
and Subset Sum Problem.

32

Downloaded by nncecse 2017 ([email protected])


lOMoARcPSD|30021711

EX. NO. : 09 BRANCH AND BOUND - TRAVELING SALESMAN


DATE : PROBLEM

AIM:
To implement Traveling Salesman Problem using Branch and Bound to find
shortest path in minimum cost.

ALGORITHEM FOR BRANCH AND BOUND:

Step 1: Initialize an upper bound (UB) to positive infinity.


Step 2: Create an empty priority queue (or a data structure to manage the search space).
Step 3: Create a node representing the initial state of the problem.
Step 4: Calculate its bound (an estimate of the best possible solution achievable from this node).

Step 5: While the priority queue is not empty:


❖ Dequeue the node with the lowest bound.
❖ If the node is a leaf node, update the UB if a better solution is found.
❖ If the node is not a leaf node:
• Create child nodes by dividing the problem into subproblems.
• Calculate bounds for each child node.
• Enqueue valid child nodes into the priority queue.

Step 6: Stop when the priority queue is empty or the problem space has been fully explored.

➢ The key idea is to systematically explore the solution space, pruning branches that cannot
lead to an optimal solution based on calculated bounds.
➢ This helps reduce the search space, making the algorithm more efficient.
➢ It's important to note that the specific implementation details can vary based on the
nature of the optimization problem being solved.
➢ The success of the branch and bound algorithm often relies on the efficiency of the
bounding function used to estimate the quality of partial solutions.

PROGRAM:

import math
maxsize = float('inf')

def copyToFinal(curr_path):
final_path[:N + 1] = curr_path[:]
final_path[N] = curr_path[0]

def firstMin(adj, i):


min_val = maxsize
for k in range(N):

33

Downloaded by nncecse 2017 ([email protected])


lOMoARcPSD|30021711

if adj[i][k] < min_val and i != k:


min_val = adj[i][k]
return min_val

def secondMin(adj, i):


first, second = maxsize, maxsize
for j in range(N):
if i == j:
continue
if adj[i][j] <= first:
second = first
first = adj[i][j]
elif adj[i][j] <= second and adj[i][j] != first:
second = adj[i][j]
return second

def TSPRec(adj, curr_bound, curr_weight, level, curr_path, visited):


global final_res

if level == N:
if adj[curr_path[level - 1]][curr_path[0]] != 0:
curr_res = curr_weight + adj[curr_path[level - 1]][curr_path[0]]
if curr_res < final_res:
copyToFinal(curr_path)
final_res = curr_res
return

for i in range(N):
if adj[curr_path[level - 1]][i] != 0 and not visited[i]:
temp = curr_bound
curr_weight += adj[curr_path[level - 1]][i]

if level == 1:
curr_bound -= (firstMin(adj, curr_path[level - 1]) + firstMin(adj, i)) / 2
else:
curr_bound -= (secondMin(adj, curr_path[level - 1]) + firstMin(adj, i)) / 2

if curr_bound + curr_weight < final_res:


curr_path[level] = i
visited[i] = True
TSPRec(adj, curr_bound, curr_weight, level + 1, curr_path, visited)
curr_weight -= adj[curr_path[level - 1]][i]
curr_bound = temp
visited = [False] * len(visited)
for j in range(level):
if curr_path[j] != -1:
visited[curr_path[j]] = True
def TSP(adj):
34

Downloaded by nncecse 2017 ([email protected])


lOMoARcPSD|30021711

global final_path
curr_bound = 0
curr_path = [-1] * (N + 1)
visited = [False] * N

for i in range(N):
curr_bound += firstMin(adj, i) + secondMin(adj, i)
curr_bound = math.ceil(curr_bound / 2)
visited[0] = True
curr_path[0] = 0
TSPRec(adj, curr_bound, 0, 1, curr_path, visited)

# Driver code
adj = [[0, 10, 15, 20],
[10, 0, 35, 25],
[15, 35, 0, 30],
[20, 25, 30, 0]]
N=4
final_path = [None] * (N + 1)
final_res = maxsize
TSP(adj)
print("Minimum cost:", final_res)
print("Path Taken:", end=' ')
for i in range(N + 1):
print(final_path[i], end=' ')

OUTPUT:

Minimum cost : 80
Path Taken : 0 1 3 2 0

RESULT:

Thus the programs have been executed using Branch and Bound - Traveling
Salesman Problem.

35

Downloaded by nncecse 2017 ([email protected])

You might also like