Heuristic Search

Heuristic Search
Blai Bonet
Depth-first search and iterated depth-first search
Universidad Sim´
on Bol´ıvar, Caracas, Venezuela
c 2015 Blai Bonet
Goals for the lecture
Dealing with time and space
Time: just wait: go for a coffee, go for lunch, go on a trip, . . .
• How to deal with time and space requirements
Space: buy more memory. Expensive . . . may work in some cases . . .
• Depth-first search: incomplete and suboptimal, but linear space
• Iterative depth-first search: incomplete*, optimal and linear space
c 2015 Blai Bonet
Lecture 6
It’s easier and cheaper to manage time than space!
Lecture 6
c 2015 Blai Bonet
Lecture 6
Depth-first search
Depth-first search for explicit graphs [CLRS]
1
void depth-first-search(Vertex root):
2
3
Depth-first search explores the search tree in depth-first manner
always expanding the most recent generated node
4
5
6
7
% initialization
foreach Vertex u
color[u] = White
distance[u] = ∞
parent[u] = null
8
Nodes can be ordered for expansion with a LIFO queue, but it is
easier to formulate the search recursively
9
10
% depth-first visit on root
dfs-visit(root)
11
12
Depth-first search is typically implemented as a tree-search algorithm
(i.e. minimal duplicate elimination)
13
14
15
16
17
18
19
20
c 2015 Blai Bonet
Lecture 6
void dfs-visit(Vertex u):
color[u] = Gray
% time of discovery
foreach Vertex v in adj[u]
if color[v] == White
parent[v] = u
distance[v] = distance[u] + 1
dfs-visit(v)
color[u] = Black
% time of finalisation
c 2015 Blai Bonet
Depth-first search: example
c 2015 Blai Bonet
Lecture 6
Depth-first search: example
Lecture 6
c 2015 Blai Bonet
Lecture 6
Depth-first search for implicit graphs
Advantages and disadvantages of depth-first search
Advantages:
– Easy to implement
1
2
3
% depth-first search without duplicate elimination
Node depth-first-search():
return dfs-visit(make-root-node(init()))
– Efficient in space: only need to store current branch and sibling
nodes along branch
Node dfs-visit(Node n):
if n.state.is-goal() return n
– Space requirement is linear in depth of graph (constant
branching)
4
5
6
7
8
9
10
11
foreach <s,a> in n.state.successors()
Node m = dfs-visit(n.make-node(s,a))
if m != null return m
Disadvantages:
12
13
return null
% failure: there is no path from node n to goal
– Incomplete (if tree is infinite, it can got stuck in an infinite branch)
– Suboptimal: it may return a suboptimal path to goal
Challenge: develop an optimal and linear-space algorithm
c 2015 Blai Bonet
Lecture 6
Iterative deepening depth-first search
c 2015 Blai Bonet
Lecture 6
Iterative deepening depth-first search: example
Iterative deepening depth-first search does:
– repeated depth-limited depth-first searches
– with increasing depth limits for each search
– until reaching a goal node
It is almost equivalent to breadth-first search but uses less memory:
– it visit the nodes within each iteration in a depth-first order
– but the nodes in the tree are discovered in a breadth-first order
It is a linear space algorithm and a tree-search algorithm
(i.e. it performs minimal or no duplicate pruning)
c 2015 Blai Bonet
Lecture 6
c 2015 Blai Bonet
Lecture 6
Iterative deepening depth-first search: example
Second iteration: depth-first search in sub-tree of height 1
First iteration: depth-first search in sub-tree of height 0
c 2015 Blai Bonet
Lecture 6
Iterative deepening depth-first search: example
Third iteration: depth-first search in sub-tree of height 2
c 2015 Blai Bonet
Iterative deepening depth-first search: example
c 2015 Blai Bonet
Lecture 6
Iterative deepening depth-first search: example
Fourth iteration: depth-first search in sub-tree of height 3
Lecture 6
c 2015 Blai Bonet
Lecture 6
Iterative deepening depth-first search: pseudocode
Iterative deepening depth-first search: example
1
2
3
Node iterative-deepening-depth-first-search():
Node root = make-root-node(init())
bound = 0
4
5
6
7
8
9
% perform depth-limited searches with increasing depth bounds
while true
Node n = bounded-dfs-visit(root,0,bound)
if n != null return n
bound = bound + 1
10
11
12
13
14
15
Node bounded-dfs-visit(Node n, unsigned d, unsigned bound):
% base cases
if d > bound return null
if n.state.is-goal() return n
16
17
Fifth iteration: depth-first search in sub-tree of height 4
18
19
20
21
c 2015 Blai Bonet
Lecture 6
% recursion
foreach <s,a> in n.state.successors()
Node m = bounded-dfs-visit(n.make-node(s,a),d+1,bound)
if m != null return m
return null
% failure: there is no path from node n to goal
c 2015 Blai Bonet
Alternative formulation
Lecture 6
Iterative deepening depth-first search: alternative
1
2
3
Each iteration of iterative deepening depth-first search must
guarantee:
Node iterative-deepening-depth-first-search():
Node root = make-root-node(init())
bound = 0
4
5
6
7
– It visits at least one new node
8
– It remains optimal (first goal found corresponds to an optimal path)
9
10
11
12
13
Instead of increasing the bound one-by-one:
% perform depth-limited searches with increasing depth bounds
while true
pair<Node,unsigned> p = bounded-dfs-visit(root,bound)
if p.first != null return p.first
bound = p.second
14
pair<Node,unsigned> bounded-dfs-visit(Node n, unsigned bound):
% base cases
if n.g > bound return (null,n.g)
if n.state.is-goal() return (n,n.g)
15
16
– Calculate the depth of the nodes generated but not expanded
17
18
– Set the bound so that next iteration expand those nodes
19
20
21
22
23
c 2015 Blai Bonet
Lecture 6
% recursion
t = ∞
foreach <s,a> in n.state.successors()
Node n’ = n.make-node(s,a)
pair<Node,unsigned> p = bounded-dfs-visit(n’,bound)
if p.first != null return p
t = min(t,p.second)
return (null,t)
% failure: there is no path from node n to goal
c 2015 Blai Bonet
Lecture 6
Analysis
Properties of iterative deepening depth-first search
Iterative deepening depth-first search does:
– Partially Complete: if goal is reachable, outputs a path (else it
doesn’t terminate if search tree is infinite)
– Complete depth-first searches for trees of depth 1, 2, 3, . . . , d
– The root is expanded d times, its children d − 1 times, its
grandchildren d − 2 times, . . .
– Optimality: it returns a shortest path
– Time complexity: O(bd ) (i.e. exponential in goal depth d)
# expanded nodes ≤ d + (d − 1)b + (d − 2)b2 + · · · + bd−1
=
d−1
X
(d − k)bk =
k=0
= bd
d
X
d
X
k=1
– Space complexity: O(bd) (i.e. linear in goal depth d)
kbd−k
k=1
∞
X
d
kb−k ≤ b
k=0
kb−k =
Time and space complexities calculated in canonical search tree
with branching factor b where shallowest goal appears at depth d
bd+1
(b − 1)2
c 2015 Blai Bonet
Lecture 6
Iterative deepening uniform-cost search
c 2015 Blai Bonet
Lecture 6
Iterative deepening uniform-cost search: pseudocode
1
2
3
Node iterative-deepening-cost-search():
Node root = make-root-node(init())
bound = 0
4
5
It is for uniform-cost search what iterative deepening depth-first
search is for breadth-first search:
6
7
8
9
– linear-space algorithm enforcing a uniform-cost discovery order
10
11
12
13
It performs cost-limited depth-first searches, increasing the cost
limit with each iteration until reaching a goal node
14
pair<Node,unsigned> cost-bounded-dfs-visit(Node n, unsigned bound):
% base cases
if n.g > bound return (null,n.g)
if n.state.is-goal() return (n,n.g)
15
16
17
18
19
20
21
22
23
c 2015 Blai Bonet
% perform cost-limited searches with increasing cost bounds
while true
pair<Node,unsigned> p = cost-bounded-dfs-visit(root,bound)
if p.first != null return p.first
bound = p.second
Lecture 6
% recursion
t = ∞
foreach <s,a> in n.state.successors()
Node n’ = n.make-node(s,a)
pair<Node,unsigned> p = cost-bounded-dfs-visit(n’,bound)
if p.first != null return p
t = min(t,p.second)
return (null,t)
% failure: there is no path from node n to goal
c 2015 Blai Bonet
Lecture 6
Summary
• In general, it is easier to deal with time than to deal with memory
• Depth-first search is incomplete and suboptimal but linear space
• Iterative deepening depth-first search: optimal for unit costs and
linear space
• Iterative deepening uniform-cost search: optimal for costs and linear
space
c 2015 Blai Bonet
Lecture 6