Aim: Write down two intelligent programs for the tic-tac toe problem.
Theory:
Tic-tac-toe (also known as noughts and crosses or Xs and Os) is a game for two
players, X and O, who take turns marking the spaces in a 3×3 grid. The player
who succeeds in placing three of their marks in a horizontal, vertical, or
diagonal row wins the game. The program draws the game board, asks the user
for the coordinates of the next mark, changes the players after every successful
move, and pronounces the winner. However, in order to fill the empty square,
the system must first verify all the possible moves of opponent to win the game
(i.e., a heuristic search must use). All these sequences require further production
rules. The control system decides which production rules to use, and in what
sequence. A magic square is a square array of numbers consisting of the
distinct positive integers 1, 2, ..., arranged such that the sum of the numbers in
any horizontal, vertical, or main diagonal line is always the same number.
Here, we will be using Target based Approach and Magic Squares to solve the
problem.
Algorithm-
This application is an example of a production system. Production systems are
applied to problem solving programs that must perform a wide-range of
searches. Production systems are symbolic AI systems. The difference
between these two terms is only one of semantics. A symbolic AI system may
not be restricted to the very definition of production systems, but they can't be
much different either.
Production systems are composed of three parts, a global database, production
rules and a control structure. Production rules (or simply productions) are
conditional if-then branches. In a production system, whenever a or condition in
the system is satisfied, the system is allowed to execute or perform a specific
action which may be specified under that rule. If the rule is not fulfilled, it may
perform another action. This can be simply paraphrased:
DATA (binded with initial global data base)
when DATA satisfies the halting condition do
begin
select some rule R that can be applied to DATA
return DATA (binded with the result of when R was applied to DATA)
end
For a scenario where a production system is attempting to find the best move in
a Tic Tac Toe, pattern matching is required to tell whether or not a condition is
satisfied. If the current state of a Tic Tac Toe matches the desired state (win
state or the solution to game), then anyone wins in game. However, if this case
is not so, the system must attempt an action that will contribute to manipulating
the global database, under the production rules in such a way that the Machine
(i.e Computer) will eventually be winner.
Code 1:
#include <bits/stdc++.h>
using namespace std;
int mark[10];
void printBoard()
{
cout<<"\n3: User 5: System 2: Empty\n";
for(int i=1;i<=3;i++)
{
for(int j=1;j<=3;j++)
{
if(j!=3)
cout<<mark[3*(i-1)+j]<<" - ";
else
cout<<mark[3*(i-1)+j];
cout<<endl;
}
}
bool checkwin(int a)
{
int a1, a2, a3=1, a4=1;
for(int i=1;i<=3;i++)
{
a1=1;a2=1;
for(int j=1;j<=3;j++)
{
a1*=mark[(i-1)*3+j];
a2*=mark[i+3*(j-1)];
if(i==j)a3*=mark[(i-1)*3+j];
if(i+j==4)a4*=mark[(i-1)*3+j];
if(a1==a)return 1;
if(a2==a)return 1;
f(a3==a)return 1;
if(a4==a)return 1; }
return 0;}
int poswin(int x)
int target=18;
if(x==5)target=50;
if(!checkwin(target))return 0;
if(target != 45)
//check rows
int i;
for(int i=1;i<=3;i++)
for(int j=1;j<=3;j++)
a1*=mark[(i-1)*3+j];
if(mark[(i-1)*3+j]==2){idx=i;jdx=j;}
}
//check columns
for(int i=1;i<=3;i++)
{
int a1=1;int idx=-1, jdx=-1;
for(int j=1;j<=3;j++)
a1*=mark[i+(j-1)*3];
if(mark[i+(j-1)*3]==2){idx=i;jdx=j;}
if(mark[1]*mark[5]*mark[9]==target)
if(mark[1]==2)return 1;
if(mark[5]==2)return 5;
if(mark[9]==2)return 9;
if(mark[3]*mark[5]*mark[7]==target)
if(mark[3]==2)return 3;
if(mark[5]==2)return 5;
if(mark[7]==2)return 7;
}
return 0;
else
if(mark[1]*mark[5]*mark[9]==target)
if(mark[1]==5)return 1;
if(mark[9]==5)return 9;
if(mark[3]*mark[5]*mark[7]==target)
if(mark[3]==2)return 3;
if(mark[7]==2)return 7;
return 0;
}
int main()
{
int i, x, o;
for(i=0;i<10;i++)mark[i]=2;
printBoard();
bool b=0;
for(i=0;i<8;i+=2)
cin>>x;
while(mark[x]!=2)
cin>>x;
mark[x]=3;
if(checkwin(27))
cout<<"\nCongrats!\n\n";
b=1;
break;
printBoard();
cout<<endl;
//computer turn
cout<<"Current Board\n";
if(i==0)
{
if(mark[1]==2 && mark[3]==2 && mark[7]==2 &&
mark[9]==2)mark[1]=5; else mark[5]=5;
else if(i==2)
{
x=poswin(3);
int y = poswin(1);
if(x==0)
if(y>0)
{ if(y == 1 || y==9)
if(mark[3] == 2) mark[3]=5;
else if(mark[7] ==2) mark[7] = 5;
else
{
if(mark[1] == 2) mark[1]=5;
}
else
{
if(mark[2]==2)mark[2]=5;
else if(mark[4]==2)mark[4]=5;
else if(mark[6]==2)mark[6]=5;
else if(mark[8]==2)mark[8]=5;
else mark[x]=5;
else if(i==4)
{
x=poswin(5);
int y=poswin(3);
if(x>0)
{ mark[x]=5;
cout<<"\nUser
Lost\n"; b=1;
break;
}
else if(y>0)
mark[y]=5; }
else if(potentialwin>0)
mark[potentialwin] = 5;
{}
else if(mark[7]==2)
mark[7]=5; }
else mark[3]=5;
else
x=poswin(5);
int y=poswin(3);
if(x>0)
{
mark[x]=5;
cout<<"\nUser Lost\n";
b=1;
break;
else if(y>0)
mark[y]=5; }
else
//mark random
for(int zz=1;zz<=9;zz++)
if(!mark[zz])
{ mark[zz]=5;
break;
}} }
printBoard();
cout<<endl;}
if(b==0)
{ cout<<"Enter Location 1-
9: "; cin>>x;
while(mark[x]!=2)
printBoard();
return 0;}
Output 1-
Discussion
In order to take a closer look to control structures let us look at a problem
involving the game. The Tic Tac Toe contains Nine Blank squares laid in a
three-by-three grid. Player fills square with either "X" or "O" (according to his
choice of mark). In next move computer appears in some, obfuscated state. The
goal of the production system is to reach some final state (the win state). This
can be obtained by successively moving squares into the empty position. This
system changes with every move of the square, thus, the global database
changes with time. The current state of the system is given as the position and
enumeration of the squares. This can be represented for example as a 9
dimensional vector with components 0, 1, 2, 3,..., 8, NULL, the NULL object
being the empty space.
The magic square logic can be used for any size of grid because it allows fast
checking of winning possibilities and in making the next move, after successfully
implementing both the approaches.
EXPERIMENT 2
Aim- To implement water jig problem using bfs and dfs
Theory
You are at the side of a river. You are given a m litre jug and a n litre jug where 0 < m
< n. Both the jugs are initially empty. The jugs don’t have markings to allow
measuring smaller quantities. You have to use the jugs to measure d litres of water
where d < n. Determine the minimum no of operations to be performed to obtain d
litres of water in one of jug.
The operations you can perform are-
1. Empty a Jug
2. Fill a Jug
3. Pour water from one jug to the other until one of the jugs is either empty or full.
The problem is solvable only when t is a multiple of gcd (a, b) and can be modelled as
search through a state space. The state space for this problem can be described as the
set of ordered pair of integers (x, y) such that x ∈ {0, 1, 2, …, a} and y ∈ {0, 1, 2, …,
b}. The initial state is (0, 0) and the goal states are (t, y) and (x, t) ∀ x, y.
Here, we will be using Recursion with Dynamic Programming (DP) (involving Stack
Data Structure) to solve the problem.
Algorithm 1
All we need now for a search procedure to work is a way to generate new states
(successors) from a given state. This is captured by production rules that specify how
and when a new state can be generated from a given state. For the water jug problem,
the following production rules are sufficient:
1. (x, y) -> (a, y) if x < a i.e., Fill the first jug if it is not already full
2. (x, y) -> (x, b) if y < b i.e., Fill the second jug if it is not already full
6. (x, y) -> (max(0, x + y – b), min(x + y, b)) if x > 0 i.e., Pour from first jug into
second jug until the second jug is full or the first jug is empty
Code 1-
#include <bits/stdc++.h>
using namespace std;
vector<pair<int, int> >ans;
bool func(int trgt, int j1, int j2, int cap1, int cap2,vector<vector<bool> >&vis) { if (vis[j1][j2])
return false;
vis[j1][j2] = true;
if (j1 == trgt && j2==0) {
ans.push_back(make_pair(j1,j2));
return true;
}
if (j2 == trgt && j1==0) {
ans.push_back(make_pair(j1, j2));
return true;}
bool done = false
done = func(trgt, cap1, j2, cap1, cap2, vis);
if (done) {
ans.push_back(make_pair(j1, j2));
return done;}
done = func(trgt, j1, cap2, cap1, cap2, vis);
if (done) {
ans.push_back(make_pair(j1, j2));
return done;}
int transfer = min(cap1 - j1, j2);
done = func(trgt, j1 + transfer, j2 - transfer, cap1, cap2, vis); if (done) {
ans.push_back(make_pair(j1, j2));
return done;
}
transfer = min(j1, cap2 - j2);
done = func(trgt, j1 - transfer, j2 + transfer, cap1, cap2, vis); if (done) {
ans.push_back(make_pair(j1, j2));
return done;
}
done = func(trgt,0,j2,cap1,cap2,vis);
if (done) {
ans.push_back(make_pair(j1, j2));
return done;
}
done = func(trgt, j1,0 , cap1, cap2, vis);
if (done) {
ans.push_back(make_pair(j1, j2));
return done; }
return false;}
int main()
{
int jug1, jug2;
cout<<"Enter capacity of jug 1: ";
cin >> jug1;
cout<<"Enter capacity of jug 2: ";
cin >> jug2;
int trgt;
cout<<"Enter volume to be measured: ";
cin >> trgt;
vector<bool> in(jug2 + 1, false);
vector<vector<bool> >vis(jug1+1,in);
bool poss=func(trgt,0,0, jug1, jug2,vis);
if (poss) {
cout<<"The states of jugs are-\n";
int lim = ans.size();
while (lim > 0) {
--lim;
cout << ans[lim].first << ", " << ans[lim].second << endl; }
} else {
cout << "Not possible";}
return 0;
}
Output 1-
Discussion- Strictly speaking, the conditions in the production rules are not required
e.g., we could fill an already full jug except that it won’t lead us anywhere and would
be wasteful in a tree search procedure where the visited states are not saved to prevent
revisiting.
Moreover, any search procedure (even BFS) can be applied to systematically search
from the initial state to one of the goal states through the state space.
Finding and learning- We were able to find out the minimum number of steps and the
associated sequence to obtain the desired amount of measurement using DFS
procedure.
Algorithm 2
All we need now for a search procedure to work is a way to generate new states
(successors) from a given state. This is captured by production rules that specify how
and when a new state can be generated from a given state. For the water jug problem,
the following production rules are sufficient:
1. (x, y) -> (a, y) if x < a i.e., Fill the first jug if it is not already full
2. (x, y) -> (x, b) if y < b i.e., Fill the second jug if it is not already full
3. (x, y) -> (0, y) if x > 0 i.e., Empty the first jug
4. (x, y) -> (x, 0) if y > 0 i.e., Empty the second jug
5. (x, y) -> (min (x + y, a), max(0, x + y – a)) if y > 0 i.e., Pour from second jug
into first jug until the first jug is full or the second jug is empty
6. (x, y) -> (max (0, x + y – b), min(x + y, b)) if x > 0 i.e., Pour from first jug into
second jug until the second jug is full or the first jug is empty.
Code 2-
#include <bits/stdc++.h>
using namespace std;
void waterJugBfs(int trgt, int cap1, int cap2, vector<vector<pair<int, int> > >&parent) { queue<pair<int,
int> >q;
pair<int, int> start = make_pair(0, 0), last;
q.push(start);
bool poss = false;
while (!q.empty() && !poss) {
pair<int, int> cur = q.front();
q.pop();
int j1 = cur.first, j2 = cur.second;
if (j1 == trgt && j2 == 0) {
last = cur;
poss = true; break; }
else if (j1 == 0 && j2 == trgt) {
last = cur;
poss = true; break; }
pair<int, int> child = make_pair(cap1, j2), unvis = make_pair(-1, -1); if
(parent[child.first][child.second] == unvis) {
parent[child.first][child.second] = cur;
q.push(child); }
child = make_pair(j1, cap2);
if (parent[child.first][child.second] == unvis) {
parent[child.first][child.second] = cur;
q.push(child);
}
int transfer = min(cap1 - j1, j2);
child = make_pair(j1 + transfer, j2 - transfer);
if (parent[child.first][child.second] == unvis) {
parent[child.first][child.second] = cur;
q.push(child); }
transfer = min(j1, cap2 - j2);
child = make_pair(j1 - transfer, j2 + transfer);
if (parent[child.first][child.second] == unvis) {
parent[child.first][child.second] = cur;
q.push(child); }
child = make_pair(0, j2);
if (parent[child.first][child.second] == unvis) {
parent[child.first][child.second] = cur;
q.push(child); }
child = make_pair(j1, 0);
if (parent[child.first][child.second] == unvis) {
parent[child.first][child.second] = cur;
q.push(child); }}
if (poss) {
cout << "The states of jugs are-\n";
vector<pair<int, int> >ans;
while (last.first != 0 || last.second != 0) {
ans.push_back(last);
last = parent[last.first][last.second];
}
ans.push_back(make_pair(0, 0));
reverse(ans.begin(), ans.end());
pair<int, int>p;
for (int i = 0; i<ans.size(); ++i) {
p = ans[i];
cout << p.first << ", " << p.second << endl;
}}
else {
cout << "Not possible";
}}
int main()
{
int jug1, jug2;
cout << "Enter capacity of jug 1: ";
cin >> jug1;
cout << "Enter capacity of jug 2: ";
cin >> jug2;
int trgt;
cout << "Enter volume to be measured: ";
cin >> trgt;
vector<pair<int, int> > in(jug2 + 1, make_pair(-1, -1));
vector<vector<pair<int, int> > > parent(jug1 + 1, in);
waterJugBfs(trgt, jug1, jug2, parent); return 0;}
Output 2-
Discussion- Strictly speaking, the conditions in the production rules are not required
e.g., we could fill an already full jug except that it won’t lead us anywhere and would
be wasteful in a tree search procedure where the visited states are not saved to prevent
revisiting.
Moreover, any search procedure (even DFS) can be applied to systematically search
from the initial state to one of the goal states through the state space.
Finding and learning- We were able to find out the minimum number of steps and
the associated sequence to obtain the desired amount of measurement using BFS
procedure.
EXPERIMENT 3
Aim: Write down program to implement least cost search for 8 puzzle problem.
Theory:
The 8 puzzle consists of eight numbered, movable tiles set in a 3x3 frame. One cell of
the frame is always empty thus making it possible to move an adjacent numbered tile
into the empty cell. The program is to change the initial configuration into the goal
configuration. To solve a problem using a production system, we must specify the
global database the rules, and the control strategy. For the 8 puzzle problem that
correspond to the problem states, moves and goal. In this problem each tile
configuration is a state. The set of all configuration in the space of problem states or
the problem space, there are only 3, 62, 880 different configurations. For the 8-puzzle,
a straight forward description is a 3X3 array of matrix of numbers. The initial global
database is this description of the initial problem state.
Algorithm:
#define N 3
struct Node
{ Node* parent;
int mat[N][N];
int x, y;
int cost;
int level;
};
printf("\n");
swap(node->mat[x][y], node->mat[newX][newY]);
node->cost = INT_MAX;
node->level = level;
node->x = newX;
node->y = newY;
return node;
int count = 0;
count++;
return count;
if (root == NULL)
return;
printPath(root->parent);
printMatrix(root->mat);
printf("\n");
}struct comp
{
return (lhs->cost + lhs->level) > (rhs->cost + rhs->level);
};
int final[N][N])
pq.push(root);
while (!pq.empty())
pq.pop();
if (min->cost == 0)
printPath(min);
return;
min->y + col[i],
min->level + 1, min);
pq.push(child);
int main()
{
int initial[N][N] =
{1, 2, 3},
{5, 6, 0},
{7, 8, 4}
};
int final[N][N] =
int x = 1, y = 2;
solve(initial, x, y, final);
return 0;
Result/Output:
Result: The code is successfully implemented and the output is shown here.
Discussion:
Branch and bound or least cost search for an answer node speeds up the search by
using an “intelligent” ranking function, also called an approximate cost function to
avoid searching in sub-trees that do not contain an answer node. It is similar to
backtracking technique but uses BFS-like search.
Finding and Learning: The least cost search algorithm can be used to solve 8-Puzzle
problem efficiently.
EXPERIMENT 4
Aim: Write down program to implement steps of A* for 8 puzzle problem.
Theory: The 8 puzzle consists of eight numbered, movable tiles set in a 3x3 frame.
One cell of the frame is always empty thus making it possible to move an adjacent
numbered tile into the empty cell. The program is to change the initial configuration
into the goal configuration. To solve a problem using a production system, we must
specify the global database the rules, and the control strategy. For the 8 puzzle problem
that correspond to the problem states, moves and goal. In this problem each tile
configuration is a state. The set of all configuration in the space of problem states or
the problem space, there are only 3, 62, 880 different configurations. For the 8-puzzle,
a straight forward description is a 3X3 array of matrix of numbers. The initial global
database is this description of the initial problem state.
Algorithm:
Step 2: Get all the possible states in which puzzle can be.
Step 5: Pick the next state which has the lowest cost in the open list
Step 6: Start repeating the steps from 1 to 6 unless you are into the final state, or no
more states to examine (in this case, no solution exists).
Step 7: Once you have the final state, backtrack to the start node and that would be the
optimal path to find the solution.
Code:
#include <bits/stdc++.h>
using namespace std;
typedef vector<vector<int>> vvi;
int calc(vvi & bo, vvi & best) {
int ret = 0;
vector<pair<int, int>> use(9);
for(int i = 0; i < 3; i ++) {
for(int j = 0; j < 3; j ++) {
use[bo[i][j]] = {i, j};
}
}
for(int i = 0; i < 3; i ++) {
for(int j = 0; j < 3; j ++) {
ret += abs(use[best[i][j]].first - i) + abs(use[best[i][j]].second - j);
}
}
return ret;
}
void print(vvi & bo) {
for(int i = 0; i < 3; i ++) {
for(int j = 0; j < 3; j ++) {
cout << (bo[i][j] ? to_string(bo[i][j]) : " ") << " \n"[j == 2];
}
}
cout << '\n';
}
int dx[] = {0, 1, -1, 0};
int dy[] = {1, 0, 0, -1};
vector<vvi> gen(vvi & bo) {
vector<vvi> ret;
for(int i = 0; i < 3; i ++) {
for(int j = 0; j < 3; j ++) if(not bo[i][j]) {
for(int k = 0; k < 4; k ++) {
int ni = i + dx[k], nj = j + dy[k];
if(ni >= 0 and nj >= 0 and ni < 3 and nj < 3) {
swap(bo[ni][nj], bo[i][j]);
ret.emplace_back(bo);
swap(bo[ni][nj], bo[i][j]);
}
}
}
}
return ret;
}
struct state {
int c = 0, s = 0;
vvi p, cur;
};
struct cmp {
bool operator() (state & l, state & r) {
return ((l.c == r.c) ? l.s > r.s : l.c > r.c);
}
};
int main() {
priority_queue<state, vector<state>, cmp> pq;
map<vvi, int> vis;
map<vvi, vvi> from;
map<vvi, int> timecost;
vvi bo(3, vector<int>(3));
vvi best = bo;
state og;
og.c = calc(bo, best);
og.cur = bo;
pq.emplace(og);
while(!pq.empty()) {
auto ele = pq.top(); pq.pop();
if(not vis[ele.cur]) {
vis[ele.cur] = 1;
from[ele.cur] = ele.p;
timecost[ele.cur] = ele.s;
if(not ele.c) {
break;
}
auto children = gen(ele.cur);
state te;
te.p = ele.cur;
te.s = ele.s + 1;
for(auto child : children) {
te.c = calc(child, best);
te.cur = child;
pq.emplace(te);
}
}
}
if(vis[best]) {
stack<vvi> use;
vvi te = best;
while(te.size()) {
use.emplace(te);
te = from[te];
}
while(not use.empty()) {
print(use.top());
use.pop();
}
} else {
cout << "Not Possible";
}
}
Result/Output:
Result: The code is successfully implemented and the output is shown here.
Finding and Learning: The A* algorithm can be used to solve 8-Puzzle problem
efficiently
EXPERIMENT 5
Aim- To solve cryptarithmatic problem, eg-
SEND
+MORE
-----------
MONEY
Theory:
Cryptarithmetic problems are where numbers are replaced with alphabets. By using
standard arithmetic rules, we need to decipher the alphabet.
General Rules:
1. Each alphabet takes only one number from 0 to 9 uniquely.
2. Two single digit numbers sum can be maximum 19 with carryover. So carry
over in problems of two number addition is always 1.
3. Try to solve left most digit in the given problem.
4. If a × b = kb, then the following are the possibilities
(3 × 5 = 15; 7 × 5 = 35; 9 × 5 = 45) or (2 × 6 = 12; 4 × 6 = 24; 8 × 6 = 48)
Here is a sample problem:
SEND + MORE = MONEY
A solution to the puzzle is
S = 9, R = 8, O = 0, M = 1, Y = 2, E = 5, N = 6, D= 7
That is,
9567+
1085
------------
10652
Algorithm-
1. Set distinct values for all variables(S, E,N,D,M,O,R,Y). The values should be
between 0 and 9.
2. Seeing the problem M and S can’t be Zero as they are first digit of the numbers.
3. For each set of values do
• Calculate send=send=S*1000+E*100+N*10+D,
more=M*1000+O*100+R*10+E and money=M*10000+O*1000+N*100+E*10+Y;
• Check if send+more=money if yes return solution else continue
4. If no solution found, return no solution.
Code
#include<iostream>
Output-
Findings and learning- Solving problems like these involves understanding some
basic principles and rules of addition and a lot of trial and error.
EXPERIMENT 6
Aim- to implement AO* algorithm
Theory
A solution in an AND-OR tree is a sub tree whose leafs are included in the goal set.
Cost function:sum of costs in AND node
f(n) = f(n1) + f(n2) + …. + f(nk)
The Depth first search and Breadth first search given earlier for OR trees or graphs
can be easily adopted by AND-OR graph. The main difference lies in the way
termination conditions are determined, since all goals following an AND nodes must
be realized; where as a single goal node following an OR, node will do. So, for this
purpose we are using AO* algorithm. Like A* algorithm here we will use two arrays
and one heuristic function.
OPEN:
It contains the nodes that has been traversed but yet not been marked solvable or
unsolvable.
CLOSE:
It contains the nodes that have already been processed.
PROCESS:
▪ Traverse the graph (from the initial node) following the best current path.
▪ Pick one of the unexpanded nodes on that path and expand it. Add its
successors to the graph and compute f for each of them
▪ Change the expanded node’s f value to reflect its successors. Propagate
the change up the graph.
▪ Reconsider the current best solution and repeat until a solution is found.
Algorithm-
Step 1: Place the starting node into OPEN.
Step 2: Compute the most promising solution tree say T0.
Step 3: Select a node n that is both on OPEN and a member of T0. Remove it from
OPEN and place it in CLOSE
Step 4: If n is the terminal goal node then leveled n as solved and leveled all the
ancestors of n as solved. If the starting node is marked as solved then success and
exit.
Step 5: If n is not a solvable node, then mark n as unsolvable. If starting node is
marked as unsolvable, then return failure and exit.
Step 6: Expand n. Find all its successors and find their h (n) value, push them into
OPEN.
Step 7: Return to Step 2.
Step 8: Exit
Code-
#include <bits/stdc++.h>
using namespace std;
struct Node {
char id;
int cost;
vector < pair < Node *, int > > or_list;
vector < pair < Node *, int > > and_list;
Node *par;
Node(char x, int c) {
id = x;
cost = c;
par = NULL;
}
};
Node* input_graph(){
queue<Node*> q;
char id;
int tempCost;
cout<<"Enter the node id: "<<endl;
cin>>id;
cout<<"Enter the cost: "<<endl;
cin>>tempCost;
Node* root = new Node(id,tempCost);
// root.par = NULL;
q.push(root);
int pathCost;
while(q.empty()!=true){
Node* temp =q.front(); q.pop();
int andnum;
cout<<"Enter number of nodes in AND list of node "<<temp->id<<endl; cin>>andnum;
for(int i=0;i<andnum;i++){ cout<<"Enter the node
id: "<<endl; cin>>id;
cout<<"Enter the cost: "<<endl;
cin>>tempCost;
Output-
Discussion-
Advantages:
It is an optimal algorithm.
If traverse according to the ordering of nodes. It can be used for both OR and
AND graph.
Disadvantages:
Sometimes for unsolvable nodes, it can’t find the optimal path. Its complexity is
than other algorithms.
Findings and learning- AO* is useful for searching game trees, problem solving etc.
but in most cases, more domain specific search algorithms (e.g. alpha-beta pruning
for game trees, general or domain specific planning algorithms) are used instead.
In particular, AI uses knowledge-intensive approaches and in practical applications
heavy use is made of domain specific knowledge or problem conditions to produce
better (faster or more optimal solutions).
EXPERIMENT 7
Aim: Compare the different search algorithms on suitable data set.
Theory:
Hill Climbing Algorithm:
Hill Climbing is a technique to solve certain optimization problems. In this technique,
we start with a sub-optimal solution and the solution is improved repeatedly until some
condition is maximized. The idea of starting with a sub-optimal solution is compared
to starting from the base of the hill, improving the solution is compared to walking up
the hill, and finally maximizing some condition is compared to reaching the top of the
hill. Hence, the hill climbing technique can be considered as the following phases −
Constructing a sub-optimal solution obeying the constraints of the problem
Improving the solution step-by-step
Improving the solution until no more improvement is possible
Hill Climbing technique is mainly used for solving computationally hard problems. It
looks only at the current state and immediate future state. Hence, this technique is
memory efficient as it does not maintain a search tree.
A* Algorithm:
A* is a search algorithm which explores a graph by expanding the most promising
node chosen according to a specified rule. Best-first search estimates the promise of
node n by a heuristic evaluation function f(n) which, in general, may depend on the
description of n, the description of the goal, the information gathered by the search up
to that point, and most important, on any extra
knowledge about the problem domain and also on g(n) which depends on the distance
of the current state from the initial state. This g(n) term is not present in Best First
Search.
Algorithm:
A* Algorithm:
function A*(start, goal)
closedSet := {}
openSet := {start}
cameFrom := an empty map
gScore := map with default value of Infinity
gScore[start] := 0
fScore := map with default value of Infinity
fScore[start] := heuristic_cost_estimate(start, goal)
while openSet is not empty
current := the node in openSet having the lowest fScore[] value
if current = goal
return reconstruct_path(cameFrom, current)
openSet.Remove(current)
closedSet.Add(current)
for each neighbor of current
if neighbor in closedSet
continue
if neighbor not in openSet
openSet.Add(neighbor)
tentative_gScore := gScore[current] + dist_between(current, neighbor)
if tentative_gScore >= gScore[neighbor]
continue // This is not a better path.
cameFrom[neighbor] := current
gScore[neighbor] := tentative_gScore
fScore[neighbor] := gScore[neighbor] + heuristic_cost_estimate(neighbor, goal)
return failure
function reconstruct_path(cameFrom, current)
total_path := [current]
while current in cameFrom.Keys:
current := cameFrom[current]
total_path.append(current)
return total_path
Discussion:
Hill Climbing Best First Algorithm A* Algorithm
Visited nodes are Visited Nodes are Visited Nodes are
not stored here. stored here. stored here.
Only compares the Compares the Compares the
neighbours of the neighbours of all open neighbours of all open
current nodes. nodes.
node.
Compares Compares Heuristic Value. Compares Heuristic
Heuristic Value. Value
+ Value of effort to
reach that node
F(n) = f(n) F(n) = f(n) F(n) = f(n) + g(n)
It may or may not find It will always find a It will always find a
a solution. solution, if there exists solution, if there
one. exists one.
It is a fast algorithm. Does not take into Does not work for And-
account the effort to Or graphs.
reach that node.
EXPERIMENT 8(a)
Aim: To write prolog program to compute factorial of a no.
Theory- In mathematics, the factorial of a non-negative integer n, denoted by n! is the
product of all positive integers less than or equal to n. For example,
4! =4*3*2*1=24The value of 0! is 1, according to the convention for an empty product
Algorithm-
Step 1: Start
Step 2: Read: Take input N
Step 3: If N == 0 then Print: Fact = 1 and Exit
Step 4: Call FACTORIAL (Fact, N – 1)
Step 5: Set Fact = N*Fact
Step 6: Return
Code-
factorial(0, 1).
factorial(N, F) :-
N > 0,
N2 is N -1,
factorial(N2, R),
F is R * N.
Output-
Discussion- This program consists of two clauses. The first clause is a unit clause,
having no body. The second is a rule, because it does have a body. The body of the
second clause is on the right side of the ‘: -' which can be read as "if". The body
consists of literals separated by commas ',' each of which can be read as "and". The
head of a clause is the whole clause if the clause is a unit clause, otherwise the head
of a clause is the part appearing to the left of the colon in ‘: -'.
Findings and learning- The factorial operation is encountered in many areas of
mathematics, notably in combinatorics, algebra, and mathematical analysis. Its
most basic occurrence is the fact that there is n! ways to arrange n distinct objects
into a sequence (i.e., permutations of the set of objects).
EXPERIMENT 8(b)
Aim- To write prolog program to compute area and circumference of a circle
Algorithm-
Step 1: Start
Step 2: Read: Take input radius R.
Step 3: If R<0, Exit.
Step 4: Set A = 3.14*R*R
Step 5: Set C = 6.28*R
Step 6: Display Area and Circumference of the Circle
Code-
func(R):-
R>0,
A is 3.14*R*R,
C is 6.28*R,
write('The Area is '),
write(A),
nl,
write('The circumference is '),
write(C).
Output-
Discussion-
Program logic is expressed in terms of relations, and a computation is initiated by
running a query over these relations. Relations and queries are constructed using
Prolog's single data type, the term. Relations are defined by clauses. Given a query,
the Prolog engine attempts to find a resolution refutation of the negated query.
Findings and learning- The circumference of a circle is the edge or rim of a circle
itself. It is the equivalent of 'perimeter' for a circle.