0% found this document useful (0 votes)
317 views42 pages

Lab Manual

The document describes experiments on algorithms using different problem solving techniques. Experiment 1 implements recursive and non-recursive algorithms to calculate factorials and study computational complexity. Experiment 2 uses the divide and conquer approach to implement Strassen's matrix multiplication algorithm. Experiment 3 applies the decrease and conquer technique to implement topological sorting of graphs. Experiment 4 transforms an unsorted array into a binary heap and then conquers it to sort the array, demonstrating the transform and conquer approach through heap sort.

Uploaded by

Shamilie M
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOC, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
317 views42 pages

Lab Manual

The document describes experiments on algorithms using different problem solving techniques. Experiment 1 implements recursive and non-recursive algorithms to calculate factorials and study computational complexity. Experiment 2 uses the divide and conquer approach to implement Strassen's matrix multiplication algorithm. Experiment 3 applies the decrease and conquer technique to implement topological sorting of graphs. Experiment 4 transforms an unsorted array into a binary heap and then conquers it to sort the array, demonstrating the transform and conquer approach through heap sort.

Uploaded by

Shamilie M
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOC, PDF, TXT or read online on Scribd
You are on page 1/ 42

VARUVAN VADIVELAN INSTITUTE OF TECHNOLOGY

DHARMAPURI – 636701

DEPARTMENT OF INFORMATION TECHNOLOGY

AI&DS-AD3301

DESIGN AND ANALYSIS OF ALORITHM LABORATORY


LIST OF EXPERIMENTS

PAGE
S.NO NAME OF THE EXPERIMENTS MARKS SIGNATURE
NO

Implement recursive and non-recursive


1 algorithms and study the order of
growth from log2n to n!.
Divide and Conquer - Strassen’s Matrix
2 Multiplication

3 Decrease and Conquer - Topological


Sorting

4 Transform and Conquer - Heap Sort

Dynamic programming –
(a)Coin change Problem,
5 (b) Warshall’s
(c)Floyd‘s algorithms,
(d) Knapsack Problem

Greedy Technique –
6
(a)Dijkstra’s algorithm,
(b)Huffman Trees and codes

7 Iterative improvement - Simplex


Method

Backtracking –
8 (a)N-Queen problem,
(b)Subset Sum Problem

Branch and Bound –


9 (a)Assignment problem,
(b)Traveling Salesman Problem
EX NO:1(a)
DATE: RECURSIVE

AIM:
Implement recursive algorithms and study the order of growth from log2n to n!

PROCEDURE:

A function is said to be recursive if it keeps calling itself until it reaches the base case.
Any recursive function has two primary components: the base case and the recursive
step. We stop going to the recursive phase once we reach the basic case.

This is one of the most interesting Algorithms as it calls itself with a smaller value as
inputs which it gets after solving for the current inputs. In simpler words, it’s an
Algorithm that calls itself repeatedly until the problem is solved. Problems such as the
Tower of Hanoi or DFS of a Graph can be easily solved by using these Algorithms.

PROGRAM:

import math.
# Recursive function to calculate n!
def factorial_recursive(n):
if n <= 1:
return 1
else:
return n * factorial_recursive(n - 1)
# Calculate the order of growth from log2(n) to n!
def calculate_growth_order_recursive(n):
log_n = math.log2(n)
factorial_n = factorial_recursive(n)
return log_n, factorial_n
# Example usage:
n = 10 # You can change the value of n as needed
log_n, factorial_n = calculate_growth_order_recursive(n)
print(f"log2({n}) = {log_n}")
print(f"{n}! = {factorial_n}")

OUTPUT:
log2(12) = 3.584962500721156
12! = 479001600

RESULT:
EX NO: 1(b)
DATE: NON-RECURSIVE

Aim:
Implement non-recursive algorithms and study the order of growth from log2n
to n!

PROCEDURE:

A non-recursive algorithm does the sorting all at once, without calling itself.
Bubble-sort is an example of a non-recursive algorithm.

PROGRAM:
import math
# Non-recursive function to calculate n!
def factorial_non_recursive(n):
result = 1
for i in range(1, n + 1):
result *= i
return result

# Calculate the order of growth from log2(n) to n!


def calculate_growth_order_non_recursive(n):
log_n = math.log2(n)
factorial_n = factorial_non_recursive(n)
return log_n, factorial_n

# Example usage:
n = 10 # You can change the value of n as needed
log_n, factorial_n = calculate_growth_order_non_recursive(n)
print(f"log2({n}) = {log_n}")
print(f"{n}! = {factorial_n}")

OUTPUT:

log2(20) = 4.321928094887363
20! = 2432902008176640000
RESULT:

EX NO: 2
DATE: DIVIDE AND CONQUER
STRASSEN’S MATRIX MULTIPLICATION

AIM:

To implement a - Strassen’s matrix multiplication using divide and conquer


method.

PROCEDURE:
Divide and Conquer: This is another effective way of solving many problems.
In Divide and Conquer algorithms, divide the algorithm into two parts; the first parts
divide the problem on hand into smaller sub problems of the same type.
Then, in the second part, these smaller problems are solved and then added together
(combined) to produce the problem’s final solution.
Problems such as Binary Search, Quick Sort, and Merge Sort can be solved using this
technique.
Strassen's algorithm is an algorithm for matrix multiplication that was devised by
Volker Strassen in 1969. It is a divide-and-conquer algorithm that can multiply two
matrices in a more efficient way compared to the standard matrix multiplication
algorithm, especially for large matrices. The standard matrix multiplication has a time
complexity of O(n^3), where n is the dimension of the matrices, while Strassen's
algorithm has a slightly better time complexity of approximately O(n^2.81).

The basic idea behind Strassen's algorithm is to break down the matrix
multiplication into a set of subproblems and then combine the results in a way that
requires fewer multiplications than the standard algorithm
PROGRAM:
import numpy as np

def strassen_multiply(A, B):


n = len(A)

# Base case: if the matrices are 1x1, just multiply the elements
if n == 1:
return np.array([[A[0, 0] * B[0, 0]]])

# Split matrices into quadrants


A11, A12, A21, A22 = A[:n//2, :n//2], A[:n//2, n//2:], A[n//2:, :n//2], A[n//2:, n//2:]
B11, B12, B21, B22 = B[:n//2, :n//2], B[:n//2, n//2:], B[n//2:, :n//2], B[n//2:, n//2:]

# Calculate the seven products as defined by the Strassen algorithm


P1 = strassen_multiply(A11 + A22, B11 + B22)
P2 = strassen_multiply(A21 + A22, B11)
P3 = strassen_multiply(A11, B12 - B22)
P4 = strassen_multiply(A22, B21 - B11)
P5 = strassen_multiply(A11 + A12, B22)
P6 = strassen_multiply(A21 - A11, B11 + B12)
P7 = strassen_multiply(A12 - A22, B21 + B22)

# Calculate the four quadrants of the result matrix


C11 = P1 + P4 - P5 + P7
C12 = P3 + P5
C21 = P2 + P4
C22 = P1 - P2 + P3 + P6

# Combine the four quadrants to get the result matrix


result = np.vstack((np.hstack((C11, C12)), np.hstack((C21, C22))))

return result

# Example usage:
A = np.array([[1, 2], [3, 4]])
B = np.array([[5, 6], [7, 8]])
result = strassen_multiply(A, B)
print(result)

OUTPUT:
[[19 22]
[43 50]]

RESULT:

EX NO:3
DATE: DECREASE AND CONQUER
AIM:
To implement a Topological Sorting using Decrease and Conquer -
PROCEDURE:
"Decrease and Conquer" is a problem-solving paradigm that involves breaking
down a problem into smaller subproblems, solving the subproblems, and then
combining their solutions to solve the original problem. In the context of topological
sorting, a common way to apply "Decrease and Conquer" is through the removal of
vertices with no incoming edges.

PROGRAM:
from collections import defaultdict
class Graph:
def __init__(self, vertices):
self.graph = defaultdict(list)
self.V = vertices
def addEdge(self, u, v):
self.graph[u].append(v)
def topologicalSortUtil(self, v, visited, stack):
visited[v] = True
for i in self.graph[v]:
if not visited[i]:
self.topologicalSortUtil(i, visited, stack)
stack.insert(0, v)
def topologicalSort(self):
visited = [False] * self.V
stack = []
for i in range(self.V):
if not visited[i]:
self.topologicalSortUtil(i, visited, stack)
return stack
# Example usage:
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)
topological_order = g.topologicalSort()
print("Topological Sort:")
print(topological_order)

OUTPUT:

Topological Sort:
[5, 4, 2, 3, 1, 0]
RESULT:

EX NO:4
DATE: TRANSFORM AND CONQUER - HEAP SORT
AIM:
To implement a Heap Sorting using Transform Conquer
PROCEDURE:
"Transform and Conquer" is a problem-solving paradigm that involves
transforming the input instance of a problem into a different representation and then
conquering the transformed instance. While "Transform and Conquer" is not a standard
term used specifically for Heap Sort, you can view Heap Sort through the lens of this
paradigm.

In the case of Heap Sort:

1. Transform:
 Convert the unsorted array into a binary heap.
 The binary heap is a data structure that satisfies the heap property (either
max-heap or min-heap).
 This transformation is achieved by viewing the array as a complete binary
tree, and then heapifying the tree.
2. Conquer:
 Perform the heap sort by repeatedly extracting the maximum (for max-
heap) or minimum (for min-heap) element from the heap.
 The extraction involves swapping the root of the heap (which contains the
maximum or minimum element) with the last element in the heap,
reducing the heap size, and then heapifying to restore the heap property.
In this view, the transformation step involves building a max heap from the unsorted
array, and the conquering step involves repeatedly extracting the maximum element
from the heap to obtain a sorted array.

Heap Sort, in general, is an in-place sorting algorithm with a time complexity of O(n log
n) for all cases (best, average, and worst-case scenarios), making it a suitable choice for
sorting large datasets.

PROGRAM :
def heapify(arr, n, i):
largest = i
left = 2 * i + 1
right = 2 * i + 2
if left < n and arr[i] < arr[left]:
largest = left
if right < n and arr[largest] < arr[right]:
largest = right
if largest != i:
arr[i], arr[largest] = arr[largest], arr[i]
heapify(arr, n, largest)
def heap_sort(arr):
n = len(arr)
for i in range(n // 2 - 1, -1, -1):
heapify(arr, n, i)
for i in range(n - 1, 0, -1):
arr[i], arr[0] = arr[0], arr[i]
heapify(arr, i, 0)
if __name__ == "__main__":
unsorted_list = [12, 11, 13, 5, 6, 7]
print("Unsorted list:", unsorted_list)
heap_sort(unsorted_list)
print("Sorted list:", unsorted_list)

OUTPUT:

Unsorted list: [12, 11, 13, 5, 6, 7]


Sorted list: [5, 6, 7, 11, 12, 13]

RESULT:
EX NO:5
DATE: DYNAMIC PROGRAM – (a) COIN CHANGE PROBLEM
AIM:
To implement a Coin change problem using Dynamic algorithm
PROCEDURE:
Dynamic programming is a method for efficiently solving a broad range of search
and optimization problems which exhibit the property of overlapping subproblems and
optimal substructure. It is often used when a problem has a recursive structure, and the
solutions to the same subproblems are needed multiple times. Dynamic programming
avoids redundant computations by storing the results of intermediate computations
and reusing them when needed.
The coin change problem is a classic problem in dynamic programming. Given a set
of coin denominations and a target amount, the task is to find the number of ways to
make change for the target amount using any combination of the given coins

PROGRAM:
def count_ways_to_make_change(coins, amount):
# Create a table to store the number of ways to make each amount
# Initialize all values to 0
dp = [0] * (amount + 1)

# There's one way to make change for an amount of 0 (no coins)


dp[0] = 1
# Iterate through each coin denomination
for coin in coins:
# Update the table for all amounts from 'coin' to 'amount'
for i in range(coin, amount + 1):
dp[i] += dp[i - coin]

# The final value in dp[amount] represents the number of ways to make change
for 'amount'
return dp[amount]

if __name__ == "__main__":
coin_denominations = [1, 2, 5]
target_amount = 5
ways = count_ways_to_make_change(coin_denominations, target_amount)
print(f"Number of ways to make change for {target_amount} is {ways}")

OUTPUT:

Number of ways to make change for 5 is 4


RESULT:

EX NO:5
DATE: DYNAMIC PROGRAM – (b) WARSHALL PROBLEM
AIM:
To implement a Warshall problem using Dynamic algorithm
PROCEDURE:
The Floyd-Warshall algorithm is a dynamic programming algorithm used for finding
the shortest paths between all pairs of vertices in a weighted graph, which may contain
negative weight edges. The algorithm was proposed by Robert W. Floyd and Stephen
Warshall independently.
PROGRAM:
def floyd_warshall(graph):
# Number of vertices in the graph
num_vertices = len(graph)

# Initialize the distance matrix with the graph


dist = [[float('inf')] * num_vertices for _ in range(num_vertices)]
for i in range(num_vertices):
for j in range(num_vertices):
if i == j:
dist[i][j] = 0
elif graph[i][j] != 0:
dist[i][j] = graph[i][j]

# Update the distance matrix using the Floyd-Warshall algorithm


for k in range(num_vertices):
for i in range(num_vertices):
for j in range(num_vertices):
if dist[i][j] > dist[i][k] + dist[k][j]:
dist[i][j] = dist[i][k] + dist[k][j]

return dist

if __name__ == "__main__":
# Example graph represented as an adjacency matrix
graph = [
[0, 1, 0, 0],
[0, 0, 1, 0],
[0, 0, 0, 1],
[1, 0, 0, 0]
]

# Find the transitive closure using Warshall's algorithm


transitive_closure = floyd_warshall(graph)

# Print the transitive closure matrix


print("Transitive Closure:")
for row in transitive_closure:
print(row)

OUTPUT:

Transitive Closure:
[0, 1, 2, 3]
[3, 0, 1, 2]
[2, 3, 0, 1]
[1, 2, 3, 0]
RESULT:

EX NO:5
DATE: DYNAMIC PROGRAM – (c) FLOYD’S PROBLEM
AIM:
To implement a Floyd’s problem using Dynamic algorithm
PROCEDURE:
The Floyd-Warshall algorithm is a dynamic programming algorithm used for
finding the shortest paths between all pairs of vertices in a weighted graph, which may
contain negative weight edges. The algorithm was proposed by Robert W. Floyd and
Stephen Warshall independently.
PROGRAM:
def floyd_warshall(graph):
num_vertices = len(graph)
# Initialize the distance matrix with the graph's adjacency matrix
dist = [row[:] for row in graph]
for k in range(num_vertices):
for i in range(num_vertices):
for j in range(num_vertices):
# If there is a shorter path from i to j through vertex k
if dist[i][j] > dist[i][k] + dist[k][j]:
dist[i][j] = dist[i][k] + dist[k][j]
return dist
if __name__ == "__main__":
# Example graph represented as an adjacency matrix
graph = [
[0, 5, float('inf'), 10],
[float('inf'), 0, 3, float('inf')],
[float('inf'), float('inf'), 0, 1],
[float('inf'), float('inf'), float('inf'), 0]
]
# Find the shortest paths using Floyd's algorithm
shortest_paths = floyd_warshall(graph)
# Print the shortest paths matrix
print("Shortest Paths:")
for row in shortest_paths:
print([f if f != float('inf') else "∞" for f in row])

OUTPUT:
Shortest Paths:
[0, 5, 8, 9]
['∞', 0, 3, 4]
['∞', '∞', 0, 1]
['∞', '∞', '∞', 0]

RESULT:

EX NO:5
DATE: DYNAMIC PROGRAM – (d) KNAPSACK PROBLEM
AIM:
To implement a Knapsack problem using Dynamic algorithm

PROCEDURE:

Given a set of items, each with a weight and a value, determine the maximum value
that can be obtained by selecting a subset of the items, such that the sum of the weights
of the selected items does not exceed a given capacity.

In other words, you have a knapsack with a limited weight capacity, and you want to
maximize the total value of the items you can carry in the knapsack without exceeding
its capacity.

PROGRAM:
def knapsack(items, capacity):
n = len(items)
# Initialize a table to store the maximum values for different capacities
dp = [[0] * (capacity + 1) for _ in range(n + 1)]

for i in range(n + 1):


for w in range(capacity + 1):
if i == 0 or w == 0:
dp[i][w] = 0
elif items[i - 1][1] <= w:
dp[i][w] = max(dp[i - 1][w], items[i - 1][0] + dp[i - 1][w - items[i - 1][1]])
else:
dp[i][w] = dp[i - 1][w]

# The result is stored in dp[n][capacity]


return dp[n][capacity]

if __name__ == "__main__":
items = [(60, 10), (100, 20), (120, 30)]
capacity = 50

max_value = knapsack(items, capacity)


print("Maximum value that can be obtained:", max_value)

OUTPUT:

Maximum value that can be obtained: 220


RESULT:

EX NO:6
DATE: GREEDY TECHNIQUE – (a) DIJKSTRA’S PROGRAM
AIM:
To implement a Dijkstra’s Program using Greedy Technique
PROCEDURE:
A greedy algorithm is an algorithmic paradigm that makes locally optimal choices
at each stage with the hope of finding a global optimum. In other words, it chooses the
best option at each step without worrying about the consequences in the future. The
strategy is to make the locally optimal choice at each stage, hoping that this will lead to
a globally optimal solution.

Greedy algorithms are often simple and easy to implement. They are particularly useful
for optimization problems where making a series of locally optimal choices leads to an
overall optimal solution. However, the method does not always guarantee a globally
optimal solution for every problem.

 Huffman Coding: Constructing an optimal binary prefix code for a set of


characters based on their frequencies.
 Dijkstra's Algorithm: Finding the shortest path in a graph from a source vertex
to all other vertices, assuming non-negative weights on the edges.
PROGRAM:
import heapq

def dijkstra(graph, start):


# Create a dictionary to store the shortest distances from the start node
distances = {node: float('infinity') for node in graph}
distances[start] = 0

# Create a priority queue to keep track of nodes with their tentative distances
priority_queue = [(0, start)]

while priority_queue:
# Get the node with the smallest tentative distance
current_distance, current_node = heapq.heappop(priority_queue)

# If the current distance is larger than the recorded distance, skip it


if current_distance > distances[current_node]:
continue

# Update the distances to neighboring nodes


for neighbor, weight in graph[current_node].items():
distance = current_distance + weight

# If this path is shorter than the recorded distance, update the distance
if distance < distances[neighbor]:
distances[neighbor] = distance
heapq.heappush(priority_queue, (distance, neighbor))

return distances

# Example usage:
graph = {
'A': {'B': 1, 'C': 4},
'B': {'A': 1, 'C': 2, 'D': 5},
'C': {'A': 4, 'B': 2, 'D': 1},
'D': {'B': 5, 'C': 1}
}

start_node = 'A'
shortest_distances = dijkstra(graph, start_node)

print("Shortest distances from", start_node, "to all other nodes:")


for node, distance in shortest_distances.items():
print(node, ":", distance)
OUTPUT:
Shortest distances from A to all other nodes:
A:0
B:1
C:3
D:4

RESULT:

EX NO:6
DATE: GREEDY TECHNIQUE – (b) HUFFMAN TREE AND CODE
AIM:
To implement a Huffman Tree and code using Greedy algorithm

PROGRAM :

import heapq
from collections import defaultdict

class Node:
def __init__(self, char, frequency):
self.char = char
self.frequency = frequency
self.left = None
self.right = None

def __lt__(self, other):


return self.frequency < other.frequency

def build_huffman_tree(data):
char_frequency = defaultdict(int)
for char in data:
char_frequency[char] += 1

heap = [Node(char, freq) for char, freq in char_frequency.items()]


heapq.heapify(heap)

while len(heap) > 1:


left = heapq.heappop(heap)
right = heapq.heappop(heap)
merged_node = Node(None, left.frequency + right.frequency)
merged_node.left = left
merged_node.right = right
heapq.heappush(heap, merged_node)

return heap[0]

def build_huffman_codes(root, current_code, huffman_codes):


if root is None:
return

if root.char is not None:


huffman_codes[root.char] = current_code
return

build_huffman_codes(root.left, current_code + '0', huffman_codes)


build_huffman_codes(root.right, current_code + '1', huffman_codes)

def huffman_encoding(data):
if not data:
return '', None

root = build_huffman_tree(data)
huffman_codes = {}
build_huffman_codes(root, '', huffman_codes)

encoded_data = ''.join(huffman_codes[char] for char in data)


return encoded_data, root

def huffman_decoding(encoded_data, root):


if not encoded_data:
return ''

current_node = root
decoded_data = ''
for bit in encoded_data:
if bit == '0':
current_node = current_node.left
else:
current_node = current_node.right

if current_node.char is not None:


decoded_data += current_node.char
current_node = root

return decoded_data

if __name__ == "__main__":
data = "hello, world!"

encoded_data, tree = huffman_encoding(data)


print("Encoded data:", encoded_data)

decoded_data = huffman_decoding(encoded_data, tree)


print("Decoded data:", decoded_data)

OUTPUT:

Encoded data: 000001010111010111111111011010000110011010


Decoded data: hello, world!

RESULT:
EX NO:7
DATE: ITREATIVE METHOD – SIMPLEX METHOD
AIM:
To implement a Simplex method using Iterative method
PROCEDURE:
The simplex method is an iterative optimization algorithm used for solving linear
programming problems. It is a popular method for finding the optimal solution to
problems

PROGRAM:
import numpy as np
def simplex_method(c, A, b):
m, n = A.shape
c = np.array(c)
A = np.array(A)
b = np.array(b)
# Initial tableau
tableau = np.zeros((m + 1, n + 1))
tableau[:m, :n] = A
tableau[:m, -1] = b
tableau[-1, :-1] = -c
while np.any(tableau[-1, :-1] < 0):
pivot_column = np.argmin(tableau[-1, :-1])
ratios = tableau[:m, -1] / tableau[:m, pivot_column]
pivot_row = np.argmin(ratios)
pivot_element = tableau[pivot_row, pivot_column]
# Perform pivot operation
tableau[pivot_row, :] /= pivot_element
for i in range(m + 1):
if i == pivot_row:
continue
tableau[i, :] -= tableau[i, pivot_column] * tableau[pivot_row, :]
optimal_solution = tableau[-1, -1]
solution = dict()
for i in range(n):
non_zero_indices = np.nonzero(tableau[:m, i])[0]
if len(non_zero_indices) == 1:
solution[i] = tableau[non_zero_indices[0], -1]
else:
solution[i] = 0
return optimal_solution, solution
# Example usage
c = [3, 2] # Coefficients of the objective function to maximize
A = np.array([[2, 1], [1, 1]]) # Coefficients of the inequality constraints
b = [4, 3] # Right-hand side of the inequality constraints
optimal_value, solution = simplex_method(c, A, b)
print("Optimal value:", optimal_value)
print("Solution:", solution)

OUTPUT:
Optimal value: 7.0
Solution: {0: 1.0, 1: 2.0}

RESULT:

EX NO:8
DATE: BACK TRACXKING METHOD – (A) 8 QUEENS PROBLEM
AIM:
To implement an 8Queens problem using Back Tracking method
PROCEDURE:
The backtracking algorithm can be described using a recursive approach. It
systematically explores the search space, and when it finds that a solution cannot be
completed, it backtracks to the last valid configuration and continues the search.
The N-Queens problem is a classic problem in computer science and
combinatorial optimization. The problem is to place N chess queens on an �×�N×N
chessboard in such a way that no two queens threaten each other. This means that no
two queens can be in the same row, column, or diagonal. Backtracking is a common
technique used to solve the N-Queens problem.

PROGRAM:
def is_safe(board, row, col):
# Check the left side of the current row
for i in range(col):
if board[row][i] == 1:
return False
# Check upper diagonal on the left side
for i, j in zip(range(row, -1, -1), range(col, -1, -1)):
if board[i][j] == 1:
return False
# Check lower diagonal on the left side
for i, j in zip(range(row, len(board), 1), range(col, -1, -1)):
if board[i][j] == 1:
return False
return True
def solve_n_queens(board, col):
# Base case: If all queens are placed successfully
if col >= len(board):
return True
for i in range(len(board)):
if is_safe(board, i, col):
# Place a queen
board[i][col] = 1
# Recur to place the rest of the queens
if solve_n_queens(board, col + 1):
return True
# If placing a queen doesn't lead to a solution, backtrack
board[i][col] = 0
# If the queen cannot be placed in any row in this column, return False
return False
def print_board(board):
for row in board:
print(" ".join("Q" if cell == 1 else "." for cell in row))
def solve_8_queens():
n = 8 # 8x8 chessboard
board = [[0 for _ in range(n)] for _ in range(n)]
if not solve_n_queens(board, 0):
print("No solution exists.")
else:
print_board(board)
if __name__ == "__main__":
solve_8_queens()

OUTPUT:

Q.......
......Q.
....Q...
.......Q
.Q......
...Q....
.....Q..
..Q.....
RESULT:

EX NO:8
DATE: BACKTRACKING – (b) SUBSET SUM PROBLEM
AIM:
To implement a Subset Sum problem using Backtracking method
PROCEDURE:
A set of positive integers and a target sum, the task is to determine whether there
exists a subset of the given set whose elements add up to the target sum.
PROGRAM:
def is_subset_sum(arr, n, target_sum, subset=[]):
if target_sum == 0:
print("Subset with the target sum found:", subset)
return True
if n == 0 and target_sum != 0:
return False
# If the last element is greater than the target sum, exclude it
if arr[n - 1] > target_sum:
return is_subset_sum(arr, n - 1, target_sum, subset)
# Consider the last element and check if a solution can be found with it
include = is_subset_sum(arr, n - 1, target_sum - arr[n - 1], subset + [arr[n - 1]])
# Exclude the last element and check if a solution can be found without it
exclude = is_subset_sum(arr, n - 1, target_sum, subset)
# Return True if either including or excluding the last element results in a solution
return include or exclude
def subset_sum(arr, target_sum):
n = len(arr)
if not is_subset_sum(arr, n, target_sum):
print("No subset with the target sum found.")
if __name__ == "__main__":
arr = [1, 3, 5, 7, 9]
target_sum = 8
subset_sum(arr, target_sum)

OUTPUT:
Subset with the target sum found: [7, 1]
Subset with the target sum found: [5, 3]
RESULT:

EX NO:9
DATE: BRANCH AND BNOUND– (a) TRAVELLING SALESPERSON PROBLEM

AIM:
To implement a Travelling Sales person Problem using Branch and Bound
method
PROCEDURE:
Branch and Bound is an algorithmic technique for solving optimization problems,
typically combinatorial optimization problems. The main idea is to divide the problem
into smaller subproblems and solve them individually, keeping track of the best solution
found so far. The technique includes bounding the search space, pruning branches that
cannot lead to better solutions, and exploring promising regions of the solution space.
Given a set of cities and the distances between each pair of cities, the task is
to find the shortest possible tour that visits each city exactly once and returns to the
original city.
PROGRAM:
# Python3 program to solve
# Traveling Salesman Problem using
# Branch and Bound.
import math
maxsize = float('inf')
# Function to copy temporary solution
# to the final solution
def copyToFinal(curr_path):
final_path[:N + 1] = curr_path[:]
final_path[N] = curr_path[0]
# Function to find the minimum edge cost
# having an end at the vertex i
def firstMin(adj, i):
min = maxsize
for k in range(N):
if adj[i][k] < min and i != k:
min = adj[i][k]
return min
# function to find the second minimum edge
# cost having an end at the vertex i
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
# function that takes as arguments:
# curr_bound -> lower bound of the root node
# curr_weight-> stores the weight of the path so far
# level-> current level while moving
# in the search space tree
# curr_path[] -> where the solution is being stored
# which would later be copied to final_path[]
def TSPRec(adj, curr_bound, curr_weight,
level, curr_path, visited):
global final_res
# base case is when we have reached level N
# which means we have covered all the nodes once
if level == N:

# check if there is an edge from


# last vertex in path back to the first vertex
if adj[curr_path[level - 1]][curr_path[0]] != 0:
# curr_res has the total weight
# of the solution we got
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 any other level iterate for all vertices
# to build the search space tree recursively
for i in range(N):
# Consider next vertex if it is not same
# (diagonal entry in adjacency matrix and
# not visited already)
if (adj[curr_path[level-1]][i] != 0 and
visited[i] == False):
temp = curr_bound
curr_weight += adj[curr_path[level - 1]][i]

# different computation of curr_bound


# for level 2 from the other levels
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)

# curr_bound + curr_weight is the actual lower bound


# for the node that we have arrived on.
# If current lower bound < final_res,
# we need to explore the node further
if curr_bound + curr_weight < final_res:
curr_path[level] = i
visited[i] = True
# call TSPRec for the next level
TSPRec(adj, curr_bound, curr_weight,
level + 1, curr_path, visited)
# Else we have to prune the node by resetting
# all changes to curr_weight and curr_bound
curr_weight -= adj[curr_path[level - 1]][i]
curr_bound = temp
# Also reset the visited array
visited = [False] * len(visited)
for j in range(level):
if curr_path[j] != -1:
visited[curr_path[j]] = True
# This function sets up final_path
def TSP(adj):
# Calculate initial lower bound for the root node
# using the formula 1/2 * (sum of first min +
# second min) for all edges. Also initialize the
# curr_path and visited array
curr_bound = 0
curr_path = [-1] * (N + 1)
visited = [False] * N
# Compute initial bound
for i in range(N):
curr_bound += (firstMin(adj, i) +
secondMin(adj, i))

# Rounding off the lower bound to an integer


curr_bound = math.ceil(curr_bound / 2)

# We start at vertex 1 so the first vertex


# in curr_path[] is 0
visited[0] = True
curr_path[0] = 0

# Call to TSPRec for curr_weight


# equal to 0 and level 1
TSPRec(adj, curr_bound, 0, 1, curr_path, visited)

# Driver code

# Adjacency matrix for the given graph


adj = [[0, 10, 15, 20],
[10, 0, 35, 25],
[15, 35, 0, 30],
[20, 25, 30, 0]]
N=4
# final_path[] stores the final solution
# i.e. the // path of the salesman.
final_path = [None] * (N + 1)
# visited[] keeps track of the already
# visited nodes in a particular path
visited = [False] * N
# Stores the final minimum weight
# of shortest tour.
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 = ' ')
# This code is contributed by ng24_7import sys

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

EX NO:9
DATE: BRANCH AND BNOUND– (b) ASSIGNMENT PROBLEM
AIM:
To implement an Assignment problem using Branch and Bound method
PROCEDURE:
The Assignment Problem is a combinatorial optimization problem that involves
finding the most cost-effective way to assign a set of tasks to a set of agents. Each task
must be assigned to exactly one agent, and each agent must be assigned exactly one
task. The goal is to minimize the total cost or maximize the total profit of the
assignment.

PROGRAM:
import sys
def assign(cost_matrix):
n = len(cost_matrix)
def subtract_min(row):
for i in range(n):
min_val = min(row)
if min_val != sys.maxsize:
for j in range(n):
if row[j] != sys.maxsize:
row[j] -= min_val
return row
def subtract_min_col(matrix):
for i in range(n):
col = [matrix[j][i] for j in range(n)]
min_val = min(val for val in col if val != sys.maxsize)
if min_val != sys.maxsize:
for j in range(n):
if matrix[j][i] != sys.maxsize:
matrix[j][i] -= min_val
return matrix
def find_zeros(matrix):
zero_positions = []
for i in range(n):
for j in range(n):
if matrix[i][j] == 0:
zero_positions.append((i, j))
return zero_positions
def assign_recursive(matrix, assigned, row_covered, col_covered, depth, total_cost):
if depth == n:
return total_cost
zero_positions = find_zeros(matrix)

if not zero_positions:
min_val = sys.maxsize
for i in range(n):
if not row_covered[i]:
for j in range(n):
if not col_covered[j]:
min_val = min(min_val, matrix[i][j])
for i in range(n):
if not row_covered[i]:
for j in range(n):
if not col_covered[j]:
matrix[i][j] -= min_val
total_cost += min_val
col_covered = [False] * n
row_covered = [False] * n
return assign_recursive(matrix, assigned, row_covered, col_covered, depth,
total_cost)
i, j = zero_positions[0]
assigned[i] = j
row_covered[i] = True
col_covered[j] = True
for k in range(n):
if k != i:
matrix[k][j] = sys.maxsize
if k != j:
matrix[i][k] = sys.maxsize
return assign_recursive(matrix, assigned, row_covered, col_covered, depth + 1,
total_cost)
# Step 1: Subtract minimum value from each row
for i in range(n):
cost_matrix[i] = subtract_min(cost_matrix[i])
# Step 2: Subtract minimum value from each column
cost_matrix = subtract_min_col(cost_matrix)
assigned = [-1] * n # Array to store assigned jobs
row_covered = [False] * n
col_covered = [False] * n
total_cost = assign_recursive(cost_matrix, assigned, row_covered, col_covered, 0, 0)
return assigned, total_cost
# Example usage:
cost_matrix = [
[9, 11, 14, 8],
[6, 15, 13, 7],
[12, 13, 6, 8],
[14, 9, 10, 12]
]

assigned, total_cost = assign(cost_matrix)


print("Assigned Jobs:", assigned)
print("Total Cost:", total_cost)

OUTPUT:
Assigned Jobs: [3, -1, -1, -1]
Total Cost: 0
RESULT:

You might also like