Theme NexT works best with JavaScript enabled
0%

人工智能-搜索算法

^ _ ^

Prerequisite

Problem

Firstly, you need to define the problem. It’s an abstract class for formal problem. You should subclass it and implements method of the class to fit different problem.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class Problem:

def __init__(self, initial, goal=None):
"""initial means initial state"""
self.initial = initial
self.goal = goal

def actions(self, state):
"""Return the actions that can be executed in the given
state. The result would typically be a list, but if there are
many actions, consider yielding them one at a time in an
iterator, rather than building them all at once."""
raise NotImplementedError

def result(self, state, action):
"""Return the state that results from executing the given
action in the given state. The action must be one of
self.actions(state)."""
raise NotImplementedError

def goal_test(self, state):
"""Return True if the state is a goal. """
if isinstance(self.goal, list):
return is_in(state, self.goal)
else:
return state == self.goal

def path_cost(self, c, state1, action, state2):
"""Return the cost of a solution path that arrives at state2 from
state1 via action"""
return c + 1

def value(self, state):
"""For optimization problems, each state has a value. Hill Climbing
and related algorithms try to maximize this value."""
raise NotImplementedError

Node

Sometimes we only want to search to get an answer, sometimes we also want to keep the path that we can track back from destination to origin.So we can define Node class to realize path recording.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
class Node:
"""A node in a search tree. Contains a pointer to the parent (the node
that this is a successor of) and to the actual state for this node. Note
that if a state is arrived at by two paths, then there are two nodes with
the same state. Also includes the action that got us to this state, and
the total path_cost (also known as g) to reach the node. Other functions
may add an f and h value; see best_first_graph_search and astar_search for
an explanation of how the f and h values are handled. You will not need to
subclass this class."""

def __init__(self, state, parent=None, action=None, path_cost=0):
"""Create a search tree Node, derived from a parent by an action."""
self.state = state
self.parent = parent
self.action = action
self.path_cost = path_cost
self.depth = 0
if parent:
self.depth = parent.depth + 1

def __repr__(self):
return "<Node {}>".format(self.state)

def __lt__(self, node):
return self.state < node.state

def expand(self, problem):
"""List the nodes reachable in one step from this node."""
return [self.child_node(problem, action)
for action in problem.actions(self.state)]

def child_node(self, problem, action):
"""[Figure 3.10]"""
next_state = problem.result(self.state, action)
next_node = Node(next_state, self, action, problem.path_cost(self.path_cost, self.state, action, next_state))
return next_node

def solution(self):
"""Return the sequence of actions to go from the root to this node."""
return [node.action for node in self.path()[1:]]

def path(self):
"""Return a list of nodes forming the path from the root to this node."""
node, path_back = self, []
while node:
path_back.append(node)
node = node.parent
return list(reversed(path_back))

# We want for a queue of nodes in breadth_first_graph_search or
# astar_search to have no duplicated states, so we treat nodes
# with the same state as equal. [Problem: this may not be what you
# want in other contexts.]

def __eq__(self, other):
return isinstance(other, Node) and self.state == other.state

def __hash__(self):
# We use the hash value of the state
# stored in the node instead of the node
# object itself to quickly search a node
# with the same state in a Hash Table
return hash(self.state)

BFS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def breadth_first_tree_search(problem):
"""
[Figure 3.7]
Search the shallowest nodes in the search tree first.
Search through the successors of a problem to find a goal.
The argument frontier should be an empty queue.
Repeats infinitely in case of loops.
"""

frontier = deque([Node(problem.initial)]) # FIFO queue

while frontier:
node = frontier.popleft()
if problem.goal_test(node.state):
return node
frontier.extend(node.expand(problem))
return None

DFS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def depth_first_tree_search(problem):
"""
[Figure 3.7]
Search the deepest nodes in the search tree first.
Search through the successors of a problem to find a goal.
The argument frontier should be an empty queue.
Repeats infinitely in case of loops.
"""

frontier = [Node(problem.initial)] # Stack

while frontier:
node = frontier.pop()
if problem.goal_test(node.state):
return node
frontier.extend(node.expand(problem))
return None

Graph-Based DFS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def depth_first_graph_search(problem):
"""
[Figure 3.7]
Search the deepest nodes in the search tree first.
Search through the successors of a problem to find a goal.
The argument frontier should be an empty queue.
Does not get trapped by loops.
If two paths reach a state, only use the first one.
"""
frontier = [(Node(problem.initial))] # Stack

explored = set()
while frontier:
node = frontier.pop()
if problem.goal_test(node.state):
return node
explored.add(node.state)
frontier.extend(child for child in node.expand(problem)
if child.state not in explored and child not in frontier)
return None

Graph-Based BFS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def breadth_first_graph_search(problem):
"""[Figure 3.11]
Note that this function can be implemented in a
single line as below:
return graph_search(problem, FIFOQueue())
"""
node = Node(problem.initial)
if problem.goal_test(node.state):
return node
frontier = deque([node])
explored = set()
while frontier:
node = frontier.popleft()
explored.add(node.state)
for child in node.expand(problem):
if child.state not in explored and child not in frontier:
if problem.goal_test(child.state):
return child
frontier.append(child)
return None