The assignment consists of three required tasks and one extra credit task involving depth-first and breadth-first search and an application of them to a particular problem.
Discussion: Artificial intelligence researchers often test theories of problem solving by modeling problems in a "blocks world", where state representation is simplified and possible actions are well defined. Consider a world in which there are two blocks, A and B, and three places (or locations), p, q and r, where blocks may be placed. The possible states in this world are:
+-------+ +-------+ +-------+ +-------+ +-------+ +-------+ S1| A | S2| | S3| | S4| B | S5| | S6| B | | B | | B A | | B A | | A | | A B | | A | | ----- | | ----- | | ----- | | ----- | | ----- | | ----- | | p q r | | p q r | | p q r | | p q r | | p q r | | p q r | +-------+ +-------+ +-------+ +-------+ +-------+ +-------+ +-------+ +-------+ +-------+ +-------+ +-------+ +-------+ S7| A | S8| | S9| | S10| B | S11| | S12| A | | B | | A B | | B A | | A | | A B | | B | | ----- | | ----- | | ----- | | ----- | | ----- | | ----- | | p q r | | p q r | | p q r | | p q r | | p q r | | p q r | +-------+ +-------+ +-------+ +-------+ +-------+ +-------+The rule of action in this world is that a block can be moved (by an idealized robot arm, say) and placed on another place or block provided no other block is on top of it. Suppose the problem is to come up with a sequence of moves which will get us from the initial state to the goal state as depicted below:
+-------+ +-------+
S1| A | S12| A |
| B | | B |
| ----- | | ----- |
| p q r | | p q r |
+-------+ +-------+
initial goal
It is immediately obvious to humans that a solution to this problem is a
three step solution that moves A from atop B to r, then
moves B from p to q,
and finally moves A from r to atop B. How could we
write a program to come up with this solution?
Pictorially, this graph could be represented as shown below:
+-------+
S1 | A |
| B |
| ----- |
| p q r |
+-------+
/ \
/ \
+-------+ +-------+
S2 | | S3 | |
| B A |-------------| B A |
| ----- | | ----- |
| p q r | | p q r |
+-------+ +-------+
/ \ \ \
/ \ \ \
+-------+ +-------+ \ +-------+
S4 | B | S5 | | \ S6 | B |
| A |-------------| A B | \ | A |
| ----- | | ----- | \ | ----- |
| p q r | | p q r | \ | p q r |
+-------+ +-------+ \ +-------+
/ | \ |
________/ | \ |
/ | \ |
+-------+ / +-------+ +-------+
S7 | A |/ S8 | | S9 | |
| B |-------------| A B | | B A |
| ----- | | ----- | | ----- |
| p q r | | p q r | | p q r |
+-------+ +-------+ +-------+
/ | / |
________/ | ________/ |
/ | / |
+-------+ / +-------+ / +-------+
S10| B |/ S11| |/ S12| A |
| A |-------------| A B |-------------| B |
| ----- | | ----- | | ----- |
| p q r | | p q r | | p q r |
+-------+ +-------+ +-------+
We can represent this graph using the adjacency-list method and then use the
depth-first or breadth-first search algorithms from Chapter 22 to find paths
within it.
The vertices V and edges E for this graph are contained in the file graphdata.txt, the beginning of which looks like the picture below. Note: as in the text, the vertices are represented by integers starting at 1; usually attributes are kept in arrays (which are of size |V|+1, and index 0 is ignored), with the vertices being indexes into the arrays (e.g. pi[5] is the predecessor of vertex 5, which might have the value 2). The first line of the file contains the number of vertices |V|. Each of the rest of the lines contains two integers u and v, where (u,v) is an edge in E:
| 12 | |
| 1 | 2 |
| 1 | 3 |
| 2 | 1 |
| 2 | 3 |
| 2 | 4 |
| 2 | 5 |
| 3 | 1 |
| 3 | 2 |
| 3 | 6 |
| 3 | 9 |
| 4 | 2 |
| 4 | 5 |
| . | . |
| . | . |
| . | . |
BFS(G, start) PRINT-PATH(G, start, end)The following output shows how the breadth-first search test works on the S1 -> S12. It is the (only) minimal solution to the problem.
+---------------------+
| |
| 1 -> 3 -> 9 -> 12 |
| |
+---------------------+
DFS(G, start) |> note addition of start node
for each vertex u in V[G] do
color[u] <- WHITE
pi[u] <- NIL
DFS-Visit(start) |> search from start only, not from all u in V
Note that although the DFS examples given in the text and in class were on
directed graphs, the DFS algorithms work just as well on undirected graphs.
The PRINT-PATH algorithm given in the text for printing paths in
breadth-first predecessor trees can also be used on depth-first predecessor
trees.
Testing depth-first
search on our blocks problem is completely analogous to the breadth-first case:
DFS(G, start) PRINT-PATH(G, start, end)Implement these depth-first search algorithms and apply them to some blocks world problems. The following output shows one possibility for how a depth-first search test works on the S1 -> S4 (you may get a different sequence).
+-----------------------------------------------------+
| |
| 1 -> 3 -> 9 -> 12 -> 11 -> 10 -> 8 -> 7 -> 5 -> 4 |
| |
+-----------------------------------------------------+
Obviously, this is not the shortest solution to the problem, although DFS
may come up with a shortest solution (even BFS can come up with
different shortest solutions on some graphs, but not this one). An intelligent
human would not seriously solve the problem by coming up with this path.
Helpful code: Here is a .h file for the Graph class graph.h and skeleton code for the Graph class implementation graph.cpp. Note: looking ahead, the Graph class has been designed to work with both weighted and unweighted graphs. Here is a .h file for the Queue class queue.h and code for the Queue class implementation queue.cpp. Here is a "driver" program that runs the tests: driver.cpp. Here is a "Makefile" that puts it all together: Makefile. When you download these files, be sure to remove the ".txt" suffixes (by moving them to the same file name without the ".txt").
+-------+ +-------+ +-------+ | C | | | | | | A | | C | | | | B | | B A | | B C A | . . . etc. | ----- | | ----- | | ----- | | p q r | | p q r | | p q r | +-------+ +-------+ +-------+Your final tasks for this part: