3203. Introduction to Artificial Intelligence

State Space Search

 

1. Problem solving as search

Intelligence is often displayed during problem-solving processes. In many situations, "to solve a problem" can be described as to change the current situation, step by step, from an initial state to a final state.

If each state is represented by a node, and each possible change is represented by a link, then a "problem" can be represented as a graph (the "state space"), with a "solution" corresponding to a path from the initial state to a final state.

In this way, a solution consists of a sequence of operations, each of which changes one state into another one, and the whole sequence changes the initial state into a final state in multiple steps.

Examples:

 

2. Search strategies

Given the graph representation of a problem, to write a problem-solving program means
  1. to choose a data structure to represent the graph,
  2. to decide a search strategy.
The data structure selection is influenced by the nature of the state space, as well as by the programming language used.

The search strategy should be correct, complete, and efficient. Sometimes we also want it to be able to find the optimal solution (such as the shortest path). A generic searching algorithm provides a framework for the relevant algorithms, which repeatedly expands a node in frontier. The direction of search can be forward (data-driven), backward (goal-driven), or bidirectional. Common search algorithms include Breadth-First and Depth-First, where the "frontier" data structure is a queue and a stack, respectively.

In depth-first search, if it is possible to move on both directions in a link, search can be done without the stack. The program can start at the root, then go all the way done, then back up, i.e. "backtrack" to the previous node to try another possibility.

 

3. Search in Prolog

In Prolog, a graph is often specified by a list of facts. For instance, a recursive search program path.pl is given here, with a sample directed graph specified using the move predicate for an edge:
path(Z, Z).
path(X, Y) :- move(X, W), not(been(W)), assert(been(W)), path(W, Y).

move(a, b).
move(b, a).
move(a, c).
move(c, d).
move(a, e).
move(d, f).
We can have the following sample run:
?- assert(been(a)), path(a,f).
true.

?- listing(been).
:- dynamic been/1.

been(a).
been(b).
been(c).
been(d).
been(f).

?- retractall(been(_)).
true.

?- assert(been(a)), path(a, X).
X = a ;
X = b ;
X = c ;
X = d ;
X = f ;
X = e ;
false.

?- assert(been(a)), path(a, X).
X = a ;
false.
The assert(been(a)) is used to prevent SWI-Prolog from complaining "undefined predicate" when not(been(W)) is called before any been fact is inserted.

From the result of trace, we can see that the search is depth-first.

The last call path(a, X) only gives one answer, because all the "been" facts inserted during the previous run are still in the database. To remove them, use retractall(been(_)).

Another program (path/3) that does not use "global variables" but a "local variable" to keep the visited node list is given here:

path(Z, Z, _).
path(X, Y, L) :- move(X, W), not(member(W, L)), path(W, Y, [W|L]).
Try it as the following in SWISH:
?- path(a, X, [a]).
and ask for all the answers. The result should be the same as above. If the goal is repeated, the result will be repeated, too, because this program does not change the database.

Other search programs in SWI-Prolog