Lab - File - AI
Lab - File - AI
List of Experiment
Definition: The Breadth First Search (BFS) algorithm is used to search a graph data structure
for a node that meets a set of criteria. It starts at the root of the graph and visits all nodes at
the current depth level before moving on to the nodes at the next depth level.
Starting from the root, all the nodes at a particular level are visited first and then the
nodes of the next level are traversed till all the nodes are visited.
To do this a queue is used. All the adjacent unvisited nodes of the current level are pushed
into the queue and the nodes of the current level are marked visited and popped from the
queue.
Algorithm:
2. Create a queue (FIFO data structure) and enqueue the source node.
Advantages of BFS:
1 BFS will never get trapped exploring the useful path forever.
3 If there is more than one solution then BFS can find the minimal one that requires less
number of steps.
5 Easily programmable
Disadvantages of BFS:
3 It has long pathways, when all paths to a destination are on approximately the same search
depth.
4 It can be very memory intensive since it needs to keep track of all the nodes in the search
tree.
5 It can be slow since it expands all the nodes at each level before moving on to the next
level.
Time Complexity of BFS: The time complexity of BFS is O(V + E), where V is the number
of vertices and E is the number of edges in the graph.
Space Complexity of BFS: The space complexity of BFS is O(V), where V is the number of
vertices.
Code:
#include <iostream>
#include <list>
#include <queue>
#include <unordered_map>
class Graph {
adjList[v].push_back(w);
visited[pair.first] = false;
std::queue<int> queue;
queue.push(start);
visited[start] = true;
while (!queue.empty()) {
queue.pop();
if (!visited[neighbor]) {
queue.push(neighbor);
visited[neighbor] = true;
} } } }};
int main() {
Graph g;
g.addEdge(0, 1);
g.addEdge(0, 2);
g.addEdge(1, 2);
g.addEdge(2, 0);
g.addEdge(2, 3);
g.addEdge(3, 3);
g.bfs(2);
return 0;
Output:
Defination: Depth First Traversal (or DFS) for a graph is similar to Depth First Traversal of a
tree. The only catch here is, that, unlike trees, graphs may contain cycles (a node may be
visited twice). To avoid processing a node more than once, use a boolean visited array. A
graph can have more than one DFS traversal.
Depth-First Search or DFS algorithm is a recursive algorithm that uses the backtracking
principle. It entails conducting exhaustive searches of all nodes by moving forward if
possible and backtracking, if necessary. To visit the next node, pop the top node from the
stack and push all of its nearby nodes into a stack. Topological sorting, scheduling problems,
graph cycle detection, and solving puzzles with just one solution, such as a maze or a sudoku
puzzle, all employ depth-first search algorithms. Other applications include network analysis,
such as determining if a graph is bipartite.
Algorithm:
6. Backtrack to the previous node and continue the process for other unvisited branches.
Advantages of DFS :
2 Less time and space complexity compared to Breadth First Search (BFS).
Disadvantages of DFS:
1 The run time may exceed when the goal node is unknown.
5 There is no guarantee to find a minimal solution if more than one solution exists.
Time Complexity of DFS: The time complexity of DFS is O(V + E), where V is the number
of vertices, and E is the number of edges in the graph.
Space Complexity of DFS: The space complexity of DFS is O(V), where V is the number of
vertices.
Code:
#include <iostream>
#include <list>
#include <unordered_map>
class Graph {
public:
adjList[v].push_back(w);
true;
if (!visited[neighbor]) {
dfs(neighbor, visited);
visited[pair.first] = false;
std::cout << "DFS starting from vertex " << start << ":" << std::endl;
dfs(start, visited);
};
int main() {
Graph g;
g.addEdge(0, 1);
g.addEdge(0, 2);
g.addEdge(1, 2);
g.addEdge(2, 0);
g.addEdge(2, 3);
g.addEdge(3, 3);
g.depthFirstSearch(2);
return 0;
Output:
Defination : Tic-Tac-Toe, also known as Noughts and Crosses, is a classic two-player game
where the objective is to be the first to form a straight line of your symbol (either "X" or "O")
on a 3x3 grid. Players take turns placing their symbols on the grid, and the game ends when
one player wins, or the grid is completely filled, resulting in a draw.
Tic-Tac-Toe Algorithm:
2. Alternate between the two players, taking turns to place their symbol (X or O) on an
empty cell of the grid.
3. After each move, check if the current player has won by forming a horizontal,
vertical, or diagonal line of their symbols.
5. Display the result, whether one player wins, it's a draw, or the game continues.
Advantages of Tic-Tac-Toe:
1. Simplicity: Tic-Tac-Toe is easy to understand and play, making it suitable for all
ages.
2. Quick Gameplay: The game typically doesn't take a long time to finish.
Disadvantages of Tic-Tac-Toe:
2. Tendency for Draws: With optimal play, Tic-Tac-Toe often ends in a draw.
Complexity of Tic-Tac-Toe: Tic-Tac-Toe is a solved game, meaning that with perfect play
by both players, the outcome is either a draw or a win for the player going first (X).
Code:
#include <bits/stdc++.h>
#define SIDE 3
printf("\n\n");
board[1][1], board[1][2]);
printf("\t\t\t--------------\n");
board[2][1], board[2][2]);
return;
}
Department of Computer Science Engineering
Faculty of Engineering
Medi-Caps University Ajeet Singh Rajput
13
CS3EA10: Artificial Intelligence Experiment no-3
void showInstructions()
printf("\t\t\t Tic-Tac-Toe\n\n");
printf("\t\t\t 1 | 2 | 3 \n");
printf("\t\t\t--------------\n");
printf("\t\t\t 4 | 5 | 6 \n");
printf("\t\t\t--------------\n");
printf("\t\t\t 7 | 8 | 9 \n\n");
printf("-\t-\t-\t-\t-\t-\t-\t-\t-\t-\n\n");
return;
{ srand(time(NULL));
if (whoseTurn == COMPUTER)
printf("COMPUTER has won\n");
else
printf("HUMAN has won\n"); return;
}
return (false);
return (true);
return (false);
return (true);
return (true);
return (false);
char board[SIDE][SIDE];
initialise(board, moves);
showInstructions();
int moveIndex = 0, x, y;
{ if (whoseTurn == COMPUTER)
{
x = moves[moveIndex] / SIDE;
y = moves[moveIndex] % SIDE;
board[x][y] = COMPUTERMOVE;
showBoard(board);
moveIndex++;
whoseTurn = HUMAN;
x = moves[moveIndex] / SIDE;
y = moves[moveIndex] % SIDE;
board[x][y] = HUMANMOVE;
showBoard(board);
moveIndex++;
whoseTurn = COMPUTER;
} }
printf("It's a draw\n");
else
{
if (whoseTurn == COMPUTER)
whoseTurn = HUMAN;
whoseTurn = COMPUTER;
declareWinner(whoseTurn);
return;
int main()
playTicTacToe(COMPUTER);
return (0);
Output:
Definition: The 8-puzzle problem is a classic puzzle and an example of an unsolvable puzzle in its
general form. It is played on a 3x3 grid with 8 numbered tiles and one empty space. The objective is to
rearrange the tiles from an arbitrary initial configuration to a goal configuration, typically arranged in
ascending order. You can only slide a tile into the empty space, and the goal is to achieve the desired
configuration in as few moves as possible.
Algorithm:
2. Create a search tree where each node represents a state of the puzzle.
3. Use a search algorithm (e.g., A* search, Breadth-First Search) to traverse the search
tree, expanding nodes and generating successor states.
4. Maintain a priority queue or open list to explore the most promising nodes.
5. Continue the search until a goal state is reached or all possible states are explored.
Advantages:
Disadvantages :
1. The general form of the 8-puzzle problem is unsolvable, meaning not all initial
configurations can be solved.
3. Depending on the initial configuration, finding a solution may require a large number
of moves.
Department of Computer Science Engineering
Faculty of Engineering
Medi-Caps University Ajeet Singh Rajput
22
CS3EA10: Artificial Intelligence Experiment no-4
Complexity of 8-Puzzle Problem: The time and space complexity of solving the 8-puzzle
problem depends on the search algorithm used. In the worst case, the number of possible
states to explore can be vast, especially for larger puzzles. Heuristic search algorithms, like
A*, can significantly reduce the search space.
Code:
#include <iostream>
#include <vector>
#include <queue>
#include <map>
using namespace std;
const int N = 3;
struct State {
vector<vector<int>> board;
int moves;
int heuristic;
}};
} } }
return h;
int value = 1;
goal[i][j] = value;
value++;
} }
goal[N - 1][N - 1] = 0;
State start;
start.board = initial;
start.moves = 0;
start.heuristic = calculateHeuristic(initial);
pq.push(start);
distance[initial] = 0;
while (!pq.empty()) {
State current = pq.top();
pq.pop();
if (current.board == goal) {
return current.moves;
}
int x, y;
if (isValid(newX, newY)) {
State next = current;
swap(next.board[x][y], next.board[newX][newY]);
} } } }
return -1;
int main() {
cout << "Minimum number of moves to solve the puzzle: " << moves << endl;
} else {
return 0;
Output:
Definition: The Water Jug Problem is a classic mathematical puzzle that involves two jugs of different
capacities, usually referred to as "Jug X" and "Jug Y." The goal is to measure a specific amount of water
(referred to as "Z" liters) using these two jugs. The problem requires finding a sequence of actions, such
as filling, emptying, or pouring water between the jugs, to obtain the desired amount of water.
Example:
Your goal is to measure 2 liters of water using these jugs. The Water Jug Problem is to find a
series of actions to achieve this target.
Algorithm:
To solve the Water Jug Problem, you can use the Breadth-First Search (BFS) algorithm.
Here's a high-level overview of the algorithm:
1. Create a queue to store states of the jugs and an empty set to store visited states.
2. Start with an initial state (0, 0), where both jugs are empty.
3. Add this state to the queue.
4. While the queue is not empty:
Fill Jug X.
Fill Jug Y.
Empty Jug X.
Empty Jug Y.
Pour water from Jug X to Jug Y until Jug X is empty or Jug Y is full.
Pour water from Jug Y to Jug X until Jug Y is empty or Jug X is full.
If it's the target state (with Z liters in one of the jugs), return the sequence of
operations.
If it's not visited, add it to the queue and mark it as visited.
Advantages:
BFS guarantees that the solution found will be the shortest in terms of the number of
steps required to reach the target state.
It is a simple and straightforward way to solve this problem.
Disadvantages:
The algorithm can be inefficient for large values of jug capacities and target volumes,
as it explores many states.
It may not be the most efficient algorithm for finding solutions to this problem in real-
world scenarios.
Complexity:
The time complexity of the BFS algorithm is O(X * Y), where X and Y are the capacities of
the two jugs. The space complexity can be up to O(X * Y) as well.
Code:
#include <iostream>
#include <vector>
#include <algorithm>
if (step.second == 1) {
cout << "Pour water from jug X to jug Y." << endl;
} else if (step.second == 2) {
cout << "Pour water from jug Y to jug X." << endl;
} else if (step.second == 3) {
} else {
} } }
if (x == 0) {
x = jugX;
steps.push_back(make_pair(step++, 3));
} else if (y == jugY) {
y = 0;
steps.push_back(make_pair(step++, 2));
} else {
x -= pourAmount;
y += pourAmount;
steps.push_back(make_pair(step++, 1));
Department of Computer Science Engineering
Faculty of Engineering
Medi-Caps University Ajeet Singh Rajput
31
CS3EA10: Artificial Intelligence Experiment no-5
} }
return steps;
int main() {
if (solution.empty()) {
} else {
printSolution(solution);
return 0;
Output:
Defination: Given a set of cities and the distance between every pair of cities, the problem
is to find the shortest possible route that visits every city exactly once and returns to the
starting point. Note the difference between Hamiltonian Cycle and TSP. The Hamiltonian
cycle problem is to find if there exists a tour that visits every city exactly once. Here we
know that Hamiltonian Tour exists (because the graph is complete) and in fact, many such
tours exist, the problem is to find a minimum weight Hamiltonian Cycle.
For example, consider the graph shown in the figure on the right side. A TSP tour in the
graph is 1-2-4-3-1. The cost of the tour is 10+25+30+15 which is 80. The problem is a
famous NP-hard problem.
Examples:
Output of Given Graph:
minimum weight Hamiltonian Cycle :
10 + 25 + 30 + 15 := 80
Algorithm:
1. Consider city 1 as the starting and ending point. Since the route is cyclic, we can
consider any point as a starting point.
2. Generate all (n-1)! permutations of cities.
Advantages:
Disadvantages:
Time complexity: O(n!) where n is the number of vertices in the graph. This is because the
algorithm uses the next_permutation function which generates all the possible permutations
of the vertex set.
Auxiliary Space: O(n) as we are using a vector to store all the vertices.
Code:
#include <bits/stdc++.h>
#define V 4
vector<int> vertex;
if (i != s)
vertex.push_back(i);
int min_path =
INT_MAX; do {
int current_pathweight = 0;
int k = s;
current_pathweight += graph[k][vertex[i]]
k = vertex[i];
current_pathweight += graph[k][s];
} while (
next_permutation(vertex.begin(), vertex.end()));
return min_path;
int main()
{ 10, 0, 35, 25 },
{ 15, 35, 0, 30 },
int s = 0;
return 0;
Output:
Definition: Tower of Hanoi is a mathematical puzzle where we have three rods (A, B,
and C) and N disks. Initially, all the disks are stacked in decreasing value of diameter i.e.,
the smallest disk is placed on the top and they are on rod A. The objective of the puzzle is
to move the entire stack to another rod (here considered C), obeying the following simple
rules:
Examples:
Input: 2
Output: Disk 1 moved from A to B
Disk 2 moved from A to C
Disk 1 moved from B to C
Input: 3
Output: Disk 1 moved from A to C
Disk 2 moved from A to B
Disk 1 moved from C to B
Disk 3 moved from A to C
Disk 1 moved from B to A
Disk 2 moved from B to C
Disk 1 moved from A to C
Algorithm:
Shift ‘N-1’ disks from ‘A’ to ‘B’, using C.
Shift last disk from ‘A’ to ‘C’.
Shift ‘N-1’ disks from ‘B’ to ‘C’, using A.
Advantages:
Recursive Nature: The algorithm for solving the Tower of Hanoi problem is
naturally recursive, making it a good example of recursion in computer science
and mathematics.
Disadvantages:
Inefficiency: The Tower of Hanoi algorithm is not efficient for large numbers of
disks. It has exponential time complexity, O(2^n), which makes it impractical for a
large number of disks.
Limited Practical Use: The Tower of Hanoi problem has limited direct practical
applications, and its primary value is in teaching recursive thinking.
Time complexity: O(2N), There are two possibilities for every disk. Therefore, 2 * 2 * 2 * .
. . * 2(N times) is 2N
Code:
#include <bits/stdc++.h>
char aux_rod)
{ if (n == 0) {
return;
cout << "Move disk " << n << " from rod " << from_rod
int main()
int N = 3;
return 0;
}
Department of Computer Science Engineering
Faculty of Engineering
Medi-Caps University Ajeet Singh Rajput
41
CS3EA10: Artificial Intelligence Experiment no-7
Output:
Definition: The Monkey and Banana problem is a classic AI problem where a monkey is
placed in a room with a banana hanging from the ceiling. The monkey's goal is to reach the
banana. To do so, the monkey can perform various actions, such as moving around the room,
grabbing the banana, climbing a box, and pushing the box.
Example:
Consider a 4x4 grid room with coordinates (1,1) in the top left corner and (4,4) in the bottom
right corner. The initial positions are as follows:
In this example:
"B" represents both the box (on the floor) and the banana (hanging from the ceiling).
Algorithm:
The Monkey and Banana problem can be solved using a simple goal-based search algorithm,
such as depth-first search or breadth-first search. The algorithm involves the following steps:
Advantages:
A simple problem that can be used to teach search algorithms and AI concepts.
Provides a foundation for understanding goal-based planning and reasoning.
Disadvantages:
The problem's complexity depends on the initial positions of the monkey, banana, and box. In
the worst case, the monkey might need to explore the entire room to reach the banana,
resulting in a time complexity that can be exponential in the worst case.
Code:
#include <iostream>
class MonkeyBananaProblem {
public:
MonkeyBananaProblem() {
monkeyX = 1;
monkeyY = 1;
bananaX = 3;
bananaY = 3;
if (isValidPosition(newX, newY)) {
monkeyX = newX;
monkeyY = newY;
} }
return monkeyX;
return monkeyY;
return bananaX;}
return bananaY;
private:
return (x >= 1 && x <= roomWidth && y >= 1 && y <= roomHeight);
} };
MonkeyBananaProblem problem;
cout << "Monkey: (" << problem.getMonkeyX() << ", " << problem.getMonkeyY() << ")"
<< endl;
cout << "Banana: (" << problem.getBananaX() << ", " << problem.getBananaY() << ")"
<< endl;
while (!problem.isGoalState()) {
problem.moveMonkey(1, 0);
problem.moveMonkey(-1, 0);
cout << "Monkey: (" << problem.getMonkeyX() << ", " << problem.getMonkeyY() << ")"
<< endl;
cout << "Banana: (" << problem.getBananaX() << ", " << problem.getBananaY() << ")"
<< endl;
Department of Computer Science Engineering
Faculty of Engineering
Medi-Caps University Ajeet Singh Rajput
47
CS3EA10: Artificial Intelligence Experiment no-8
return 0;
Output:
Defination: The N-Queens problem is a classic combinatorial puzzle that involves placing N
queens on an NxN chessboard in such a way that no two queens threaten each other. In other
words, no two queens can be in the same row, column, or diagonal.
The N Queen is the problem of placing N chess queens on an N×N chessboard so that no
two queens attack each other.
For example, the following is a solution for the 4 Queen problem.
The expected output is in the form of a matrix that has ‘Q‘s for the blocks where queens are
placed and the empty spaces are represented by ‘.’ . For example, the following is the
output matrix for the above 4-Queen solution.
Algorithm:
1. Start in the leftmost column.
2. If all queens are placed, return true (base case).
3. Try all rows in the current column. For each row:
a. If the queen can be safely placed in this row and column, mark this cell as part of
the solution.
b. Recur to place queens in the next column.
c. If placing queens in the next column leads to a solution, return true.
d. If placing queens in the next column does not lead to a solution, unmark this cell
(backtrack) and return false.
4. If all rows have been tried and none worked, return false to trigger backtracking.
Advantages:
The N-Queens problem is a well-known problem used for teaching and learning
backtracking algorithms.
It has real-world applications in areas like scheduling, job assignment, and resource
allocation.
Disadvantages:
Complexity:
Code:
#include <bits/stdc++.h>
#define N 4
using namespace std;
void printSolution(int board[N][N])
{
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++)
if(board[i][j])
return true;
return true;
board[i][col] = 0;
}
}
return false;
}
bool solveNQ()
{
int board[N][N] = { { 0, 0, 0, 0 },
{ 0, 0, 0, 0 },
{ 0, 0, 0, 0 },
{ 0, 0, 0, 0 } };
if (solveNQUtil(board, 0) == false)
{
cout << "Solution does not exist";
return false;
}
printSolution(board);
return true;
}
int main()
{
solveNQ();
return 0;
}
Output:
Objective: Write a Program to Implement Mini-max and Alpha beta pruning algorithm.
Mini-max is a kind of backtracking algorithm that is used in decision making and game
theory to find the optimal move for a player, assuming that your opponent also plays
optimally. It is widely used in two player turn-based games such as Tic-Tac-Toe,
Backgammon, Mancala, Chess, etc.
In Minimax the two players are called maximizer and minimizer. The maximizer tries to get
the highest score possible while the minimizer tries to do the opposite and get the lowest
score possible.
Every board state has a value associated with it. In a given state if the maximizer has upper
hand then, the score of the board will tend to be some positive value. If the minimizer has
the upper hand in that board state then it will tend to be some negative value. The values of
the board are calculated by some heuristics which are unique for every type of game.
Example:
Consider a game which has 4 final states and paths to reach final state are from root to 4
leaves of a perfect binary tree as shown below. Assume you are the maximizing player and
you get the first chance to move, i.e., you are at the root and your opponent at next
level. Which move you would make as a maximizing player considering that your opponent
also plays optimally?
Since this is a backtracking based algorithm, it tries all possible moves, then backtracks and
makes a decision.
Maximizer goes LEFT: It is now the minimizers turn. The minimizer now has a choice
between 3 and 5. Being the minimizer it will definitely choose the least among both,
that is 3
Maximizer goes RIGHT: It is now the minimizers turn. The minimizer now has a choice
between 2 and 9. He will choose 2 as it is the least among the two values.
Being the maximizer you would choose the larger value that is 3. Hence the optimal move
for the maximizer is to go LEFT and the optimal value is 3.
Now the game tree looks like below :
The above tree shows two possible scores when maximizer makes left and right moves.
Algorithm:
1. If the game has reached a terminal state (win, lose, or draw), return the utility value.
2. If it's the player's turn, maximize the utility value by choosing the move with the highest
value.
3. If it's the opponent's turn, minimize the utility value by choosing the move with the lowest
value.
Advantages:
Disadvantages:
Time complexity : O(b^d) b is the branching factor and d is count of depth or ply of graph
or tree.
Space Complexity : O(bd) where b is branching factor into d is maximum depth of tree
similar to DFS.
Code:
#include<bits/stdc++.h>
if (depth == h)
return scores[nodeIndex];
if (isMax)
return max(minimax(depth+1, nodeIndex*2, false, scores, h),
minimax(depth+1, nodeIndex*2 + 1, false, scores, h));
else
int log2(int n)
int main() {
int n = sizeof(scores)/sizeof(scores[0]);
int h = log2(n);
cout << "The optimal value is : " << res << endl;
return 0;
Output:
Alpha-beta pruning can be applied at any depth of a tree, and sometimes it not only prune the
tree leaves but also entire sub-tree.
a. Alpha: The best (highest-value) choice we have found so far at any point
along the path of Maximizer. The initial value of alpha is -∞.
b. Beta: The best (lowest-value) choice we have found so far at any point along
c.
Department of Computer Science Engineering
Faculty of Engineering
Medi-Caps University Ajeet Singh Rajput
59
CS3EA10: Artificial Intelligence Experiment no-10
The Alpha-beta pruning to a standard minimax algorithm returns the same move as the
standard algorithm does, but it removes all the nodes which are not really affecting the final
decision but making algorithm slow. Hence by pruning these nodes, it makes the algorithm
fast.
α>=β
Algorithm:
4. Update alpha and beta values based on the player (maximizer or minimizer).
5. Prune branches that cannot affect the final decision (alpha-beta cutoff).
Advantages:
Disadvantages:
Assumes that the opponent plays optimally, so may not be as effective against
suboptimal opponents.
Implementation can be complex.
Complexity:
The time complexity of the alpha-beta pruning algorithm is O(b^(d/2)), where b is the
branching factor and d is the depth of the tree.
Code:
#include<bits/stdc++.h>
using namespace std;
const int MAX = 1000;
const int MIN = -1000;
break;
}
return best;
}
else
{
int best = MAX;
for (int i = 0; i < 2; i++)
{
int val = minimax(depth + 1, nodeIndex * 2 + i,
true, values, alpha, beta);
best = min(best, val);
beta = min(beta, best);
Output: