Measuring Problem-Solving Performance (Section 3.3.2)

  • Completeness: Is algorithm guaranteed to find a solution if one exists?
  • Optimality: Will the algorithm find the optimal solution, the one with the lowest path cost?
  • Time complexity: How long does the algorithm take to find a solution?
  • Space complexity: How much memory does the algorithm need to perform the search?

Measuring complexity can be tricky. The complexity of an algorithm varies depending on the problem. To deal with this, the following definitions are often followed to characterize a general search problem and to express complexity in general.

  • branching factor: maximum number of successors of any node
  • depth: the shallowest goal node in number of steps or levels
  • maximum depth: the maximum depth of the search tree.

Time and space complexity for an algorithm can then be expressed as formulas involving these terms.

For example, consider a search tree. A search graph becomes a search tree if we do not explore repeated states. If every node in a search tree has $b$ successors, and the search progresses through depth $d$, how many nodes must be generated by a breadth-first search?

$$ b + (b)\, b + (b\, b)\, b \ldots = b + b^2 + b^3 + \cdots + b^d $$

Big-O notation is the highest power term, so time complexity is $O(b^d)$.

Space complexity is the number of nodes held in memory. We need to store all unexplored nodes, and all explored nodes to generate the solution path. So, if a breadth-first search has generated $d$ levels, there are $b^d$ nodes in the unexpanded set, and $b + b^2 + \cdots + b^{(d-1)}$ in the expanded set. Again, $b^d$ dominates this sum, so the space complexity is $O(b^d)$.

What is the complexity of depth-first search? For a state space with branching factor $b$ and maximum depth $m$, the number of nodes generated (time complexity) is $O(b^m)$ because the whole tree might need to be explored. However, space complexity is a nicer story. Once all descendants of a node have been expanded, that node can be removed from memory. Why? So, space complexity is $O(bm)$.

If a state space has $b=5$ and $m=10$ and the shallowest goal node is at $d=10$, breadth-first requires storage of $b^d = 5^{10} = 9,765,625$. (What tool would you use to calculate this?) Depth-first would require storage of $bm = 5 \times 10 = 50$ at the most. Time complexities are similar, unless $d << m$.

Two variations on depth-first implementation reduce its space complexity. One variation is backtracking depth-first search. If you have a way of knowing how to generate the next child of the same parent given a previous child (where previous and next are defined by left-to-right order of our simple graph, for example), then you only need to store one child of a parent. Explore the path from that child as deep as you can go. If goal is not found, back up one level from the deepest level and try the next child at that level. If no next child at that level, back up another level. This way only one path of nodes from the start node to the deepest level needs to be stored. Its space complexity is $O(m)$, rather than $O(bm)$ for our earlier definition of depth-first search.

The second variation requires a procedure to undo the effects of each action on a state. If this is available, then storage for only a single state is required, plus storage for a sequence of actions. To backtrack, the inverse of the most recent action is applied to the deepest state to get to its parent. So now we are storing one state and $O(m)$ actions. If the representation of actions is much smaller than the representation of the state, this is a huge win.