You are on page 1of 25

ОТЧЕТ

о переводе статьи
«Control and implementation of state space search»
(25 000 печатных знаков)

Abstract

This article describes problem solving as search through a set of problem


states. It is shown that the state space model of problem solving allows graph
theory to be used as a tool for designing and analyzing intelligent programs. It is
dealt with a general backtracking graph search algorithm as well as algorithms for
both depth-first and breadth-first search. Attempts are made to algorithms for
heuristic search. The article is devoted to narrow range of readers that research in
artificial intelligence.

Summary

The article headlined “Control and implementation of state space search” is


written by George F. Luger and William A. Stubblefield. It’s taken from the
scientific book “Artificial Intelligence. Structures and Strategies for Complex
Problem Solving”. It is published in 1998.
The article is about control and implementation of state space search. The
purpose of the article is to provide reader some information about state space
search. The author starts by telling the readers about data and control structures
used to implement state space search. The article is divided into 3 parts.
The first part deals with recursion-based search. In mathematics, a recursive
definition uses the term being defined as part of its own definition. In computer
science, recursion is used to define and analyze both data structures and
procedures.
The second part is about pattern-directed search. Apply recursive search to a
space of logical inferences. The result is a general search procedure for predicate
calculus.
The third part touches upon pattern-search algorithm. Although the initial
version of “pattern_search” defined the behavior of a search algorithm for
predicate calculus expressions. These include the order with which the algorithm
tries alternative matches and proper handling of the full set of logical operators.
In conclusion the article reads about complete version of “pattern.search”,
which returns the set of unifications that satisfies each subgoal.

Questions

 Special
1. How can recursive algorithm help with search in program?

2. What are the two algorithms that used for search in program?

3. What definitions that used to implement state space search do you know?

4. Who wrote “Artificial Intelligence. Structures and Strategies for Complex


Problem Solving”?

“Do you think” question


5. Why do you think about using recursive algorithm in depth search?

 Alternative
6. Did George F. Luger’s book release in 1998 or 1988?

 General
7. Does recursion repeat itself?

 Tag-question
8. Recursive search is a problem-solving technique, isn’t it?
Examples on grammatical phenomena

«Passive voice»

1. These operations are supported by program languages.


2. AI problem-solving technique was researched.
3. These operations are supported by both LISP and PROLOG are described in
detail in the chapters on each language.
4. Book “Artificial Intelligence. Structures and Strategies for Complex Problem
Solving” was printed in 1998.
5. Depth-first search as just presented does not utilize the full power of recursion.
6. These results are then combined (summed) to produce the final result.
7. These subproblems are then solved recursively, with the separate solutions being
recombined into a final answer.
8. Backtracking is effected when all descendants of a state fail to include a goal.

Term list

1. priority queue Приоритетная очередь для алгоритмов поиска


2. production systems Продукционные системы модель, основанная на
правилах, позволяет представить знание в виде
предложений типа «Если (условие), то
(действие)».
3. recursive search Рекурсивный поиск, который повторяет сам себя.
Заранее задается условие выхода из рекурсивного
поиска, для того чтобы он не зациклился.
4. AI problem-solving Модель «классной доски» — это технология
technique, the совместного решения задач.
blackboard
5. search space Пространство поиска возможных решений.
6. pattern-directed Алгоритм поиска по образцу
7. subgoal Подцель
Artificial Intelligence. Structures and Strategies for Complex Problem Solving
George F. Luger and William A. Stubblefield. All rights reserved.
© Addison Wesley Longman, Inc.1998.

CONTROL AND IMPLEMENTATION OF STATE SPACE SEARCH

If we carefully factor out the influences of the task environments from the influences of the
underlying hardware components and organization, we reveal the true simplicity of the
adaptive system. For, as we have seen, we need postulate only a very simple information
processing system in order to account for human problem solving in such tasks as chess, logic,
and cryptarithmetic. The apparently complex behavior of the information processing system in
a given environment is produced by the interaction of the demands of the environment with a
few basic parameters of the system, particularly characteristics of its memories.

—A. Newell and H. A. Simon, "Human Problem Solving"

What we call the beginning is often the end


And to make an end is to make a beginning.
The end is where we start from...

T. S. Eliot, "Four Quartets"

5.0 Introduction

Chapters 3 and 4 represented problem solving as search through a set of problem states.
The state space model of problem solving allows graph theory to be used as a tool for designing
and analyzing intelligent programs. Chapter 3 defined a general backtracking graph search
algorithm as well as algorithms for both depth-first and breadth-first search. Chapter 4 presented
algorithms for heuristic search. The following definitions characterize the data and control
structures used to implement state space search:
1. Representation of a problem solution as a path from a start state to a goal.
2. Search to test systematically alternative paths to a goal.

3. Backtracking to allow an algorithm to recover from paths that fail to find a goal.
4. Lists to keep explicit records of states under consideration.
a. The open list allows the algorithm to explore untried states if necessary.
b. The closed list of visited states allows the algorithm to implement loop
detection and avoid repeating fruitless paths.
5. Implementation of the open list as a stack for depth-first search, a queue for breadth-
first search, and a priority queue for best-first search.
Chapter 5 introduces higher-level techniques for implementing search algorithms. The
first of these, recursive search, implements depth-first search with backtracking in a more
concise, natural fashion than in Chapter 3 and forms the basis for many of the algorithms in this
text, Section 5.1. Recursive search is augmented through the use of unification to search the state
space generated by predicate calculus assertions. This pattern- directed search algorithm. Section
5.2, is the basis of PROLOG, see Chapter 9, and many of the expert system shells discussed in
Chapter 6. Next, in Section 5.3 we introduce production systems, a general architecture for
pattern-directed problem solving that has been used extensively both to model human problem
solving and to build expert systems and other AI applications. In Section 5.4 we show how
predicate calculus and pattern matching may be used in robot planning or problem solving across
time periods. Finally we present another AI problem-solving technique, the blackboard.

5.1 Recursion-Based Search


5.1.1 Recursion
In mathematics, a recursive definition uses the term being defined as part of its own
definition. In computer science, recursion is used to define and analyze both data structures and
procedures. A recursive procedure consists of:
1. A recursive step in which the procedure calls itself to repeat a sequence of actions.
2. A terminating condition that stops die procedure from recurring endlessly (the
recursive version of an endless loop).
Both these components are essential and appear in all recursive definitions and
algorithms. Recursion is a natural control construct for data structures that have no definite size,
such as lists, trees, and graphs, and particularly appropriate for state space search.
A simple example of recursion is an algorithm for determining if an item is a member of
a list Lists are ordered sequences of elements and fundamental building blocks for computer
science data structures. Lists have already been used as an alternative syntax for predicate
calculus expressions (Chapter 2) and to implement the open and closed lists in the search
algorithms of Chapter 3. A procedure for testing whether an element is a member of a list is
defined recursively by:

This procedure first tests whether list is empty; if so, the algorithm returns fail. Other-
wise, it compares item to the first element of list; if these are equal, the procedure halts with
success. These are the terminating conditions of die procedure. If neither terminating condition is
met, member removes the first element from list and calls itself on the shortened list. In this
fashion, member examines each element of the list in turn. Note that because list is finite and
each step reduces its size by one element, the algorithm halts.
This algorithm uses two fundamental list operations: one that returns the first element
(the head) of a list and a tail operation, which returns the list with its first element removed.
When coupled with recursion, these operations are the basis for higher-level list operations such
as member. These operations are supported by both LISP and PROLOG and are described in
detail in the chapters on each language.
When supported by a programming language, recursion offers all the power of теме
traditional control constructs such as loops and conditional branching. In other words, anything
that can be done using explicit iteration can be done recursively. The benefit of recursive
formulations is greater clarity and compactness of expression. Mathematical notations such as
logic or functions do not support such concepts as sequencing, branching, and iteration; instead,
they use recursion to indicate repetition. As recursion is easier to describe mathematically than
explicit iteration, it is easier to analyze formally the correctness and complexity of recursive
algorithms. Recursive formulations are also used frequently by systems that automatically
generate or verify programs, and they play an important role in implementing compilers and
interpreters. More important, however, is the power and naturalness of recursion as a tool for
implementing AI problem-solving strategies such as graph search.

5.1.2 Recursive Search


A direct translation of the depth-first search algorithm of Chapter 3 into recursive form
illustrates the equivalence of recursion and iteration. This algorithm uses global variables closed
and open to maintain lists of states:

Breadth-first search can be designed with virtually the same algorithm, that is, by
retaining closed as a global data structure and by implementing the open list as a queue rather
than as a stack.
Depth-first search as just presented does not utilize the full power of recursion. It is
possible to simplify the procedure further by using recursion itself (rather than an explicit open
list) to organize states and paths through the state space. In this version of the algorithm, a global
closed list is used to detect duplicate states and prevent loops, and the open list is implict in the
activation records of the recursive environment

Rather than generating all children of a state and placing them on an open list this
algorithm produces the child states one at a time and recursively searches the descendants of
each child before generating its sibling. Note that the algorithm assumes an order to the state
generation operators. In recursively searching a child state, if some descendant of that state is a
goal, the recursive call returns success and the algorithm ignores the siblings. If the recursive call
on the child state foils to find a goal, the next sibling is generated and all of its descendants are
searched. In this fashion, the algorithm searches the entire graph in a depth-first order. The
reader should verify that it actually searches the graph in the same order as the depth-first search
algorithm of Section 3.2.3.

The omission of an explicit open list is made possible through recursion. The
mechanisms by which a programming language implements recursion include a separate
activation record (Aho and Ullman 1977) of each recursive call. Each activation record captures
the local variables and state of execution of each procedure call. When the procedure is called
recursively with a new state, a new activation record stores its parameters (the state), any local
variables, and the current state of execution. In a recursive search algorithm, the series of states
on the current path are recorded in the sequence of activation records of the recursive calls. The
record of each call also indicates the last operation used to generate a child state; this allows the
next sibling to be generated when needed.
Backtracking is effected when all descendants of a state fail to include a goal, causing the
recursive call to fail. This returns foil to the procedure expanding the parent state, which then
generates and recurs on the next sibling. In this situation, the internal mechanisms of recursion
do the work of the open list used in the iterative version of the algorithm. The recursive
implementation allows die programmer to restrict his or her point of view to a single state and its
children rather than having to explicitly maintain an open list of states. The ability of recursion to
express global concepts in a closed form is a major source of its power.
State space search is an inherently recursive process, To find a path from a current state
to a goal, move to a child state and recur. If that child state does not lead to a goal, try its siblings
in order. Recursion breaks a large and difficult problem (searching the whole space) into smaller,
simpler pieces (generate the children of a single state) and applies this strategy (recursively) to
each of them. This process continues until a goal state is discovered or the space is exhausted.
Symbolic integration, discussed in Section 3.3.3, is an excellent example of the power of
a recursive approach to search problems. When attempting to integrate an expression, it applies
either a substitution or a decomposition to the expression, replacing it with one (in the case of a
substitution) or more (in the case of a decomposition) simpler subproblems. These subproblems
are then solved recursively, with the separate solutions being recombined into a final answer. For
example, after applying the rule that the integral of a sum equals the sum of the integrals of the
terms, it recursively attempts to integrate each term. This may lead to further decompositions or
substitutions, until each term has been integrated. These results are then combined (summed) to
produce the final result Recursion is a natural tool for systematically decomposing a problem and
recombining partial solutions into a final answer.
In the next section, this recursive approach to problem solving is extended into a
controller for a logic-based problem solver that uses unification and inference to generate and
search a space of logical relations. The algorithm supports the and of multiple goals as well as
back chaining from a goal to premises.
5.2 Pattern-Directed Search
In Section 5.2 we apply recursive search to a space of logical inferences; the result is a
general search procedure for predicate calculus.
Suppose, for example, we want to write an algorithm that determines whether a predicate
calculus expression is a logical consequence of some set of assertions. The algorithm must find a
sequence of inferences that produce the goal expression. This suggests a goal- directed search
with the initial query forming the goal, and modus ponens defining the transitions between
states. Given a goal (such as p(a)>, the algorithm uses unification to select the implications
whose conclusions match the goal (e.g., q(X) —» p(X)>. Because the algorithm treats
implications as potential rules for solving the query, they are often simply called rules. After
unifying the goal with the conclusion of the implication (or rule) and applying the resulting
substitutions throughout the rule, the rule premise becomes a new goal (q(a)). This is called a
subgoal. The algorithm then recurs on the subgoal. If a subgoal matches a fact in the knowledge
base, search terminates. The series of inferences that led from the initial goal to the given facts
prove the truth of the original goal.
In the function pattern_search, search is performed by a modified version of the
recursive search algorithm that uses unification, Section 2.3.2, to determine when two
expressions match and modus ponens to generate the children of states. The current focus of the
search is represented by the variable current__goal. If curren_goal matches with a fact, the
algorithm returns success. Otherwise the algorithm attempts to match Current_goal with the
conclusion of some rule, recursively attempting to solve the premise. If Current_goal does not
match any of the given assertions, the algorithm returns fail. This algorithm also handles
conjunctive goals such as are often found in the premise of a rule.
For simplicity, this algorithm does not address the problem of maintaining consistency
among the variable substitutions produced by unification. This is important when solving
conjunctive queries with shared variables (as in p(X) л q(X) ). Not only must both conjuncts
succeed, but they must succeed with unifiable bindings for X, Section 2.3.2. This problem is
addressed at the end of this section.
The major advantage of using general methods such as unification and modus ponens to
generate states is that the resulting algorithm may search any space of logical inferences. The
specifics of a problem are described using predicate calculus assertions. Thus, we have a means
of separating problem-solving knowledge from its control and implementation on the computer.
pattern_search provides our first implementation of the separation of knowledge and control.

5.2.1 Example: Recursive Search in the Knight's Tour Problem


The use of predicate calculus with a general controller to solve search problems is
illustrated through an example: a reduced version of the knight's tour problem. In the game of
chess, a knight can move two squares either horizontally or vertically followed by one square in
an orthogonal direction as long as it does not move off the board. There are thus at most eight
possible moves that the knight may make (Figure 5.1).
As traditionally defined, the knight*s tour problem attempts to find a series of legal
moves in which the knight lands on each square of the chessboard exactly once. This problem
has been a mainstay in the development and presentation of search algorithms. The example
given in this chapter is a simplified version of the knight's tour problem: is there a series of legal
moves that will take the knight from one square to another on a reduced-size (3 x 3) chessboard?
(The details of the full knight's tour problem on an 8 x 8 chessboard are discussed in Examples
5.3.2 and 5.3.3.)
Figure 5.2 shows a 3 x 3 chessboard with each square labeled with integers 1 to 9. This
labeling scheme is used instead of the more general approach of giving each space a row and
column number in order to further simplify the example. Because of the reduced size of the
problem, we simply enumerate the alternative moves rather than developing a general move
operator. The legal moves on the board are then described in predicate calculus using a predicate
called move, whose parameters are the starting and ending squares of a legal move. For example.
move(1,8) takes the knight from the upper left-hand corner to the middle of the bottom row. The
predicates of Figure 5.2 enumerate all possible moves for the 3 x 3 chessboard.
These predicates form the knowledge base for the knight's tour problem. As an example of how
unification is used to access this knowledge base, we test for the existence of various moves on the board.
To determine whether there is a move from 1 to 8, call pattem_search(move(1,8)). Because this goal
unifies with move(1,8) in the knowledge base, the result is success, with no variable substitutions required.
Another request might be to find where the knight can move from a particular location, such as square
2. The goal move(2,X) unifies with two different predicates in the knowledge base, with the substitutions
of {7/X} and {9/X}. Given the goal move(2,3), the response is foil, because no move(2,3) exists in the
knowledge base. The goal query move(5,Y) also fails because no assertions exist that define a move from
square 5.
The next task in constructing a search algorithm is to devise a general definition for a
path of successive moves around the board. This is done through the use of predicate calculus
implications. These are added to the knowledge base as rules for creating paths of successive
moves. To emphasize the goal-directed use of these rules, we have reversed the direction of the
implication arrow; i.e., the rules are written as Conclusion Premise.
For example, a two-move path could be formulated as:

This rule says that for all locations X and Y, a two-move path exists between them if
there exists a location Z such that the knight can move from X to Z and then move from Z to Y.
The general path2 rule can be applied in a number of ways. First, it may be used to
determine whether there is a two-move path from one location to another. If pattem.search is
called with the goal path2(1,3), it matches the goal with the consequence of the rule
path2(X,Y), and the substitutions are made in the rule's premise; the result is a specific rule that
defines the conditions required for the path:

pattern.search then calls itself on this premise. Because this is a conjunction of two
expressions, pattern_search will attempt to solve each subgoal separately. This requires not
only that both subgoals succeed but also that any variable bindings be consistent across subgoals.
Substituting 8 for Z allows both subgoals to succeed.
Another request might be to find all locations that can be reached in two moves from
location 2. This is accomplished by giving pattern_search the goal path2(2,Y). Through a
similar process, a number of such substitutions may be found, including {6/Y} and {2/Y} (with
intermediate Z being 7) and {2/Y} and {4/Y} (with intermediate location 9). Further requests
could be to find a two-move path from a number to itself, from any number to 5, and so on.
Notice here one of the advantages of pattern-driven control: a variety of queries may be taken as
the initial goal.
Similarly, a three-move path is defined as including two intermediate locations that are
part of the path from the initial location to the goal. This is defined by:

This clause can solve such goals as path3(1,2), path3(1 ,X), or even path3(X,Y).
Tracing the results of these queries is left as an exercise.
It soon becomes evident that the path moves are the same for a path of any length, simply
requiring die proper number of intermediate places to "land." It is also evident that the path
moves could be stated in terms of each other, such as

This suggests the single, general recursive role:


The last path rule can be used to determine whether a path of any length exists. The rule
may be stated as "to find a path from one square to another, first make a move from the starting
square to an intermediate location and then find a path from the intermediate to the final square."
This recursive "path" rule is incomplete in that it includes no terminating condition. Any
attempt to solve a goal involving the path predicate would fail to halt because each attempt to
solve the rule premise would lead to another recursive call on path(Z,Y). There is no test in the
rule to determine whether the desired goal state is ever reached. This can be remedied by adding
the clause path(X,X) to the knowledge base. Because path(X,X) will unify only with predicates
such as path(3,3) or path(5,5), it defines the desired terminating condition. The general recursive
path definition is then given by two predicate calculus formulas:

Note once again the elegance and simplicity of the recursive formulation. When
combined with the recursive control provided by pattern_search, these rules will search the space
of possible paths in the knight's tour problem. Combined with the move rules, this yields the
complete problem description (or knowledge base):

It is important to note that the solution to the problem is implemented through both the
logical descriptions that define the state space and the use of pattem_search to control search of
that space. Although the path rule is a satisfactory definition of a path, it does not tell us how to
find that path. Indeed, many undesirable or meaningless paths around the chessboard also fit this
definition. For example, without some way to prevent loops, the goal path(1,3) could lead to a
path that simply goes back and forth between 1 and 8, instead of finding the correct path from 1
to 8 to 3. Both the loop and the correct path are logical consequences of the knowledge base.
Similarly, if the recursive rule is tried before the terminating condition, the fact that path(3,3)
should terminate the search could be overlooked, allowing the search to continue meaninglessly.

5.2.2 Refining the Pattern-search Algorithm


Although the initial version of pattern_search defined the behavior of a search algorithm
for predicate calculus expressions, several subtleties must still be addressed. These include the
order with which the algorithm tries alternative matches and proper handling of the full set of
logical operators (л, v, and -»). Logic is nondeterministic: it defines a space of possible
inferences but does not tell a problem solver how to make the useful ones.
To reason with predicate calculus, we need a control regime that systematically searches
the space, avoiding meaningless paths and loops. A control algorithm such as pattern_search
must try alternative matches in some sequential order. Knowing this order allows the program
designer to control search by properly ordering rules in the knowledge base. The simplest way to
define such an order is to require that the algorithm tiy rules and facts in the order in which they
appear in the knowledge base. In the knight's tour, this ensures that the terminating condition
(path(X,X)) is tried before the recursive step.
A second issue is the existence of logical connectives in the rule premises: e.g.,
implications of the form. As will be recalled from the discussion of and/or graphs, an л operator
indicates that both expressions must be shown to be true for the entire premise to be true. In
addition, the conjuncts of the expression must be solved with consistent variable bindings. Thus,
to solve p(X) л q(X), it is not sufficient to solve p(X) with the substitution {a/X} and q(X) with
the substitution {b/X}. Both must be solved with the same or unifiable bindings for X. An or
operator, on the other hand, indicates that either expression must be found to be true. The search
algorithm must take this into account
The last addition to the algorithm is the ability to solve goals involving logical negation
(-«). pattern_search handles negated goals by first solving the operand of the If this subgoal
succeeds, then pattern.search returns fail. If the operand fails, then pattern.search returns an
empty substitution set, indicating success. Note that even though a subgoal may contain
variables, the result of solving its negation may not contain any substitutions. This is because -n
can succeed only if its operand fails', hence, it cannot return any bindings for the operand.
Finally, the algorithm should not return success but should return the bindings involved
in the solution. Consider the goal move(1 ,X); the substitutions {6/X} and {8/X} are an essential
part of the solution.
The complete version of pattern.search, which returns the set of unifications that satisfies each
subgoal, is:
Перевод

Управление поиском и его реализация в пространстве состояний

Если мы аккуратно отделим влияние среды. в которой выполняется задача. от влияния


аппаратных компонентов и организации, то получим истинную простоту адаптивной
системы. Как мы видели, для описания способа решения человеком таких задач, как
шахматы, логика и криптоарифметика, необходимо постулировать только очень
простую систему обработки информации. Очевидно сложное поведение системы
обработки информации. Очевидно сложное поведение системы обработки информации
в данной среде обусловлено взаимодействие требований среды с несколькими основными
параметрами системы, в частности, характеристиками её блоков памяти.

- А.Ньюэлл и Н.А. Саймон, решение проблем человеком, 1972

То. что мы называем началом, часто будет концом.


Приходя к концу, мы приходим к началу.
Конец — это наше начало...

— Т. С. Элиот (Т. S. Eliot), Четыре квартета

5.0. Введение
В главах 3 и 4 представлены задачи, решаемые истодом поиска в пространстве со-
стояний. Этот подход "пространства состояний" к решению задачи позволяет использо-
вать теорию графов для сознания и анализа компьютерных пробами. В главе 3 определен
общий алгоритм поиска и возвратами, а также алгоритмы поиска в глубину и в ширину.
Эвристический поиск был представлен в главе 4. Следующие понятия характеризуют
данные и управляющие структуры, используемые для реализации поиска в пространстве
состояний.
1.Представление решения задачи в виде пути от начального состояния к целевому.
2. Алгоритм поиска, ссистематически проверяющим наличие альтернативных
путей к цели.
3. Поиск с возвратами или другой механизм, позволяющий выходить из тупиковых
состояний и находить путь к цели.
4. Списки, содержащие точную информацию о рассматриваемых в данный момент
состояниях, в том числе:
a. список open, позволяющий алгоритму в случае необходимости
исследовать ранее не рассмотренные состояния;
b. список closed, содержащий рассмотренные состояния. Он позволяет
алгоритму избегать зацикливаний и учитывать тупиковые пути.
5. Список open можно рассматривать, как стек для поиска в глубину, очередь для
поиска в ширину или приоритетную очередь для “жадного” алгоритма поиска.
В данной главе вводится высокоуровневые методы для построения алгоритмов
поиска. Первый из них — рекурсивный поиск, определяющий поиск в глубину с
возвратами более кратким н естественным способом, чем в главе 3. Этот механизм
составляет основу многих алгоритмов в разделе 5.1. Применение унификации в процессе
поиска в пространстве состояний расширяет возможности рекурсивного поиска. Этот
алгоритм поиска но образцу (раздел S3) положен в основу языка PROLOG, описанного в
главе 14, и многих экспертных систем, рассмотренных в главе 7.Раздел 5.3 посвящен гак
называемым продукционным системам. Это общая архитектура для решения образно-
ориентированных проблем, которая широко используется как для моделирования
человеческого образа мышления при решении многих задач, так и для построения
экспертных систем и других приложений ИИ. Наконец, в разделе 5.4 рассматривается еще
одно представление для решения задач искусственного интеллекта — так называемая
модель классной доски (blackboard).

5.1 Рекурсивный поиск


5.1.1 Рекурсия
В математике рекурсивное определение объекта — это его описание в терминах
частей этого же определения. В информатике рекурсия используется для определения и
анализа структур данных и процедур. Рекурсивная процедура состоит из следующих
этапов.
1. Рекурсивный шаг: вызов процедуры из этой же процедуры для повторения
последовательности действий.
2. Условие, которое обеспечивает выход из процедуры и таким образом предотвра-
щает зацикливание (рекурсивная версия бесконечного цикла;
Оба этих компонента необходимы и появляются во всех рекурсивных
определениях « алгоритмах. Рекурсия — естественный инструмент управления массивами
данных, которые имеют правильную структуру и неопределенны»» размер, например,
списками, деревьями и графами. Рекурсия особенно удобна для поиска в пространстве
состояний.
Простои пример — алгоритм определения принадлежности данного элемента
списку- Спискам (list) называют упорядоченную последовательность элементов н
фундаментальных строительных блоков структур данных. Списки использовались как
альтернативная форма записи выражений исчисления предикатом (глава 2) и для
представления списков open и closed в алгоритмах поиска из главы 3. Процедура проверки
принадлежности элемента списку определяется так.
Эта процедура сначала проверяет, пуст ли список, и, если да, алгоритм возвращает
ошибку. В противном случае он сравнивает выбранный элемент с первым элементом
списка. Если они равны, происходит успешное завершение процедуры. Таким образом,
это условие завершения процедуры. Если ни одно из приведенных выше условии
завершения не выполнено, процедура удаляет первый элемент из списка н снова вызывает
сама себя для сокращенного списка. Это позволяет исследовать каждый элемент списка по
очереди. Обратил; внимание на то, что список конечен, и каждый шаг рекурсии
уменьшает его размер на одни элемент. Это означает, что данный алгоритм не приведет к
зацикливанию.
В приведенном выше алгоритме используются две фундаментальные операции со
списком: первая из них (head) возвращает первый элемент списка, вторая операция (tail)
возвращает список, полученный после удаления первого элемента. Эти операции наряду с
рекурсией составляют основу высокоуровневых операций со списком типа member.
Данные операции поддерживаются языками обработки списков LISP и PROLOG и под-
робно описаны я главах, посвященных каждому из этих языков.
Рекурсия, поддерживаемая каким-либо языком программирования, расширяет воз-
можности более традиционных управляющих конструкций, например, циклов и условных
операций. Другими словами, любая итерационная процедура может быть также реа-
лизована рекурсивно. Удобство рекурсивных формулировок— ясность и компактность
выражения. Математические понятия логики или функций не поддерживают таких меха-
низмов. как упорядочение, ветвление и итерация. Для индикации повторения можно ис-
пользовать рекурсию. Поскольку рекурсию прощс описать математически, чем явные
итерации, то легче формально проанализировать правильность и сложность рекурсивных
алгоритмов. Рекурсия часто используется в системах автоматической генерации или ве-
рификации программ, необходимых при создании компиляторов и интерпретаторов. Од-
нако более важным является тот факт, что рекурсия — это естественный способ реализа-
ции таких стратегий искусственною интеллекта, как поиск на графе.

5.1.2 Рекурсивный поиск


Прямое преобразование описанною в главе 3 алгоритма поиска в глубину а рекур-
сивную форму иллюстрирует эквивалентность рекурсии и итерационного подхода. Этот
алгоритм для поддержки списка состояний использует глобальные переменные open и
closed.
Поиск в ширину можно описать фактически тем же самым алгоритмом. При этом
необходимо сохранять список closed как глобальную структуру данных и рассматривать
список орел как очередь, а не как стек.
Из представленного алгоритма ясно, что поиск в глубину не использует все
возможности рекурсии. Процедуру можно упростить, используя рекурсию
непосредственно (а не через список open) для рассмотрения состояний н нахождения
путей в их пространстве. В новой версии алгоритма глобальная переменная closed
используется для обнаружения повторяющихся состоянии и предотвращения циклов.
Список open неявно используется в записях активации рекурсивной среды.

Вместо того чтобы находить все потомки данного состояния, а затем помещать их в
список open, этот алгоритм рассматривает потомки по одному. Для каждого из них ре-
курсивно находятся все потомки, а затем рассматривается очередной потомок исходного
состояния. Следует заметить, что алгоритм предполагает определенный порядок опера-
торов генерации состоянии. Если при рекурсивном поиске некоторый потомок является
целевым состоянием, то процедура поиска успешно завершается, и, естественно, алгоритм
игнорирует братьев данного потомка. Если рекурсивный вызов для одного из потомков
приведет в тупик, то будет рассмотрен брат этого потомка, а также все его потомки.
Таким образом, алгоритм обходит весь граф аналогично алгоритму поиска в Ширину (см.
подраздел 3.2.3).
Рекурсия позволяет обойтись без списка open. Механизм, посредством которого
можно реализовать рекурсию на каком-либо языке программирования, должен включать
отдельную дались активации (activation record) [Aho и UHman. 1977] для каждого
рекурсивного вызова. Каждая такая запись активации должна содержать локальные
переменные и состояние выполнения для каждого вызова процедуры. При каждом
рекурсивном вызове процедуры с новым состоянием новая запись активации сохраняет
параметры процедуры (состояние), все локальные переменные и текущее состояние
выполнения. В рекурсивном алгоритме поиска состояния, находящиеся на
рассматриваемом пути, сохраняются в последовательсноти записей активации
рекурсивных вызовов. Запись каждого вызова содержит также последнюю операцию,
которая была сделана при генерации соответствующего потомка. Это позволяет
генерировать при необходимости брата данного потомка.
Возврат производится, если ни один из потомков данного состояния не привел к
цели, что послужило причиной неудачного завершения процедуры. В этом случае в
процедуру генерации потомков возвращается значение FAIL. Затем эта процедура
применяется к брату данного состояния. В рассматриваемом случае внутренние меха-
низмы рекурсии заменяют список open, который был использован, а итерационной версии
алгоритма. Рекурсивная реализация позволяет программисту ограничить рассмотрение
единственным состоянием н его потомками, а не иметь дело со всем списком open.
Возможность рекурсии выражать глобальные понятия в простой форме — главный
источник ее удобства.
Поиск в пространстве состояний — процесс рекурсивный, но природе. Чтобы
найти путь от текущего состояния к цели, необходимо двигаться в дочернее состояние,
затем из него перейти к потомку и так далее. Если соответствующее дочернее состояние
не ведет к цели, то необходимо рассмотреть состояние тою же уровня, на котором
находится родительское состояние. Рекурсия разбивает большую и трудную проблему
(поиск во всем пространстве состояний) на более простые части (генерирование потомков
отдельного состояния), а затем применяет (рекурсивно) эту стратегию к каждому потомку.
Этот процесс продолжается до тех пор. пока не будет найдено целевое состояние или
исчерпано все пространство состояний.
Задача символьного интегрирования, рассмотренная в подразделе 3.3.3. является
превосходным примером мощности рекурсивного подхода в задачах поиска. При ин-
тегрировании выражения обычно выполняется замена переменных или декомпозиция
интеграла, сведение его к совокупности более простых интегралов (подзадач). Эти
подзадачи также могут быть решены рекурсивно с последующим объединением ре-
зультатов в решении исходной задачи. Например, после применения правила суммы
интегралов (интеграл суммы равняется сумме интегралов) можно попытаться рекурсивно
проинтегрировать каждое из слагаемых. В дальнейшем можно снова применять
декомпозицию и замену, пока не будет проинтегрирован каждый из членов. Затем ре-
зультаты интеграции объединяются (посредством суммирования) в конечным результат.
Таким образом, рекурсия — это естественный инструмент для систематической
декомпозиции задачи с последующим объединением результатов решения частных задач
для решения исходной проблемы.
В следующем разделе этот рекурсивный подход будет расширен до некоторого
контроллера логического решателя задач, который использует унификацию и вывод для
генерации пространства логических отношений и поиска в нем. Алгоритм поддерживает
операцию логического умножения нескольких целей and, а также построение обратных
цепочек от цели к предпосылкам.
5.2 Поиск по образцу
В разделе 5.2 рекурсивный поиск будет применен к пространству логического
вывода. В результате получим общую процедуру поиска для исчисления предикатов.
Допустим, необходимо построить алгоритм, который определяет является ли
выражение исчисления предикатов логическим следствием некоторого набора
утверждений. Имея цель (типа р(а)), алгоритм использует унификацию для выбора
импликаций, заключения которых соответствует цели (например, q[X) р(*М. Поскольку
алгоритм обрабатывает импликации как потенциальные правила дня разрешения запроса,
их часто называют просто правилами. После унификации цели с заключением
импликации (или привила) и применения результирующих подстановок ко всему правил)*
предпосылка зтого правила становится новой целью (q(e)}. Ее называют подцелью. Затем
алгоритм повторяется для подцелей. Если подцель соответствует факту в бак факту в бак
знаний, поиск прекращается. Последовательность выводов, ведущая от исходной цели к
данным фактам, доказывает истинность первоначальной цели.
В функции pattern search поиск выполняется с помощью модифицировании» версии
рекурсивного алгоритма, который, чтобы установил, соответствие двух выражений.
использует унификацию (подраздел 2.5 2), а для создания потомков состояний — правило
модус поненс. Текущее состояние поиска представлено переменной сиг- rent_goal. Если
current_goal соответствует факту, алгоритм возвращает SUC- CBSS. В противном случае
алгоритм пытается сопоставить current_goal с заключением некоторого правила,
рекурсивно просматривая все предпосылки. Если current, goal не согласуется ми с одним
из утверждений, алгоритм возвращает FAIL. Этот алгоритм также обрабатывает
конъюнктивные цели, которые часто встречаются в предпосылках правила.
Для простоты этот алгоритм не решает проблему согласования подстановок
переменных при унификации. Это важно при разрешении конъюнктивных запросов с
общими переменными . Оба конъюнкта должны быть согласованы не только между собой,
но и с унифицированными значениями переменной X (подраздел 2.3.2). Эту проблему мы
рассмотрим и конце раздела.
Главное преимущество использования таких общих методов, как унификация и
правило модус поненс, для генерации состояний заключается в том. что результирующий
алгоритм может просматривать любое пространство логических выводов. Специфические
проблемы описываются утверждениями исчисления предикатов. Таким образом. знания о
задаче отделяются от реализации ее решения на компьютере. Функция pattern_search
обеспечивает первую реализацию такого отделения знаний от управления.

5.2.1 Пример рекурсивного поиска: вариант задами хода конем


Использование исчисления предикатов с общим контроллером для решения задач
поиска можно проиллюстрировать на примере упрощенной версии задачи хода коней
(knight's tour problem). В шахматах конь может перемешаться на два поля по горизонтали
или вертикали и на одно иоле в перпендикулярном направлении. При этом он не должен
выйти за пределы шахматной доски. Таким образом, в самом общем случае существует не
более восьми возможных ходов (рис. 5.1).
Традиционная формулировка задачи заключается в том. чтобы найти такую
последовательность ходов конем, при которой он становится на каждую клетку только
один раз. Эта задача дала толчок к развитию и представлению алгоритмов поиска.
Пример, который мы используем к этом разделе. — упрошенная версия задачи хода
конем. Здесь необходимо найти такую последовательность ходов, при которой конь
побывал бы в каждом поле уменьшенной шахматной доски (3x3) только одни раз. (Полная
задача хода конем рассмотрена в подразделе 5.3.2.)
На рис. 5.2 показана шахматная лоска, размером 3x3. где каждая клетка помечена
целым числом от 1 до 9. Для простоты эта схема маркировки будет использоваться вместо
более общей схемы, в которой каждая клетка маркируется двумя цифрами, номером
строки и номером столбца. Поскольку размер шахматной доски уменьшен, мы просто
перечислим возможные ходы, а не будем разрабатывать общий оператор перемещения. В
этом случае допустимые ходы ив доске можно описать с помощью предиката move.
параметрами которого являются номера начальной и конечной клетки допустимого хода.
Например, move{ 1,8) перемешает коня из верхнего левого угла в середину нижнего ряда.
Предикаты на рис. 5.2 описывают все возможные ходы для шахматной доски 3x3.
Эти предикаты составляют базу знаний для задачи хода конем. В качестве примера
использования унификации для обращения к этой базе знаний проверим существование
различных ходов на шахматной доске. Для определения возможности перемещения коня из
клетки 1 в клетку 8 будем вызывать процедуру pattorn.search(move<1.8)). Поскольку данная цель
унифицируется с фактом move (1.8) в базе знаний, функция возвращает значение SUCCESS без
каких-либо подстановок переменных.

Другой запрос позволяет определить, куда конь может переместиться из некоторого


местоположения, например, из клетки 2. В этом случае цель move(2. X) унифицируется с двумя
различными предикатами в базе знаний с подстановками {7/Х} и {9/Х}. Для цели move(2,3)
результатом будет FAIL, так как в базе знаний не существует элемента move[2.3) Ответом нa
запрос move{S.Y) тоже будет FAIL, потому что не существует утверждений, задающих
перемещение из клетки 5.
Следующая задача состоит в построении алгоритма поиска для определения пути
последовательных шагов по доске. Это можно сделать с помощью импликаций
исчисления предикатов. Они добавляются к базе знаний как правила для создания путей
последовательных шагов. Чтобы подчеркнуть, что эти правила обеспечивают поиск от
цели, мы изменили направление стрелки импликации. Следовательно, правила будем
писать как Заключение <- Предпосылка,
Например, путь с двумя ходами можно записать так.

Это правило утверждает, что для всех клеток X и У существует путь с двумя
ходами, если существует такая клетка Z, которая может переместиться из X в Z. а затем —
из Z в У.
Общее правило path2 можно применять различными способами. Например, оно
подходит для проверки существования двухходового пути из одной клетки в другую. Если
функция pattem_search вызывается для цели ра(/>2<1.3), то проверяется соответствие этой
цели последовательности правил path2{X,Y). При этом выполняются подстановки для
предпосылок правила. Результатом является конкретное правило, которое определяет
условия, необходимые для такого пути.

Затем функция pattem_search вызывает сама себя с этим параметром. Поскольку это
конъюнкция двух выражений, pat tem_ae arch пытается разрешить каждую подцель
отдельно. Это требует не только согласованности обеих подцелей, но и непро-
тиворечивости связывания всех переменных. Подстановка 8 вместо Z дает возможность
согласовать обе подцели.
Другой запрос может состоять в том, чтобы найти все клетки, которые могут быть
достигнуты из клетки 2 за два хода. Это достигается вызовом pattern_search для цели
ра№2(2, У). Используя подобный процесс, можно найти все возможные подстановки,
включая {б/У) и {2/У) (с промежуточным значением Z. равным 7), а также (2/У) и (4/У) (с
промежуточным значением 9). Дальнейшие запросы могут состоять в том. чтобы найти
путь за два хода из клетки с некоторым номером в ту я» самую клетку, из любой клетки в
клетку 5 и так далее. Обратите внимание на одно из преимуществ поиска по образцу: в
качестве начальной цели могут быть взяты самые разнообразные запросы.
Аналогично путь с тремя ходами проходит через две промежуточные клетки,
которые являются частью пути от начального состояния к цели. Такой путь может быть
определен следующим образом.

Используя это определение, можно рассматривать такие задачи, как parft3{ 1.2),
P9th3( 1 ,Х) или даже paf/i3(X, У). Эти задачи мы оставляем читателю в качестве уп-
ражнений.
Очевидно, что аналогичным способом можно решать задачи нахождения пути
произвольной длины. Просто необходимо указать соответствующее количество
промежуточных мест "приземления". Также очевидно, что путь большей длины может
быть определен в терминах пути меньшей длины.

This suggests the single, general recursive role:


Последнее правило можно использовать для проверки существования пути произ-
вольной длины. Такая задача может быть сформулирована следующим образом: "Найти
путь из одной клетки в другую, дели при этом ход из начальной клетки и промежуточную,
а затем из промежуточной в конечную".
Это рекурсивное правило "пути" неполно в том смысле, что в нем не определено
условие выхода из рекурсии. Поэтому любая попытка определить путь к цели, используя
предикат par/), будет безуспешной, потому что каждая попытка найти промежуточный
путь, снова будет приводить к рекурсивному вызову path(Z, У). Эти объясняется тем, что
не определено условие достижения конечного состояния. Этот недостаток может быть
исправлен, если к базе знаний добавить условие path(X, X). Поскольку path(X, X) уни-
фицируется только с предикатами типа раth(Э, 3) или path( 5,5). то оно определяет не-
обходимое условие завершения рекурсии. Тогда общее рекурсивное определение задается
двумя формулами исчисления предикатов.

Еще раз обратите внимание на элегантность н простоту рекурсивной


формулировки. Если объединить приведенные выше условия с рекурсивной процедурой
pat- tern_search. то эти правила позволяют на йти всевозможные пути в задаче хода ко-
нем. Объединяя их с правилами перемещения коня, получим полное описание задачи (или
базы знаний).

Следует заметить, что при решении задачи использовались логические описания,


определяющие пространство состояний, и процедура pattern^search поиска в этом про-
странстве. Хотя правило path достаточно удовлетворительно определяет путь, оно не ука-
зывает, как найти этот путь. Действительно, на шахматной доске существует много друга
путей, которые не ведут к цели, но, тем не менее, удовлетворяют этому определению. На-
пример, если не принимать во внимание механизм предотвращения зацикливания, то
поиск цели path(1,3) может привести к зацикливанию между клетками 1 и 8, вместо
нахождения правильного пути I—>8-*3. Таким образом, логическими следствиями базы
знаний являются и цикл, и правильный (допустимый) путь. Аналогично, если рекурсивное
правило будет проверяться перед условием выхода из рекурсии, то условие выхода из ре-
курсии path( 3.3) будет проигнорировано, и поиск продолжится безо всякого смысла.

5.2.2 Усовершенствование алгоритма поиска по образцу


Хотя начальная версия функции pattern.search определяет правильное поведение
алгоритма поиска для выражений исчисления предикатов, все же стоит обратить
внимание на некоторые тонкости. Они включают порядок рассмотрения альтернативных
вариантов и корректную обработку полного набора логических операторов (a, v и -»).
Логика по своей сути декларативна, и без предписаний стратегии поиска она определяет
пространство возможных выводов, но не указывает решателю, как выделить из него
полезные.
Дня реализации рассуждений средствами исчисления предикатов необходим такой
режим управления, который осуществляет систематический поиск в пространстве, и при
этом позволяет избежать тупиковых путей и зацикливания. Алгоритм управления типа
pactern__search должен пытаться найти альтернативные нуги в некоторой последо-
вательности. Знание этой последовательности позволяет разработчику программы
управлять поиском, должным образом систематизируя правила в базе знаний. Самый
простой способ определения такой последовательности — потребовать, чтобы алгоритм
проверял правила и факты в том порядке, в котором они встречаются в базе знаний. В
задаче хода конем это гарантирует, что условие выхода из рекурсии path(X, X) будет
рассматриваться перед следующим рекурсивным шагом.
Другая проблема заключается в наличии логических связок в предпосылках
правил, например, в импликациях вида: р<-длг или p*-q*(rv$). Оператор означает, что ре-
зультат будет истиной, если выражения q и г тоже ис1инны. Кроме того, конъюнкты в
выражениях должны разрешаться с помощью согласованных значений переменных. Так.
чтобы найти значение выражения р(Х)лф(Х), не достаточно разрешить р(Х) и <?(Х). ис-
пользуя подстановку (а/Х) и {a/Х). Оба конъюнкта должны быть разрешены с помощью
одного и того же соответствующего значения переменной X. С другой стороны, оператор
v (или) обеспечивает истинность для всего выражения, если хотя бы одна из его частей
является истиной. Алгоритм поиска должен принимать это во внимание.
Последнее дополнение к рассмотренному алгоритму — это его способность
находить цель, используя логическое отрицание. Функция pattem_search обрабатывает ин-
вертированные цели, разрешая вначале операнд отрицания. Если эта подцель достижима.
то процедура pattern_search возвращает FAIL. Если же эта цель недостижима. то
pattern_search возвращает пустое множество подстановок, указывающие на успех.
Обратите внимание на то, что даже если подцель содержит переменные, то результат
разрешения ее отрицания не может содержать никаких подстановок. Это связано с тем.
что отрицание принимает значение "истина", только если его операнд не удовлетворяется.
Следовательно, операция отрицания не может возвращать никаких значений связывания
для этого операнда.
Наконец, алгоритм должен возвращать не значение SUCCESS, а те значения
связывания, которые использовались при решении Например, для нахождения пути move{
1, X) существенной частью решения являются подстановки (6/Х) и (8/Х).
Окончательная версия функции pattern.search. возвращающая множество уни-
фикаций, удовлетворяющих каждой подцели, имеет следующий вид.

You might also like