AI Data Science Lab Exercises Guide
AI Data Science Lab Exercises Guide
Laboratory
Year / Semester
:
II Year / III Semester
[Link] [Link]
TABLE OF CONTENTS
1 BINARY SEARCH
2
FIND MAXIMUM AND
MINIMUM
GREEDY METHOD (UNIT IV)
3
KNAPSACK
4
MINIMUM SPANNING TREE
USINGPRIM’S ALGORITHM
5
MINIMUM SPANNING
TREE USINGKRUSKAL’S
ALGORITHM
6
SINGLE SOURCE SHORTEST
PATHS
DYNAMIC PROGRAMMING (UNIT V)
8 TRAVELING SALESMAN
PROBLEM
BACKTRACKING METHOD (UNIT V)
9 N QUEEN OR 8 QUEENS
PROBLEM
10 SUM OF SUBSETS
TABLE OF CONTENTS
11 TRAVELING SALESMAN
PROBLEM
12 TELECOMMUNICATION
NETWORK DESIGN
13 GRID-BASED ROBOTIC PATH
PLANNING
14 SUDOKU SOLVER
15 VEHICLE ROUTINGPROBLEM
Divide-and-Conquer
Technique
Divide and Conquer Algorithm involves breaking a larger problem into smaller subproblems, solving them
independently, and then combining their solutions to solve the original problem. The basic idea is to
recursively divide the problem into smaller subproblems until they become simple enough to be solved
directly. Once the solutions to the subproblems are obtained, they are then combined to produce the overall
solution.
Working of Divide and Conquer Algorithm:
The divide and Conquer Algorithm can be divided into three steps: Divide, Conquer, and Merge.
1. Divide:
Break down the original problem into smaller subproblems.
Each subproblem should represent a part of the overall problem.
The goal is to divide the problem until no further division is possible.
2. Conquer:
Solve each of the smaller subproblems individually.
If a subproblem is small enough (often referred to as the “base case”), we solve it directly without further
recursion.
The goal is to find solutions for these subproblems independently.
3. Merge:
Combine the sub-problems to get the final solution of the whole problem.
Once the smaller subproblems are solved, we recursively combine their solutions to get the solution to a
larger problem.
The goal is to formulate a solution for the original problem by merging the results from the subproblems.
Example Problems:
1. Binary search
2. Merge Sort
3. Quick Sort
4. Finding the maximum and minimum elements in an array
[Link]: BINARY SEARCH
DATE :
AIM:
To find whether the given element is found in the array or not using the divide and conquer algorithm
PSEUDOCODE:
int mid;
if(low<=high)
{
mid=(low+high)/2;
if(a[mid]==key)
{
return mid;
}
if(key<a[mid])
{
return(binarySearch(a,low,mid-1,key));
}
if(key>a[mid])
{
return(binarySearch(a,mid+1,high,key));
}
}
return -1;
}
int main()
{
int i,key,n,res;
printf("\nEnter the number of elements\n");
scanf("%d",&n);
printf("\nEnter the array elements\n");
for(i=0;i<n;i++)
{
scanf("%d",&a[i]);
}
printf("Enter the key to be searched\n");
scanf("%d",&key);
res=binarySearch(a,0,n-1,key);
if(res==-1)
{
printf("\nElement %d is not found in the array",key);
return 0;
}
printf("\nElement %d is found at posisiton\t%d in the array",key,res);
return 0;
}
INPUT/OUTPUT:
Binary Search in C
Complexity Analysis
RESULT:
Using the divide and conquer algorithm whether the given element is found in the array or not
[Link]: FINDING MAXIMUM AND MINIMUM
DATE:
AIM:
To find the maximum and minimum element in an array using divide and conquer method
PSEUDOCODE:
MinMax(A, n){
int min = A[0];
int max = A[0];
for(int i = 1; i < n; i++){
if(max < A[i])
max = A[i];
else if(min > A[i])
min = A[i];
}
return (min, max);
}
Step 1: Find the mid of the array.
Step 2: Find the maximum and minimum of the left subarray recursively.
Step 3: Find the maximum and minimum of the right subarray recursively.
Step 4: Compare the result of step 3 and step 4
Step 5: Return the minimum and maximum.
PROGRAM:
#include<stdio.h>
#include<stdio.h>
int max, min;
int a[100];
void maxmin(int i, int j)
{
int max1, min1, mid; if(i==j)
{
max = min = a[i];
}
else
{
if(i == j-1)
{
if(a[i] <a[j])
{
max = a[j];
min = a[i];
}
else
{
max = a[i];
min = a[j];
}
}
else
Time Complexity:
The recurrence relation for the divide and conquer approach in this problem is: T(n)=2T(n/2)+Θ(1)
This simplifies to Θ(n) using the Master Theorem, which means the time complexity is linear.
Space Complexity:
The space complexity is primarily due to the recursive stack space used. In the worst case, the depth of the
recursion tree is log n so the space complexity O(log n).
RESULT:
AIM:
To solve the knapsack problem using greedy algorithm
PSEUDOCODE:
#include <stdio.h>
int main() {
float weight[50], profit[50], ratio[50], Totalvalue = 0, temp, capacity, amount;
int n, i, j;
temp = weight[j];
weight[j] = weight[i];
weight[i] = temp;
temp = profit[j];
profit[j] = profit[i];
profit[i] = temp;
}
}
}
printf("Knapsack problem using Greedy Algorithm:\n");
printf("Items chosen (index, fraction):\n");
for (i = 0; i < n; i++) {
if (weight[i] > capacity)
break;
else {
Totalvalue += profit[i];
capacity -= weight[i];
printf("%d, 1.0\n", i);
}
}
if (i < n) {
Totalvalue += (ratio[i] * capacity);
printf("%d, %.2f\n", i, capacity / weight[i]);
}
Knapsack
The given program uses a greedy approach to solve the fractional knapsack problem. Let's analyze the time
complexity step by step:
1. Input Reading:
o Reading the number of items (n): O(1)
o Reading the weights and profits of n items: O(n)
2. Ratio Calculation:
o Calculating the ratio of profit to weight for each item: O(n)
3. Sorting:
o The program sorts the items based on the ratio in descending order. The sorting algorithm used here
is a simple nested loop for selection sort which has a time complexity of O(n2 ).
4. Knapsack Filling:
o The loop iterates over the sorted items to fill the knapsack: O(n)
Combining these steps, the overall time complexity is dominated by the sorting step:O(n2 )
Space Complexity
1. Input Storage:
o Arrays weight, profit, and ratio, each of size n: O(n) for each, so O(3n)=O(n)
2. Auxiliary Variables:
o Variables like Totalvalue, temp, capacity, amount, n, i, j are all constants: O(1)
Since the primary storage requirement grows linearly with the number of items, the overall space complexity is:
O(n)
INPUT/OUTPUT:
RESULT:
AIM:
To find the minimum spanning tree using Prim’s algorithm
PSEUDOCODE:
T = ∅;
U = { 1 };
while (U ≠ V)
let (u, v) be the lowest cost edge such that u ∈ U and v ∈ V - U;
T = T 𝖴 {(u, v)}
U = U 𝖴 {v}
The steps for implementing Prim's algorithm are as follows:
1. Initialize the minimum spanning tree with a vertex chosen at random.
2. Find all the edges that connect the tree to new vertices, find the minimum and add it to the tree
3. Keep repeating step 2 until we get a minimum spanning tree
PROGRAM:
#include<stdio.h>
#include<stdbool.h>
#define INF 9999999
// number of vertices in graph
#define V 5
// create a 2d array of size 5x5
//for adjacency matrix to represent graph
int G[V][V] = {
{0, 9, 75, 0, 0},
{9, 0, 95, 19, 42},
{75, 95, 0, 51, 66},
{0, 19, 51, 0, 31},
{0, 42, 66, 31, 0}};
int main() {
int no_edge; // number of edge
int mincost=0;
// create a array to track selected vertex
// selected will become true otherwise false
int selected[V];
}
}
}
}
}
printf("%d - %d : %d\n", x, y, G[x][y]);
selected[y] = true;
no_edge++;
mincost=mincost+min;
}
printf("Minimum cost is %d",mincost);
return 0;
}
Time Complexity Analysis:
Time Complexity
The time complexity of Prim's algorithm depends on the data structure used to represent the graph:
Adjacency Matrix Representation:
o Finding Minimum Edge:
The nested loops iterate through all vertices (V), checking edges for each vertex. This results in
a time complexity of O(V2) for finding the minimum edge.
o Total Algorithm Complexity:
Since each of the V vertices can potentially connect with V-1 other vertices, the overall time
complexity of Prim's algorithm with an adjacency matrix representation is O(V2)
Space Complexity Analysis:
Space Complexity:
o The space complexity primarily depends on the adjacency matrix G, which uses O(V2) space to store
the graph.
o Additional space is used for the selected array of size V, which tracks the vertices included in the MST.
Hence, the space complexity is O(V2+V), which simplifies to O(V2)
INPUT/OUTPUT:
RESULT:
AIM:
PSEUDOCODE:
KRUSKAL(G):
A=∅
For each vertex v ∈ G.V:
MAKE-SET(v)
For each edge (u, v) ∈ G.E ordered by increasing order by weight(u, v):
if FIND-SET(u) ≠ FIND-SET(v):
A = A 𝖴 {(u, v)}
UNION(u, v)
return A
PROGRAM:
#include <stdio.h>
#include <conio.h>
#include <stdlib.h>
int i, j, k, a, b, u, v, n, ne = 1;
int min, mincost = 0, cost[9][9], parent[9];
int find(int);
int uni(int, int);
void main() {
printf("\n\tImplementation of Kruskal's Algorithm\n");
printf("\nEnter the no. of vertices:");
scanf("%d", & n);
printf("\nEnter the cost adjacency matrix:\n");
for (i = 1; i <= n; i++) {
for (j = 1; j <= n; j++) {
scanf("%d", & cost[i][j]);
if (cost[i][j] == 0)
cost[i][j] = 999;
}
}
printf("The edges of Minimum Cost Spanning Tree are\n");
while (ne < n) {
for (i = 1, min = 999; i <= n; i++) {
for (j = 1; j <= n; j++) {
if (cost[i][j] < min) {
min = cost[i][j];
a = u = i;
b = v = j;
}
}
}
u = find(u);
v = find(v);
if (uni(u, v)) {
printf("%d edge (%d,%d) =%d\n", ne++, a, b, min);
mincost += min;
}
cost[a][b] = cost[b][a] = 999;
}
printf("\n\tMinimum cost = %d\n", mincost);
getch();
}
int find(int i) {
while (parent[i])
i = parent[i];
return i;
}
int uni(int i, int j) {
if (i != j) {
parent[j] = i;
return 1;
}
return 0;
}
RESULT:
AIM:
To find the shortest path from a single source using Dijkstra’s Algorithm
PSEUDOCODE:
function dijkstra(G, S)
for each vertex V in G
distance[V] <- infinite
previous[V] <- NULL
If V != S, add V to Priority Queue Q
distance[S] <- 0
#include <stdio.h>
#include <conio.h>
#define infinity 9999
void dij(int n,int v,int cost[10][10],int dist[])
{
int i,u,count,w,flag[10],min;
for(i=1;i<=n;i++) f
lag[i]=0,dist[i]=cost[v][i];
count=2;
while(count<=n)
{
min=99;
for(w=1;w<=n;w++)
if(dist[w]<min && !flag[w])
min=dist[w],u=w; flag[u]=1; count++;
for(w=1;w<=n;w++)
if((dist[u]+cost[u][w]<dist[w]) && !flag[w])
dist[w]=dist[u]+cost[u][w];
}
}
void main()
{
int n,v,i,j,cost[10][10],dist[10];
clrscr();
printf("\n Enter the number of nodes:");
scanf("%d",&n);
printf("\n Enter the cost matrix:\n");
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
{
scanf("%d",&cost[i][j]);
if(cost[i][j]==0) cost[i][j]=infinity;
}
printf("\n Enter the source matrix:");
scanf("%d",&v);
dij(n,v,cost,dist);
printf("\n Shortest path:\n");
for(i=1;i<=n;i++)
if(i!=v)
printf("%d->%d,cost=%d\n",v,i,dist[i]);
getch();
}
Dijkstra's algorithm, as implemented above, has a time complexity of O(n2)
1. Initialization (O(n)):
o Initializing the dist and flag arrays takes O(n) time, where nis the number of vertices.
2. Finding the Minimum Distance Vertex (O(n2))
o In each iteration of the while loop, we need to find the vertex with the minimum distance that
hasn't been included in the shortest path tree yet. This involves scanning all vertices, which takes
O(n)) time in the worst case.
o This process is repeated nnn times (once for each vertex), leading to a total of O(n)×O(n)= O(n2)
3. Updating the Distance Values O(n2)
o For each selected vertex u, we update the distance values of its adjacent vertices. In the worst
case, this can take O(n) time for each vertex u.
o Since we do this for n vertices, the total time for this step is also O(n)×O(n)= O(n2)
Thus, the overall time complexity of Dijkstra's algorithm using an adjacency matrix and simple
arrays is O(n2 )
Space Complexity
The space complexity of the algorithm is determined by the amount of memory used by the adjacency
matrix and the additional arrays.
O(n2)+O(n)+O(1)=O(n2)
INPUT/OUTPUT:
Enter the number of nodes: 7
Enter the cost matrix
0 0 1 2 0 0 0
0 0 2 0 0 3 0
1 2 0 1 3 0 0
2 0 1 0 0 0 1
0 0 3 0 0 2 0
0 3 0 0 2 0 1
0 0 0 1 0 1 0
RESULT:
Using Dijkstra algorithm the shortest path from a single source in the given cost matrix is found.
Dynamic Programming
Technique
Dynamic Programming (DP) is a method used in mathematics and computer science to solve complex problems
by breaking them down into simpler subproblems. By solving each subproblem only once and storing the results,
it avoids redundant computations, leading to more efficient solutions for a wide range of problems.
AIM:
To find the shortest path from all the vertices using Floyd-Warshall Algorithm (Dynamic Programming
Approach)
PSEUDOCODE:
n = no of vertices
A = matrix of dimension n*n
for k = 1 to n
for i = 1 to n
for j = 1 to n
Ak[i, j] = min (Ak-1[i, j], Ak-1[i, k] + Ak-1[k, j])
return A
PROGRAM:
// Floyd-Warshall Algorithm in C
#include <stdio.h>
// defining the number of vertices
#define nV 4
int main() {
int graph[nV][nV] = {{0, 3, INF, 5},
{2, 0, INF, 4},
{INF, 1, 0, INF},
{INF, INF, 2, 0}};
floydWarshall(graph);
}
Time Complexity
The time complexity of the Floyd-Warshall algorithm can be analyzed by examining its triple nested loops.
Since each loop runs n times, the total number of iterations of the innermost statement is:
n×n×n=n
O(n3)
This cubic time complexity indicates that the algorithm will take significantly longer to run as the number
of vertices increases, making it suitable for graphs with a relatively small number of vertices.
Space Complexity
The space complexity of the Floyd-Warshall algorithm primarily depends on the space required to store the
graph and the shortest path matrix.
1. Graph Representation:
o The input graph is typically represented as an adjacency matrix, which requires O(n2) space.
2. Shortest Path Matrix:
o The algorithm uses an additional 2D array (matrix) to store the shortest path distances. This also
requires O(n2)) space.
3. Auxiliary Space:
o The space for the loop control variables (i, j, k) and other variables (min) is insignificant compared
to the space required for the matrices.
O(n2)
INPUT/OUTPUT:
RESULT:
Using Floyd-Warshall Algorithm the shortest path from all the vertices are found.
[Link]: TRAVELING SALESMAN PROBLEM USING DYNAMIC
DATE: PROGRAMMING
AIM:
PSEUDOCODE:
Algorithm: Traveling-Salesman-Problem
C ({1}, 1) = 0
for s = 2 to n do
for all subsets S є {1, 2, 3, … , n} of size s and containing 1
C (S, 1) = ∞
for all j є S and j ≠ 1
C (S, j) = min {C (S – {j}, i) + d(i, j) for i є S and i ≠ j}
Return minj C ({1, 2, 3, …, n}, j) + d(j, i)
PROGRAM:
#include <stdio.h>
int matrix[25][25], visited_cities[10], limit, cost = 0;
int tsp(int c)
{
int count, nearest_city = 999;
int minimum = 999, temp;
for(count = 0; count < limit; count++)
{
if((matrix[c][count] != 0) && (visited_cities[count] == 0))
{
if(matrix[c][count] < minimum)
{
minimum = matrix[count][0] + matrix[c][count];
}
temp = matrix[c][count];
nearest_city = count;
}
}
if(minimum != 999)
{
cost = cost + temp;
}
return nearest_city;
}
int main()
{
int i, j;
printf("Enter Total Number of Cities:\t");
scanf("%d", &limit);
printf("\nEnter Cost Matrix\n");
for(i = 0; i < limit; i++)
{
printf("\nEnter %d Elements in Row[%d]\n", limit, i + 1);
for(j = 0; j < limit; j++)
{
scanf("%d", &matrix[i][j]);
}
visited_cities[i] = 0;
}
printf("\nEntered Cost Matrix\n");
for(i = 0; i < limit; i++)
{
printf("\n");
for(j = 0; j < limit; j++)
{
printf("%d ", matrix[i][j]);
}
}
printf("\n\nPath:\t");
minimum_cost(0);
printf("\n\nMinimum Cost: \t");
printf("%d\n", cost);
return 0;
}
Time Complexity Analysis
1. Matrix Input:
o Reading the cost matrix: O(N 2 ) where NNN is the number of cities. This is because the program
iterates through each element of the matrix to read the input.
2. Greedy Algorithm (tsp function):
o Finding the nearest unvisited city: O(N) In the worst case, this involves checking each city to find
the minimum cost path from the current city.
3. Recursive Path Construction (minimum_cost function):
o The recursion depth in the minimum_cost function can go up to NNN, because in the worst case,
each city is visited exactly once.
RESULT:
Thus the Travelling salesman problem is solved using dynamic programming.
Backtracking Technique
Backtracking is a problem-solving algorithmic technique that involves finding a solution incrementally by trying
different options and undoing them if they lead to a dead end. It is commonly used in situations where you need
to explore multiple possibilities to solve a problem, like searching for a path in a maze or solving puzzles like
Sudoku. When a dead end is reached, the algorithm backtracks to the previous decision point and explores a
different path until a solution is found or all possibilities have been exhausted.
Applications of Backtracking
Creating smart bots to play Board Games such as Chess.
Solving mazes and puzzles such as N-Queen problem.
Network Routing and Congestion Control.
Decryption
Text Justification
[Link]: N-QUEEN PROBLEM OR 8 QUEEN
DATE: PROBLEM
AIM:
To solve N-queen problem using backtracking algorithm
PSEUDOCODE:
Place (k, i)
{
For j ← 1 to k - 1
do if (x [j] = i)
or (Abs x [j]) - i) = (Abs (j - k))
then return false;
return true;
}
N - Queens (k, n)
{
For i ← 1 to n
do if Place (k, i) then
{
x [k] ← i;
if (k ==n) then
write (x [1. ..n));
else
N - Queens (k + 1, n);
}
}
PROGRAM:
#include<stdio.h>
#define BOARD_SIZE 5
void displayChess(int chBoard[BOARD_SIZE][BOARD_SIZE]) {
for (int row = 0; row < BOARD_SIZE; row++) {
for (int col = 0; col < BOARD_SIZE; col++)
printf("%d ", chBoard[row][col]);
printf("\n");
}
}
int isQueenPlaceValid(int chBoard[BOARD_SIZE][BOARD_SIZE], int crntRow, int crntCol) {
// checking if queen is in the left or not
for (int i = 0; i < crntCol; i++)
if (chBoard[crntRow][i])
return 0;
for (int i = crntRow, j = crntCol; i >= 0 && j >= 0; i--, j--)
//checking if queen is in the left upper diagonal or not
if (chBoard[i][j])
return 0;
for (int i = crntRow, j = crntCol; j >= 0 && i < BOARD_SIZE; i++, j--)
//checking if queen is in the left lower diagonal or not
if (chBoard[i][j])
return 0;
return 1;
}
int solveProblem(int chBoard[BOARD_SIZE][BOARD_SIZE], int crntCol) {
//when N queens are placed successfully
if (crntCol >= BOARD_SIZE)
return 1;
// checking placement of queen is possible or not
for (int i = 0; i < BOARD_SIZE; i++) {
if (isQueenPlaceValid(chBoard, i, crntCol)) {
//if validate, place the queen at place (i, col)
chBoard[i][crntCol] = 1;
//Go for the other columns recursively
return 0;
displayChess(chBoard);
return 1;
}
int main() {
displaySolution();
return 0;
}
Time Complexity: The time complexity of the solveProblem function can be expressed as O(N!), where
N is the size of the chessboard (BOARD_SIZE). This is because:
o In the worst case, the function recursively tries all possible combinations of queen placements.
o The function explores N choices for the first queen, N-1 choices for the second queen, and so on.
o Therefore, the total number of recursive calls (and hence operations) can be estimated as N * (N-1) * (N-2)
* ... * 1 = N!.
Space Complexity: The space complexity primarily comes from the space used by the chBoard array:
o chBoard: Requires O(N2) space to store the NxN chessboard.
o Recursive Stack: The recursive calls of solveProblem can go up to depth N. Therefore, the space
complexity due to recursion is O(N).
o Overall, the space complexity is O(N2+ N) = O(N2), dominated by the chessboard array.
INPUT/OUTPUT:
PSEUDOCODE:
subset_countsubset_count+1;
if(starting_index < [Link])
subset_sum(list, sum - list[starting_index-1], starting_index, target_sum);
else
end
PROGRAM:
#include <stdio.h>
#include <stdbool.h>
void printSubset(int subset[], int size) {
printf("Subset: { ");
for (int i = 0; i < size; i++) {
printf("%d ", subset[i]);
}
printf("}\n");
}
void sumOfSubsetsUtil(int weights[], int targetSum, int n, int subset[], int subsetSize, int
sum, int index) {
if (sum == targetSum) {
printSubset(subset, subsetSize);
return;
}
for (int i = index; i < n; i++) {
if (sum + weights[i] <= targetSum) {
subset[subsetSize] = weights[i];
sumOfSubsetsUtil(weights, targetSum, n, subset, subsetSize + 1, sum +
weights[i], i + 1);
}
}
}
int main() {
int n, targetSum;
printf("Enter the number of elements: ");
scanf("%d", &n);
int weights[n];
printf("Enter the elements: ");
for (int i = 0; i < n; i++) {
scanf("%d", &weights[i]);
} printf("Enter the target sum: ");
scanf("%d", &targetSum);
sumOfSubsets(weights, targetSum, n);
return 0;
}
RESULT:
Thus the subset of a set is found by using the element of the given subset that sum up to
a given Number k.
Branch-and-Bound
Technique
Branch and bound algorithms are used to find the optimal solution for combinatory, discrete, and general mathematical
optimization problems.
A branch and bound algorithm provides an optimal solution to an NP-Hard problem by exploring the entire search space.
Through the exploration of the entire search space, a branch and bound algorithm identify possible candidates for solutions
step-by-step.
Basic Concepts of Branch and Bound:
▸ Generation of a state space tree:
As in the case of backtracking, B&B generates a state space tree to efficiently search the solution space of a given problem
[Link] B&B, all children of an E-node in a state space tree are produced before any live node gets converted in an E-
node. Thus, the E-node remains an E-node until i becomes a dead node.
Evaluation of a candidate solution:
Unlike backtracking, B&B needs additional factors evaluate a candidate solution:
A way to assign a bound on the best values of the given criterion functions to each node in a state space tree: It is produced
by the addition of further omponents to the partial solution given by that node.
The best values of a given criterion function obtained so far: It describes the upper bound for the maximization problem and
the lower bound for the minimization problem.
A feasible solution is defined by the problem states that satisfy all the given constraints.
An optimal solution is a feasible solution, which produces the best value of a given objective function.
Bounding function :
It optimizes the search for a solution vector in the solution space of a given problem instance. It is a heuristic function that
evaluates the lower and upper bounds on the possible solutions at each node. The bound values are used to search the partial
solutions leading to an optimal solution. If a node does not produce a solution better than the best solution obtained thus far,
then it is abandoned without further [Link] algorithm then branches to another path to get a better solution. The
desired solution to the problem is the value of the best solution produced so far.
Applications of Branch and Bound:
Combinatorial Optimization: Branch and Bound is widely used in solving combinatorial optimization problems such as the
Traveling Salesman Problem (TSP), Knapsack Problem, and Job Scheduling.
Constraint Satisfaction Problems: Branch and Bound can efficiently handle constraint satisfaction problems by systematically
exploring the search space and pruning branches based on constraints.
Resource Allocation: It’s applied in scenarios like resource allocation where resources need to be distributed optimally among
competing demands.
[Link]: TRAVELLING SALESMAN PROBLEM USING BRANCH AND BOUND
DATE:
AIM:
To solve the traveling salesman problem using branch and bound algorithm
PSEUDOCODE:
′ ′
Require: set Q of nodes (Y, N, S, c ) ordered by increasing c ∈R
Let Q = {( ø, ø, ø, −∞)}
while Q /= ø do
if φ(α) = 1 then
Let R = lowerBound(α)
if d(R) < d(S) then if τ ( R) =
0 then
if Y 𝖴N /= A then
choose a ∈A z (Y 𝖴N )
Let β = (Y {a}, N, S, d(R)); let γ = (Y, N 𝖴{a}, S, d(R))
Let Q = Q 𝖴{β, γ}
end if
else
Let S = R
end if end if
end if
end while return S
PROGRAM:
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
curr_weight -= adj[curr_path[level-1]][i];
curr_bound = temp;
int curr_bound = 0;
for (int i = 0; i < n; i++) {
curr_bound += (findMin(adj, i, n) + findMin(adj, i, n));
}
curr_bound = (curr_bound & 1) ? curr_bound/2 + 1 : curr_bound/2;
visited[0] = 1;
curr_path[0] = 0;
TSPRec(adj, curr_bound, 0, 1, curr_path, visited, final_path, &final_res, n);
printf("Minimum cost: %d\n", final_res);
printf("Path: ");
for (int i = 0; i <= n; i++) {
printf("%d ", final_path[i]);
}
printf("\n");
}
int main()
{
int adj[N][N] = {
{0, 10, 15, 20},
{10, 0, 35, 25},
{15, 35, 0, 30},
{20, 25, 30, 0}
};
TSP(adj, N);
return 0;
}
Time Complexity Analysis:
1. Initial Setup (TSP function):
o Calculating curr_bound: This involves finding the minimum edges connected to each node, which is
O(N2), where NNN is the number of cities.
o Initial setup of arrays: Setting up arrays like curr_path and visited takes O(N)
2. Recursive Function (TSPRec function):
o The recursive function TSPRec is called recursively to explore all possible paths. The number of recursive
calls in the worst case is N! (factorial of N), because the algorithm explores all permutations of cities.
o Within each recursive call:
Finding minimum edges: findMin function is called twice for each city, contributing O(N) each
time.
Updating curr_bound: This operation is O(1).
Checking conditions and updating paths: These operations are O(1) each.
Resetting visited array: This operation is O(N).
Therefore, each call to TSPRec has a time complexity of O(N2).
3. Final Output:
o Printing the final path and minimum cost takes O(N).
Combining these, the overall time complexity of the algorithm is dominated by the recursive calls, which is O(N×N!+N2).
Space Complexity Analysis:
1. Arrays and Matrices:
o adj matrix: Takes O(N2) space.
o visited, curr_path, final_path: Each takes O(N) space.
o Other variables: Take constant space, O(1)
2. Recursive Stack:
o The depth of recursion can go up to NNN, so the space complexity due to recursion is O(N)
Therefore, the overall space complexity is O(N 2)
INPUT/OUTPUT:
RESULT:
Thus The traveling salesman problem is solved using branch and bound algorithm.
Content Beyond the Syllabus
[Link]: Telecommunication Network Design
DATE:
Description
Consider a real time scenario, where the network designers need to connect five cities with the least
cost in terms of laying down optical fiber cables. The cities and the costs (in units) of laying cables between them are
represented in a weighted graph.
Here is the adjacency matrix for the graph:
A B C D
A 0 2 3 0
B 2 0 4 1
C 3 4 0 5
D 0 1 5 0
E 0 0 6 7
PSEUDOCODE:
Function kruskalAlgo():
For i from 0 to n-1:
For j from 0 to n-1:
If Graph[i][j] is not 0 and i < j:
Set [Link][elist.n].u = i
Set [Link][elist.n].v = j
Set [Link][elist.n].w =Graph[i][j]
Increment elist.n by 1
For i from 0 to n-1:
Set belongs[i] = i
Set spanlist.n = 0
For i from 0 to elist.n-1:
Set cno1 = find(belongs, [Link][i].u)
Set cno2 = find(belongs, [Link][i].v)
If cno1!= cno2:
Set [Link][spanlist.n] = [Link][i]
Increment spanlist.n by 1
applyUnion(belongs, cno1, cno2)
Return belongs[vertexno]
Function applyUnion(belongs, c1, c2):
For i from 0 to n-1:
If belongs[i] == c2:
Set belongs[i] = c
Function sort():
For i from 1 to elist.n-1:
For j from 0 to elist.n-2:
If [Link][j].w > [Link][j + 1].w:
Set temp = [Link][j]
Set [Link][j] = [Link][j + 1]
Set [Link][j + 1] = temp
Function print():
For i from 0 to spanlist.n-1:
Print [Link][i].u, [Link][i].v, and [Link][i].w
Add [Link][i].w to cost
Print the total cost of the spanning tree as cost
PROGRAM:
#include <stdio.h>
# define MAX 30
typedef struct edge {
int u, v, w;
} edge;
typedef struct edge_list {
edge data[MAX];
int n;
} edge_list;
edge_list elist;
int Graph[MAX][MAX], n;
edge_list spanlist;
void kruskalAlgo();
int find(int belongs[], int vertexno);
void applyUnion(int belongs[], int c1, int c2);
void sort();
void print();
// Applying Krushkal Algo
void kruskalAlgo() {
int belongs[MAX], i, j, cno1, cno2;
elist.n = 0;
for (i = 1; i < n; i++)
for (j = 0; j < i; j++) {
if (Graph[i][j] != 0) {
[Link][elist.n].u = i;
[Link][elist.n].v = j;
[Link][elist.n].w = Graph[i][j];
elist.n++;
}
}
}
sort();
for (i = 0; i < n; i++)
belongs[i] = i;
spanlist.n = 0;
for (i = 0; i < elist.n; i++) {
cno1 = find(belongs, [Link][i].u);
cno2 = find(belongs, [Link][i].v);
if (cno1 != cno2) {
[Link][spanlist.n] = [Link][i];
spanlist.n = spanlist.n + 1;
applyUnion(belongs, cno1, cno2);
}
}}
int find(int belongs[], int vertexno) {
return (belongs[vertexno]);
}
void applyUnion(int belongs[], int c1, int c2) {
int i;
for (i = 0; i < n; i++)
if (belongs[i] == c2)
belongs[i] = c1;}
void sort() {
int i, j;
edge temp;
for (i = 1; i < elist.n; i++)
for (j = 0; j < elist.n - 1; j++)
if ([Link][j].w > [Link][j + 1].w) {
temp = [Link][j];
[Link][j] = [Link][j + 1];
[Link][j + 1] = temp;
}
}
void print() {
int i, cost = 0;
for (i = 0; i < spanlist.n; i++) {
printf("\n%d - %d : %d", [Link][i].u, [Link][i].v, [Link][i].w);
cost = cost + [Link][i].w;
}
printf("\nSpanning tree cost: %d", cost);
}
int main() {
int i, j, total_cost,n;
n = 6;
Graph[0][0] = 0;
Graph[0][1] = 4;
Graph[0][2] = 4;
Graph[0][3] = 0;
Graph[0][4] = 0;
Graph[1][0] = 4;
Graph[1][1] = 0;
Graph[1][2] = 2;
Graph[1][3] = 0;
Graph[1][4] = 0;
Graph[2][0] = 4;
Graph[2][1] = 2;
Graph[2][2] = 0;
Graph[2][3] = 3;
Graph[2][4] = 4;
Graph[3][0] = 0;
Graph[3][1] = 0;
Graph[3][2] = 3;
Graph[3][3] = 0;
Graph[3][4] = 3;
Graph[4][0] = 0;
Graph[4][1] = 0;
Graph[4][2] = 4;
Graph[4][3] = 4;
Graph[4][4] = 4;
Graph[5][0] = 0;
Graph[5][1] = 0;
Graph[5][2] = 2;
Graph[5][3] = 0;
Graph[5][4] = 3;
kruskalAlgo();
print();}
INPUT/OUTPUT:
RESULT:
The cities are efficiently connected with a total cost of 14 units, using the least expensive
routes to ensure optimal network design.
[Link]: Grid-Based Robotic Path Planning Using
DATE: Dynamic Programming
AIM:
Consider a grid-world where an agent needs to find the shortest path from a start position to a goal
position. The agent can move in four directions (up, down, left, right) and each movement has a cost
associated with it.
PSEUDOCODE:
Define Constants
ROWS = 5
COLS = 5
INF = Infinity // Represents a very large number
Function min(a, b)
If a < b
Return a
Else
Return b
End If
End Function
Function shortestPath(grid, startX, startY, goalX, goalY)
Declare dp[ROWS][COLS] // DP table to store minimum cost to reach each cell b. Initialize DP table with INF
. For i <- 0 to ROWS-1
- For j <- 0 to COLS-1 –
dp[i][j] = INF
dp[startX][startY] = grid[startX][startY]
Return dp[goalX][goalY]
PROGRAM:
#include <stdio.h>
#include <limits.h>
#define ROWS 5
#define COLS 5
#define INF INT_MAX
int min(int a, int b) {
return (a < b) ? a : b;
}
// Function to find the shortest path from start to goal in a grid
int shortestPath(int grid[ROWS][COLS], int startX, int startY, int goalX, int goalY) {
// DP table to store minimum cost to reach each cell
int dp[ROWS][COLS];
// Cost to reach the starting cell is the cost of the starting cell itself
dp[startX][startY] = grid[startX][startY];
int main() {
int grid[ROWS][COLS] = {
{3, 2, 8, 6, 4},
{6, 7, 3, 9, 2},
{5, 1, 5, 2, 7},
{8, 4, 1, 3, 4},
{4, 3, 2, 1, 9}
};
int startX = 0, startY = 0;
int goalX = ROWS - 1, goalY = COLS - 1;
printf("Minimum cost to reach from (%d, %d) to (%d, %d) is: %d\n", startX, startY, goalX, goalY, minCost);
return 0;
}
INPUT/OUTPUT:
RESULT:
The minimum cost to travel from the starting position (0, 0) to the goal position (4, 4) in the grid is 30. This
represents the least amount of cost incurred while moving through the specified grid cells.
[Link]: Sudoku Solver
DATE:
PSEUDOCODE:
Function printGrid(grid)
For row <- 0 to N-1
For col <- 0 to N-1
Print grid[row][col] + " "
Function solveSudoku(grid)
Declare row, col
If findEmptyCell(grid, row, col) == false
Return true
#include <stdio.h>
#include <stdbool.h>
#define N 9
// Check if the number is not already present in the current 3x3 box
int startRow = row - row % 3, startCol = col - col % 3;
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
if (grid[i + startRow][j + startCol] == num) {
return false;
}
}
}
return true;
}
// Function to find an empty cell in the grid
bool findEmptyCell(int grid[N][N], int *row, int *col) {
for (*row = 0; *row < N; (*row)++) {
for (*col = 0; *col < N; (*col)++) {
if (grid[*row][*col] == 0) {
return true;
}
}
return false;
}
// If the current number does not lead to a solution, undo the move
grid[row][col] = 0;
}
}
// Trigger backtracking
return false;
}
// Driver code
int main() {
int grid[N][N] = {
{5, 3, 0, 0, 7, 0, 0, 0, 0},
{6, 0, 0, 1, 9, 5, 0, 0, 0},
{0, 9, 8, 0, 0, 0, 0, 6, 0},
{8, 0, 0, 0, 6, 0, 0, 0, 3},
{4, 0, 0, 8, 0, 3, 0, 0, 1},
{7, 0, 0, 0, 2, 0, 0, 0, 6},
{0, 6, 0, 0, 0, 0, 2, 8, 0},
{0, 0, 0, 4, 1, 9, 0, 0, 5},
{0, 0, 0, 0, 8, 0, 0, 7, 9}
};
if (solveSudoku(grid) == true) {
printGrid(grid);
} else {
printf("No solution exists\n");
}
return 0;
}
INPUT/OUTPUT:
RESULT:
Thus the traveling salesman problem is solved using branch and bound algorithm.
[Link]: Vehicle Routing Problem
DATE:
Problem Description:
We have a set of customers with known demands.
A fleet of vehicles, each with a maximum capacity.
A single depot where all vehicles start and end.
The goal is to minimize the total distance traveled while servicing all customers without exceeding
vehicle capacities.
This example will use a simplified problem with a small number of customers and vehicles.
PSEUDOCODE:
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#define N 4 // Number of customers + 1 depot
#define K 2 // Number of vehicles
#define INF INT_MAX
// Structure to represent a vehicle
typedef struct {
int capacity;
int load;
int position;
} Vehicle;
// Structure to represent the problem instance
typedef struct {
int distances[N][N];
int demands[N];
Vehicle vehicles[K];
int best_cost;
int best_route[N];
} VRPInstance;
// Function to initialize the VRP instance
void initializeVRP(VRPInstance *vrp) {
// Example distances (symmetric matrix)
int distances[N][N] = {
{0, 10, 15, 20},
{10, 0, 35, 25},
{15, 35, 0, 30},
{20, 25, 30, 0}
};
// Example demands
int demands[N] = {0, 10, 15, 10}; // Depot has 0 demand
// Example vehicle capacities
for (int i = 0; i < K; i++) {
vrp->vehicles[i].capacity = 20;
vrp->vehicles[i].load = 0;
vrp->vehicles[i].position = 0; // All start at the depot
}
// Copy the data to the VRP instance
for (int i = 0; i < N; i++) {
int main() {
VRPInstance vrp;
initializeVRP(&vrp);
return 0;
}
INPUT/OUTPUT:
RESULT:
The minimum total distance traveled to service all customers using the vehicles is 60 units. The optimal
route starts and ends at the depot, visiting the customers in the order: Depot → Customer 1 → Customer 3 →
Customer 2 Depot
[Link] [Link]