- •Введение
- •1. Составление программ
- •1.1. Основные определения
- •1.2. Декларации в программах
- •1.3. Объявление предикатов и типов их аргументов
- •1.4. Другие разделы программы
- •Упражнения
- •2. Механизмы доказательства правил
- •2.1. Сопоставление с откатом
- •2.2. Рекурсия
- •Упражнения
- •3.Операции в Visual Prolog. Ввод-вывод
- •3.1. Операции
- •3.2. Предикаты ввода-вывода
- •4. Управление процессом доказательства правил
- •4.1. Искусственный откат
- •4.2. Отсечение
- •4.3. Повтор, определяемый пользователем
- •5. Списки
- •5.1. Процедуры обработки списков
- •5.2. Организация стеков и очередей
- •6. Внутренняя база фактов
- •7. Иерархическая организация данных
- •8. Работа с деревьями и графами
- •8.1. Двоичные деревья
- •8.2. Графы
- •9. Работа с именами и строками
- •Заключение
- •Библиографический список
- •Оглавление
8. Работа с деревьями и графами
8.1. Двоичные деревья
Двоичное дерево имеет следующее определение:
дерево либо пусто, либо содержит три компоненты:
узел;
левое поддерево;
правое поддерево.
Если узлом может служить любой объект, то поддеревьями могут быть только деревья, в том числе и пустые.
Широко применяемый способ представления двоичных деревьев в Прологе состоит в использовании структуры вида:
tree(узел, левое поддерево, правое_поддерево)
Введем для обозначения пустого дерева константу empty. Дерево на рис.3, имеющее 4 узла, может быть представлено так:
tree( d, tree (c, empty, emty), tree ( f, tree (e, empty, empty), empty).

Рис.3
Двоичное дерево – эффективная структура для организации данных в памяти ЭВМ, в частности, словарей. В упорядоченном словаре любой узел больше узла левого поддерева и меньше узла правого поддерева. Так как в машинном представлении значение символов c<d<f и f>e, то дерево на рис. 3 – упорядоченный двоичный словарь.
Запишем представление дерева, приведенного на рис. 4, в узлах которого записаны имена и числа. Определим узел как структуру node(имя, число)
.

Рис.4
tree (node(вера, 70),
tree(node(борис, 50),
tree(node(анна,24) empty,empty),
tree(node(валентин,26) empty,empty)),
tree(node(дмитрий, 48),
tree(node(владимир,23), empty,empty),
tree(node(федор, 19),empty,empty)))
Примем имя в качестве ключа, которому соответствует число в узле. Так как согласно лексикографическому порядку анна< борис >вера, игорь> борис, дмитрий>вера и владимир<дмитрий< федор, то дерево упорядочено по ключам.
Основными процедурами работы с деревьями вообще и с двоичными упорядоченными деревьями в частности являются следующие [1]:
определение принадлежности элемента дереву;
создание дерева;
обход дерева;
удаление и вставка узла в имеющееся дерево;
сортировка дерева.
Так как дерево – рекурсивная структура данных, то все процедуры, кроме первой, являются рекурсивными.
Рассмотрим процедуру принадлежности. Введем предикат
принадлежит(node(X, _), tree(node(K,_), Left, Right)).
Элемент Х принадлежит двоичному упорядоченному дереву, если:
Х равен ключу K или
если Х меньше ключа, то он находится в узлах левого поддерева или
если Х больше ключа, то он находится в узлах правого поддерева.
Программа поиска числа, ассоциированного с заданным именем, выглядит следующим образом:
DOMAINS
nodetype=node(symbol, integer) %тип узла
treetype = tree(nodetype, treetype, treetype);
empty
PREDICATES
nondeterm принадлежит (nodetype, treetype)
CLAUSES
принадлежит(node(X,V), tree(node(X,V),_,_)).
% терминальный предикат
принадлежит(node(X,V), tree(node(Y,_),L,_)):- X< Y, принадлежит(node(X,V),L).
принадлежит(node(X,V), tree(node(Y,_),_,R)):- X>Y, принадлежит(node(X,V),R).
GOAL
L= tree(node(вера, 70)
tree(node(борис, 50)
tree(node(анна,24) empty,empty)),
tree(node(валентин,26) empty,empty)),
tree(node(дмитрий, 48),
tree(node(владимир,23), empty,empty),
tree(node(федор, 19),empty,empty))),
принадлежит(node(федор,Y), L), write(Y).
/* федор – заданное имя */
Y=19
Для
создания словаря необходимо сначала
создать дерево из одного узла. Это
делается предикатом
создать(node(K,N), tree(node(K,N), empty, empty)),
в котором второй аргумент – новое дерево из одного узла.
Далее следует последовательно вставлять левые или правые поддеревья в зависимости от результата сравнения:
если ключ поддерева меньше ключа дерева, то новое поддерево станет левым;
иначе оно станет правым.
вставить(node(NewK,NewN), tree(node(K,N), L,R),
tree(node(K,N), Left, Right))
В этом предикате второй аргумент является исходным деревом, первый – узлом дерева, которое станет для нового левым или правым поддеревом, третий аргумент – новое дерево.
вставить(node(NewK,NewN), tree(node(K,N), L,R),
tree(node(K,N), NewL, R)):-
NewK<K,!, вставить (node(NewK,NewN), L,NewL).
вставить(node(NewK,NewN), tree(node(K,N), L,R),
tree(node(K,N), L, NewR)):-
вставить(node(NewK,NewN), R,NewR).
Приведем алгоритм обхода дерева:
если дерево пусто, то не совершать никаких действий;
иначе обработать текущее значение;
затем перейти на левое поддерево;
затем перейти на правое поддерево.
Схема рекурсивной процедуры следующая:
обход(empty). % терминальный предикат
обход(tree(node(_,_), Left, Right)):-
/*обработка node*/,
обход (Left),
обход(Right).
Эта процедура наглядно демонстрирует поиск в глубину: спускаемся по левой ветви вниз до пустого поддерева, затем возвращаемся назад и начинаем обход правой ветви.
Вставка и удаление элементов дерева, а также его сортировка подробно рассмотрены в [1, 6].
