6(A)-GREEDY TECHNIQUE
DIJKSTRA’S ALGORITHM
PROGRAM:
import heapq
# Function to implement Dijkstra's algorithm using a greedy approach
def dijkstra(graph, start):
# Number of nodes in the graph
num_nodes = len(graph)
# Initialize distances from start to all nodes as infinity
distances = {node: float('inf') for node in range(num_nodes)}
distances[start] = 0 # Distance to start node is 0
# Priority queue to keep track of the next node to visit
pq = [(0, start)] # (distance, node)
while pq:
# Get the node with the smallest distance
current_distance, current_node = heapq.heappop(pq)
# If the current distance is greater than the recorded distance, skip processing
if current_distance > distances[current_node]:
continue
# Explore each neighbor of the current node
for neighbor, weight in graph[current_node]:
distance = current_distance + weight
# If a shorter path to the neighbor is found, update the distance and add to queue
if distance < distances[neighbor]:
distances[neighbor] = distance
heapq.heappush(pq, (distance, neighbor))
return distances
# Function to take user input and run the algorithm
def main():
# Get number of nodes and edges from the user
num_nodes = int(input("Enter number of nodes: "))
num_edges = int(input("Enter number of edges: "))
# Initialize the graph as an adjacency list
graph = {i: [] for i in range(num_nodes)}
# Get the edges from the user (format: node1 node2 weight)
for _ in range(num_edges):
u, v, w = map(int, input("Enter edge (u, v, weight): ").split())
graph[u].append((v, w))
graph[v].append((u, w)) # Assuming an undirected graph
# Get the source node for Dijkstra's algorithm
start_node = int(input("Enter the start node: "))
# Run Dijkstra's algorithm
distances = dijkstra(graph, start_node)
# Output the shortest distances
print("\nShortest distances from node", start_node)
for node, distance in distances.items():
print(f"Node {node}: {distance}")
if __name__ == "__main__":
main()
OUTPUT:
Enter number of nodes: 5
Enter number of edges: 6
Enter edge (u, v, weight): 0 1 10
Enter edge (u, v, weight): 0 2 5
Enter edge (u, v, weight): 1 2 2
Enter edge (u, v, weight): 1 3 1
Enter edge (u, v, weight): 2 3 9
Enter edge (u, v, weight): 3 4 4
Enter the start node: 0
Shortest distances from node 0
Node 0: 0
Node 1: 7
Node 2: 5
Node 3: 8
Node 4: 12
6(B)-GREEDY TECHNIQUE
HUFFMAN TREES AND CODES
PROGRAM:
import heapq
# Helper class for Huffman Tree Node
class Node:
def __init__(self, char, freq):
self.char = char
self.freq = freq
self.left = None
self.right = None
# For heapq to compare nodes based on frequency
def __lt__(self, other):
return self.freq < other.freq
# Function to build the Huffman Tree
def build_huffman_tree(freq_table):
# Create a priority queue (min-heap)
heap = [Node(char, freq) for char, freq in freq_table.items()]
heapq.heapify(heap)
# Build the Huffman Tree
while len(heap) > 1:
# Pop the two nodes with the lowest frequency
left = heapq.heappop(heap)
right = heapq.heappop(heap)
# Create a new internal node with the sum of frequencies
merged_node = Node(None, left.freq + right.freq)
merged_node.left = left
merged_node.right = right
# Push the internal node back into the priority queue
heapq.heappush(heap, merged_node)
# The root of the tree is the only remaining node in the heap
return heap[0]
# Function to generate Huffman codes from the tree
def generate_huffman_codes(node, current_code, codes):
if node is None:
return
# If we reached a leaf node, assign the Huffman code
if node.char is not None:
codes[node.char] = current_code
# Recursively generate codes for left and right subtrees
generate_huffman_codes(node.left, current_code + '0', codes)
generate_huffman_codes(node.right, current_code + '1', codes)
# Function to encode the text using Huffman codes
def encode_text(text, huffman_codes):
return ''.join(huffman_codes[char] for char in text)
# Function to decode the encoded text using the Huffman tree
def decode_text(encoded_text, root):
decoded_text = []
current_node = root
for bit in encoded_text:
# Traverse the tree based on the bit (0 = left, 1 = right)
current_node = current_node.left if bit == '0' else current_node.right
# If we reach a leaf node, add the character to decoded text
if current_node.char is not None:
decoded_text.append(current_node.char)
current_node = root # Restart from root after decoding a character
return ''.join(decoded_text)
# Main function to handle user input and perform the operations
def main():
# Get the number of characters in the frequency table
num_chars = int(input("Enter the number of unique characters: "))
# Input the character and frequency pairs
freq_table = {}
print("Enter character and frequency pairs (e.g., A 5, B 9, C 12):")
for _ in range(num_chars):
char, freq = input().split()
freq_table[char] = int(freq)
# Build Huffman Tree using the frequency table
root = build_huffman_tree(freq_table)
# Generate Huffman codes from the tree
huffman_codes = {}
generate_huffman_codes(root, '', huffman_codes)
# Display the generated Huffman codes
print("\nHuffman Codes:")
for char, code in huffman_codes.items():
print(f"Character: '{char}' | Code: {code}")
# Take the text input for encoding
text = input("\nEnter the text to encode: ")
# Encode the text using Huffman codes
encoded_text = encode_text(text, huffman_codes)
print("\nEncoded text:", encoded_text)
# Decode the encoded text
decoded_text = decode_text(encoded_text, root)
print("\nDecoded text:", decoded_text)
if __name__ == "__main__":
main()
OUTPUT:
Enter the number of unique characters: 5
Enter character and frequency pairs (e.g., A 5, B 9, C 12):
A5
B9
C 12
D 13
E 16
Enter the text to encode: ABCD
Huffman Codes:
Character: 'A' | Code: 000
Character: 'B' | Code: 01
Character: 'C' | Code: 10
Character: 'D' | Code: 11
Character: 'E' | Code: 111
Encoded text: 0000101011
Decoded text: ABCD
7-ITERATIVE IMPROVEMENT
SIMPLEX METHOD
PROGRAM:
import numpy as np
def simplex(c, A, b):
num_variables = len(c)
num_constraints = len(b)
tableau = np.hstack([A, np.eye(num_constraints), b.reshape(-1, 1)])
objective_row = np.hstack([c, np.zeros(num_constraints + 1)])
tableau = np.vstack([tableau, objective_row])
while True:
if np.all(tableau[-1, :-1] >= 0):
return tableau[:-1, -1], tableau[-1, -1]
pivot_col = np.argmin(tableau[-1, :-1])
ratios = tableau[:-1, -1] / tableau[:-1, pivot_col]
ratios[ratios <= 0] = np.inf
pivot_row = np.argmin(ratios)
pivot_value = tableau[pivot_row, pivot_col]
tableau[pivot_row] /= pivot_value
for i in range(len(tableau)):
if i != pivot_row:
tableau[i] -= tableau[i, pivot_col] * tableau[pivot_row]
def main():
num_variables = int(input("Enter the number of variables: "))
num_constraints = int(input("Enter the number of constraints: "))
c = np.array(list(map(float, input(f"Enter the coefficients of the objective function (c):
").split())))
A = []
for _ in range(num_constraints):
A.append(list(map(float, input(f"Enter the coefficients for constraint {_ + 1}: ").split())))
b = np.array(list(map(float, input("Enter the right-hand side values (b): ").split())))
optimal_solution, optimal_value = simplex(c, np.array(A), b)
print("\nOptimal Solution (Variable Values):")
for i, value in enumerate(optimal_solution):
print(f"x{i + 1} = {value}")
print(f"\nOptimal Value of the Objective Function: {optimal_value}")
if __name__ == "__main__":
main()
OUTPUT:
Enter the number of variables: 2
Enter the number of constraints: 2
Enter the coefficients of the objective function (c): 3 2
Enter the coefficients for constraint 1: 1 1
Enter the coefficients for constraint 2: 2 1
Enter the right-hand side values (b): 4 5
Optimal Solution (Variable Values):
x1 = 1.0
x2 = 3.0
Optimal Value of the Objective Function: 9.0
8(A)-BACKTRACKING N-QUEEN PROBLEM
PROGRAM:
def is_safe(board, row, col, n):
for i in range(row):
if board[i] == col or \
board[i] - i == col - row or \
board[i] + i == col + row:
return False
return True
def solve_nqueens(board, row, n):
if row == n:
print_solution(board, n)
return True
res = False
for col in range(n):
if is_safe(board, row, col, n):
board[row] = col
res = solve_nqueens(board, row + 1, n) or res
board[row] = -1 # Backtrack
return res
def print_solution(board, n):
for i in range(n):
row = ['.'] * n
row[board[i]] = 'Q'
print(' '.join(row))
print()
def main():
n = int(input("Enter the number of queens (N): "))
board = [-1] * n
if not solve_nqueens(board, 0, n):
print("Solution does not exist")
if __name__ == "__main__":
main()
OUTPUT:
Enter the number of queens (N): 4
.Q..
...Q
Q...
..Q.
8(B)-BACKTRACKING-SUBSET SUM PROBLEM
PROGRAM:
def subset_sum(arr, target, current_sum=0, index=0, current_subset=[]):
if current_sum == target:
print("Subset found:", current_subset)
return True
if current_sum > target or index >= len(arr):
return False
# Include the current element in the subset
current_subset.append(arr[index])
if subset_sum(arr, target, current_sum + arr[index], index + 1, current_subset):
return True
# Backtrack and exclude the current element from the subset
current_subset.pop()
if subset_sum(arr, target, current_sum, index + 1, current_subset):
return True
return False
def main():
arr = list(map(int, input("Enter the array of numbers: ").split()))
target = int(input("Enter the target sum: "))
if not subset_sum(arr, target):
print("No subset found with the given sum.")
if __name__ == "__main__":
main()
OUTPUT:
Enter the array of numbers: 3 34 4 12 5 2
Enter the target sum: 9
Subset found: [4, 5]
9(A)-BRANCH AND BOUND-ASSIGNEMENT PROBLEM
PROGRAM:
import numpy as np
import heapq
class Node:
def __init__(self, level, cost_matrix, assigned, cost, bound):
self.level = level
self.cost_matrix = cost_matrix
self.assigned = assigned
self.cost = cost
self.bound = bound
def __lt__(self, other):
return self.bound < other.bound
def calculate_lower_bound(cost_matrix, assigned, n):
# Calculate the lower bound based on row and column reductions
# First reduce rows
row_reduced = np.copy(cost_matrix)
row_min = np.min(row_reduced, axis=1)
row_reduced -= row_min[:, np.newaxis]
# Then reduce columns
col_reduced = np.copy(row_reduced)
col_min = np.min(col_reduced, axis=0)
col_reduced -= col_min
# The lower bound is the sum of the row and column reductions
row_reduction_sum = np.sum(row_min)
col_reduction_sum = np.sum(col_min)
return row_reduction_sum + col_reduction_sum
def branch_and_bound(cost_matrix, n):
# Initialize the best solution as a large number
best_cost = float('inf')
best_assignment = None
# Priority queue to explore the state space
queue = []
# Initial Node (level -1, no assignments, no cost, and initial bound)
assigned = [-1] * n
initial_bound = calculate_lower_bound(cost_matrix, assigned, n)
root = Node(-1, cost_matrix, assigned, 0, initial_bound)
heapq.heappush(queue, root)
while queue:
node = heapq.heappop(queue)
# If the lower bound is greater than the best solution found so far, prune the branch
if node.bound >= best_cost:
continue
# If we have assigned all tasks (level == n-1), check if we have a better solution
if node.level == n - 1:
if node.cost < best_cost:
best_cost = node.cost
best_assignment = node.assigned.copy()
continue
# Generate the next level nodes (assign the next task to a worker)
level = node.level + 1
for j in range(n):
if node.assigned[j] == -1: # If worker j is not yet assigned
new_assigned = node.assigned.copy()
new_assigned[j] = level
# Create a new cost matrix by marking the current row as assigned
new_cost_matrix = np.copy(node.cost_matrix)
new_cost_matrix[level, :] = float('inf') # Exclude the current row
# Calculate the new bound
new_cost = node.cost + cost_matrix[level][j]
new_bound = calculate_lower_bound(new_cost_matrix, new_assigned, n)
# If the bound is promising, push it into the priority queue
if new_bound < best_cost:
heapq.heappush(queue, Node(level, new_cost_matrix, new_assigned, new_cost,
new_bound))
return best_cost, best_assignment
def main():
# Read the input for the Assignment Problem
n = int(input("Enter the number of workers/tasks (n): "))
print("Enter the cost matrix:")
cost_matrix = []
for _ in range(n):
OUTPUT:
Enter the number of workers/tasks (n): 4
Enter the cost matrix:
10 15 20 25
20 25 30 35
30 35 40 45
40 45 50 55
Optimal Assignment (Task -> Worker): [0, 1, 2, 3]
Minimum Cost: 80
9(B)-BRANCH AND BOUND-TRAVELLING SALES MAN
PROGRAM:
import numpy as np
import heapq
class Node:
def __init__(self, level, cost, path, bound):
self.level = level # Current level (number of cities visited)
self.cost = cost # Total cost of the path so far
self.path = path # List of cities visited so far
self.bound = bound # Lower bound for this node
def __lt__(self, other):
return self.bound < other.bound # Priority queue sorted by bound
def calculate_lower_bound(cost_matrix, path, n):
"""
Calculate the lower bound for a node.
Uses matrix reduction method (similar to the MST bound).
"""
# Calculate the row reduction
row_reduced = np.copy(cost_matrix)
for i in range(n):
row_min = np.min(row_reduced[i][np.where(row_reduced[i] != float('inf'))]) # min in
the row
if row_min != float('inf'):
row_reduced[i] -= row_min
# Calculate the column reduction
col_reduced = np.copy(row_reduced)
for j in range(n):
col_min = np.min(col_reduced[np.where(col_reduced[:, j] != float('inf')), j]) # min in
the column
if col_min != float('inf'):
col_reduced[:, j] -= col_min
# Add row and column reductions to calculate the lower bound
row_reduction_sum = np.sum(np.min(row_reduced, axis=1))
col_reduction_sum = np.sum(np.min(col_reduced, axis=0))
return row_reduction_sum + col_reduction_sum
def branch_and_bound_tsp(cost_matrix, n):
# Priority queue to explore the search space
min_heap = []
# Initial state (starting from city 0)
path = [0]
cost = 0
initial_bound = calculate_lower_bound(cost_matrix, path, n)
root = Node(level=0, cost=cost, path=path, bound=initial_bound)
heapq.heappush(min_heap, root)
best_cost = float('inf')
best_path = None
while min_heap:
# Get the node with the smallest bound (best possible solution so far)
node = heapq.heappop(min_heap)
# If the bound is greater than the best cost, prune this branch
if node.bound >= best_cost:
continue
# If all cities are visited, check if this is the best solution
if node.level == n - 1:
last_city = node.path[-1]
# Complete the tour by returning to the starting city
total_cost = node.cost + cost_matrix[last_city][0]
if total_cost < best_cost:
best_cost = total_cost
best_path = node.path + [0] # Return to the start city
continue
# Generate the child nodes (add a new city to the path)
for city in range(n):
if city not in node.path:
new_path = node.path + [city]
new_cost = node.cost + cost_matrix[node.path[-1]][city]
new_bound = calculate_lower_bound(cost_matrix, new_path, n)
# If the new bound is promising, push it into the queue
if new_bound < best_cost:
heapq.heappush(min_heap, Node(level=node.level + 1, cost=new_cost,
path=new_path, bound=new_bound))
return best_cost, best_path
def main():
# Input the number of cities
n = int(input("Enter the number of cities: "))
print("Enter the distance matrix:")
cost_matrix = []
for i in range(n):
row = list(map(int, input().split()))
cost_matrix.append(row)
# Solve the TSP using Branch and Bound
min_cost, best_path = branch_and_bound_tsp(np.array(cost_matrix), n)
print(f"\nOptimal Path: {best_path}")
print(f"Minimum Cost: {min_cost}")
if __name__ == "__main__":
main()
OUTPUT:
Enter the number of cities: 4
Enter the distance matrix:
0 10 15 20
10 0 35 25
15 35 0 30
20 25 30 0
Optimal Path: [0, 1, 3, 2, 0]
Minimum Cost: 80