csc405 lecture notes 2020-2021 Search
csc405 lecture notes 2020-2021 Search
1.1 Introduction
A goal and a set of means for achieving the goal, is called a problem; in other words, a problem is a collection of
information that an agent will use to decide what to do. The process of exploring the various action sequences that
may lead to a solution to the problem, and then choosing the best one, is called search. We consider in this section
how a problem-solving agent can decide what to do by systematically considering the outcomes of various
sequences of actions that it might take.
Figure 1: Simplified roadmap of Romania (adapted from Russel & Norvig (1995))
We illustrate the processes involved by considering the following problem (see map in Figure 1). Assume the
agent represents a holiday maker currently in the city of Arad, Romania. The agent has a ticket to fly out of the
city of Bucharest the next day. The ticket is non-refundable, and the agent’s visa is about to expire. If the agent
misses the flight of tomorrow, then the next available seat on a plane will only be in about 2 months time because
this is the peak of the travel season. Because of the seriousness of the current situation, the agent should adopt the
goal of driving to Bucharest so that it can catch the flight of tomorrow. Such a goal helps the agent by limiting its
search space (i.e., the various things the agent has to take into consideration before arriving to a solution). For
example, the agent can out rightly reject all actions that will prevent it from reaching Bucharest on time. Goal
formulation based on the current situation, is the first step in problem solving.
What is a goal? Consider there to be a number of world states. Goal states are a set of world states that satisfy the
goal. Agent actions cause transitions between the world states. Some of these transitions cause the agent to reach
goal states. The job of the agent is thus to find those transitions that lead to goal states.
The next task for the agent is problem formulation. What sorts of actions should the agent consider? Should it
consider the possible transitions in world states that result from moving an inch at a time, or should the agent
consider the transitions that cause it to drive from one city to the next? Obviously, the first of these options is
inappropriate for this problem, as the amount of computations and memory requirements for such fine-grained
processing is beyond the capabilities of any computer. Problem formulation is the process of deciding what
actions and states to consider. In this example, our agent will consider actions at the level of driving from one city
to another.
1.3.1.1 8-Puzzle
An instance of this problem is shown in Figure 3. The puzzle comprises a 3 × 3 board with eight numbered tiles
and a blank space. A tile adjacent to the blank space can slide into the space. The aim is to reach the goal state on
the right of the figure.
This problem can be greatly simplified by noting that it is easier to use operators such as: the blank space changes
position with the tile to its left (see Figure 4), instead of using operators such as: move the 4 tile into the blank
space. The reason for this is that there are fewer of the former type of operator, and so the state space for the
problem would be much more manageable.
The state space for this problem in the case where there are three disks is given in Figure 7.
Figure 8: Partial search tree for route finding from Arad to Bucharest
How can the nodes of the search tree be represented? One way is to use the following five components:
• The state in the state space to which the node corresponds
• The node in the search tree that generated this node (i.e., the parent node)
• The operator that was applied to generate the node
• The number of nodes on the path from the root to this node, i.e., the depth of the node
• The path cost of the path from the initial node to this node
The collection of nodes that are waiting to be expanded is called the fringe or frontier. This collection of nodes
can be stored in a queue; the search strategy is a function that selects the next node to be expanded from this
collection of nodes.
Breadth-first search is guaranteed to find a solution if it exists (i.e., the algorithm is complete). If several solutions
exist, the shallowest ones are found first. If the path cost is a non-decreasing function of the depth of the node, the
solution is optimal.
Breadth-first search has some very serious
drawbacks though, related to the time and
space it takes to complete the search.
Consider for example, a situation with a
branching factor of b, i.e., where every
state can be expanded to yield b new
states. So, b nodes are generated at the first
level, each of the b nodes generates b other
nodes at the second level for a total of b 2
nodes generated at the second level, b3
nodes generated at the third level, etc. This
gives a maximum of: 1 + b + b 2 +…+bd
nodes expanded for a tree of path height d.
The time complexity of the algorithm is
therefore exponential (i.e., O(bd)). The Figure 10: Illustrating time and space complexities of breadth-first search
space complexity is similarly O(bd)
because all the leaf nodes must be maintained in memory at the same time. Assuming a branching factor of 10 and
that it takes an average of 1 ms to expand one node, and that each node requires 100 bytes of storage, Figure 10
demonstrates the explosion in time and space complexity of the algorithm.
We notice from Figure 10 that execution time is a major problem with this algorithm for solutions that require a
search tree with a large depth. And as bad as execution time is, memory requirement is an even bigger problem.
While many people can wait 18 minutes for an important solution, those of them with low-end computers do not
have sufficient RAM to run the algorithm. And while some users can even wait up to 31 hours to get a really
important solution, very few users can have the luxury of a computer with 11 GB of RAM.
Figure 11: Breadth-first search example, and corresponding FIFO queue evolution
Order of generation of queue: ABCDEFGHIJKLMNOPQRSTUV
Order of expansion of nodes: ABCDEFGHIJKLMNOPQRSTUV
Figure 12: Depth-first search trees for left subtree of a binary tree rooted at Node 1
The memory requirements of depth-first search are modest. The algorithm only need store a single path from the
root to a leaf node, along with the remaining unexpanded sibling nodes for each node on the path. If the branching
factor is b and the maximum depth is m, depth-first search requires storage of bm nodes (cf. minimum b d required
for breadth-first search). Using the same assumptions as in Figure 11, we see that for a depth of 12, the space
requirement for depth-first search is 12 kilobytes (cf. 111 terabytes for breadth-first search!).
The time complexity of depth-first search for a maximum depth of m is O(b m) in the worst case. But the algorithm
can be faster than breadth-first search in problems that have many solutions because there is a good chance of
Example
Figure 13: Depth-first search example, and corresponding LIFO queue evolution
Order of generation of stack: ABCDEFKLMGHINORSTVJPQU
Order of expansion of nodes: ABEKLFMCGHNORSVTIDJPQU
The order of expansion of nodes is illustrated in Figure 14. The order of expansion of states is similar to breadth-
first, except that some states are expanded multiple times. Expanding some states multiple times may seem too
wasteful, but as we illustrate below, that is not really the case.
Total # of expansions in breadth-first search to depth d with branching factor b is
1 + b + b2 + b3 +…+ bd-2 + bd-1 + bd
If b = 10 and d = 5, this gives 1 + 10 + 100 + 1,000 + 10,000 + 100,000 = 111,111
In iterative deepening search, the nodes at the bottom level are expanded once, those at the next level up expanded
twice, etc., up to the root node which is expanded d + 1 times. The total number of expansions is thus
(d + 1)1 + (d)b + (d – 1)b2 +…+ 3bd-2 + 2bd-1 + 1bd
If b = 10 and d = 5, this gives 6 + 50 + 400 + 3,000 + 20,000 + 100,000 = 123,456, only about 11% more
expansions than breadth-first or depth-first search.
The time complexity of iterative deepening is still O(b d) as in the strategies we saw before, and the space
complexity O(bd) as in depth-first search.
1
The word heuristic means to find or to discover. A heuristic for a problem is a process that may solve a given problem but offers no
guarantees of doing so. In the area of search algorithms, it refers to a function that provides an estimate of the cost of a solution.
Figure 17 shows the stages of the algorithm from Arad to Bucharest. As before, the Arad node is expanded to
Sibiu, Timisoara, and Zerind. The next node to be expanded is Sibiu, since it has the shortest straight line distance
to the goal. This is followed by Fagaras, which leads to Bucharest.
Figure 17: Stages in a greedy search from Arad to Bucharest using the hSLD heuristic function
In this example, the algorithm leads to a minimal search cost as the goal state is reached without ever expanding a
node that is not on the solution path. Notice however that the solution is not optimal as a lower cost path: Sibiu,
Rimnicu Vilcea, Pitesi, Bucharest, can be found (418 km vs. 450 km). This path was not found because Fagaras
has a shorter straight line distance to Bucharest than Rimnicu Vilcea. The algorithm prefers to take the biggest bite
towards the goal without worrying about whether it will be the best in the long run, hence the name greedy.
1.4.2.3 A* Search
As we saw in Section 1.4.1.5, uniform cost search minimizes the cost g(n) of the path so far. It is also complete
and optimal, but it can be very inefficient because of the large number of nodes that may need to be expanded to
determine their associated costs. We also saw in Section 1.4.2.2 that greedy search can cut the estimated cost h(n)
to a goal significantly, but is unfortunately neither complete nor optimal.
A* search aims at combining the strategies used in greedy search and uniform cost search to capitalize on the
advantages of each by simply combining their evaluation functions: f(n) = g(n) + h(n).
Because g(n) is the path cost from the start node to node n, and h(n) is the estimated cost of the cheapest path from
node n to the goal, f(n) effectively is an estimate of the cost of the cheapest solution through node n to the goal.
It can be proven that if function h never overestimates the cost to reach the goal, A* is complete and optimal. In
such a case, h is an admissible heuristics, and like all admissible heuristics, is optimistic in the sense that it
assumes that the cost of solving the problem is actually less than it actually is. If h is admissible, then f(n) never
overestimates the actual cost of the best solution through n.
Figure 18 illustrates the behaviour of the A* algorithm. We note that along any path from the root, the f-cost never
decreases. This means that the f exhibits monotonicity.
Figure 20 shows the search tree of a much more trivial game than Tic-Tac-Toe. The possible moves for MAX are
A1, A2, A3, and the possible replies for MIN are A11, A12, A13, etc. This simple game ends after one move each
by MAX and MIN, i.e., the game is one move deep, consisting of two half-moves or two ply. The utilities
computed at the terminal states of the game range from 2 to 14.
Figure 20: A 2-ply game tree generated by minimax algorithm. The D nodes are moves by MAX, and the Ñ nodes moves by MIN.
Terminal nodes show utility value for MAX computed by the rules of the game (i.e., utility function); the utilities of the other nodes
are computed by the minimax algorithm from the utilities of their successors. MAX’s best move is A 1, and MIN’s best reply is A11