РЕКУРСИЯ
При обработке динамических структур типа «дерево» чаще всего используются рекурсивные алгоритмы.
Рекурсия предполагает определение некоторого понятия с использованием самого этого понятия. Никлаус Вирт приводит следующее утверждение о возможностях рекурсии:
«… мощность рекурсии связана с тем, что она позволяет определить бесконечное множество объектов с помощью конечного высказывания».
Примером рекурсивного определения является факториал неотрицательного числа:
|
В Турбо-Паскале рекурсия разрешена: подпрограмма может вызывать сама себя:
С помощью выражения Factor(n-1) приведенная функция Factor будет вызывать сама себя до тех пор, пока значение ее входного параметра не станет равным нулю. При каждом повторном вызове функции Factor создается новый экземпляр памяти для всех локальных переменных и для значения самой функции, который запоминается в стеке.
Продемонстрируем это на примере вычисления факториала от 4.
Вначале изобразим процесс развертывания рекурсии:
n | ||||||||||||
Factor | ||||||||||||
Шаг |
Первое значение Factor вычисляется, когда n=0, оно подставляется в соответствующий экземпляр памяти. Теперь на каждом очередном шаге значения всех членов выражения n*Factor(n-1) известны, и алгоритм подставляет в это выражение значение, полученное на предыдущем шаге. Это происходит в процессе свертывания рекурсии:
n | ||||||||||||
Factor | ||||||||||||
Шаг |
Сформулируем два важных свойства рекурсивных алгоритмов:
1. Наличие тривиального случая:
Правильный рекурсивный алгоритм не должен создавать бесконечную последовательность вызовов самого себя. Для этого он обязательно должен содержать нерекурсивный выход: т.е. при некоторых входных данных вычисления в алгоритме должны производиться без вызовов его самого.
Для функции «факториал» тривиальный случай: «0!=1».
2. Определение сложного случая в терминах более простого:
При любых входных данных нерекурсивный выход должен достигаться за конечное число рекурсивных вызовов. Для этого каждый новый вызов рекурсивного алгоритма должен решать более простую задачу. Иными словами рекурсивный алгоритм должен содержать определение некоторого сложного случая в терминах более простого случая.
Для функции «факториал» вместо вычисления n! заменяется умножением n*(n-1)!, и при этом с каждым вызовом значение n уменьшается, стремясь к нулю и достигая его за конечное число вызовов.
Из определения структуры типа «дерево» видно, что она рекурсивна по определению, а в силу этого рекурсивными являются и практически все алгоритмы работы с деревьями. Для этого достаточно посмотреть на приведенный выше пример создания бинарного упорядоченного дерева. В процедуре Add имеется тривиальный случай (когда дерево пустое) и рекурсивные вызовы: добавить в левое и правое поддеревья - Add(NewData,Root^.left) и Add(NewData,Root^.right).
Рассмотрим ряд алгоритмов работы с деревьями, обращая при этом внимание также на правильность их построения, как рекурсивных алгоритмов.