Скачиваний:
50
Добавлен:
01.05.2014
Размер:
283.14 Кб
Скачать

8.3. Замена рекурсии итерацией

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

Тем не менее существует ограниченный класс рекурсивных программ, достаточно точно соответствующих обычным итерационным программам. При некоторых условиях, описанных в связи с оптимизацией рекурсии в разд. 11.2, такие программы можно реализовать на Прологе почти так же эффективно, как итерационные программы в обычных языках. По этой причине желательно по возможности задавать отношение с помощью итерационной программы. В данном разделе мы покажем, как использование накопления позволяет преобразовать рекурсивную программу в итерационную.

Напомним, что предложение чистого Пролога называется итерационным, если в теле предложения содержится один рекурсивный вызов. Мы распространим это определение на общий Пролог, допуская использование любого числа системных предикатов перед рекурсивным вызовом. Программа на Прологе называется итерационной, если она содержит лишь единичные и итерационные предложения.

Большинство простых арифметических вычислений может быть задано с помощью итерационных программ.

Например, факториал может быть выполнен с помощью цикла, в котором перемножаются все числа, вплоть до числа, факториал которого мы хотим найти. Соответствующая процедура на языке, подобном языку Паскаль, приведена на рис. 8.1. Итерационный характер этой процедуры может быть непосредственно выражен в итерационной программе на Прологе.

factorial(N);

I:=0; Т:=1;

while I<N do

I:= I+ I; T:=T*I end;

return Т

Рис. 8.1. Итеративное вычисление факториала.

В Прологе отсутствуют “локальные” переменные для удержания промежуточных результатов и их изменения в процессе вычисления. Поэтому для реализации итерационных алгоритмов, требующих сохранения промежуточных результатов, процедуры Пролога дополняются аргументами, называемыми накопителями. Обычно промежуточное значение соответствует результату вычисления при завершении итерации. Это значение сопоставляется результирующей переменной с помощью единичною предложения процедуры.

Этот метод демонстрируется на примере программы 8.3, которая содержит определение на Прологе отношения factorial, соответствующее циклу “while” рис. 8.1. В программе используется отношение factorial (I,N,T.F), выполненное, если F равно факториалу числа N, а значения переменных I и T совпадают со значениями соответствующих переменных в цикле перед (I+1)-й итерацией цикла.

factorial (N,F)

F равно факториалу целого числа N.

factorial(N,F)  factorial (О,N,1,F).

factorial (I, N, Т, F)

I<N,I1: = 1+1, Tl := T*I1, factorial^],N,T1,F)

factorial (N,N,F,F).

Программа 8.3. Итерационное вычисление факториала.

Основной итерационный цикл реализован с помощью итерационной процедуры factorial/4. Каждая редукция цели, использующая процедуру factorial/4, соответствует итерации цикла “while”. Обращение к процедуре factorial/4 в процедуре factorial/2 соответствует начальному этапу. Первый аргумент отношения factorial/4, соответствующий счетчику цикла, полагается равным 0.

Третий аргумент отношения factorial/4 используется для накопления текущего значения произведения. Он устанавливается равным 1 при обращении к процедуре factorial/4 в процедуре factorial/2. Использование обоих накопителей в программе 8.3 типично для программирования на Прологе. Оно очень похоже на использование накопителей в программах 3.16b и 7.10 для отбора элементов списка.

Накопители являются логическими переменными, а не ячейками памяти. В процессе итерации передается не адрес, а значение. Так как логические переменные обладают свойством “одноразовой записи”, то измененное значение - новая логическая переменная - передается каждый раз. Чтобы выразить этот факт, мы используем для указания измененных значений суффикс 1 в обозначениях переменных, например Т1 и I1.

Вычисление остановится, когда счетчик достигнет значения N. Правило для предиката faclorial/4 в программе 8.3 более не применяется, и факт достигнут. После успешного завершения редукции значение факториала “возвращается” как результат унификации с накопителем в базисном предложении. Отметим, что логическая переменная, представляющая решение (последний аргумент отношения factorial/4), должна следовать по всему вычислению, чтобы получить значение при заключительном вызове процедуры faclorial/4. Подобная передача значений с помощью аргументов типична для программ на Прологе, хотя новичку может показаться странной.

Программа 8.3 является точным отражением цикла “while”, задающего факториал на рис. 8,1. Другой вариант итерационного вычисления отношения factorial может быть получен путем изменения счетчика от N до 0, а не от 0 до N. Основная структура программы остается той же и приведена как программа 8.4. Здесь имеются начальное обращение, устанавливающее значение накопителя, рекурсивное и базисное предложения, реализующие цикл “while”.

Программа 8.4 несколько эффективнее программы 8.3. Вообще, чем меньше аргументов имеется в процедуре, тем легче понять ее запись и тем быстрее она выполняется.

Полезным итерационным предикатом является предикат between (I,J,К), истинный, если целое число К лежит между I и J включительно. Предикат может быть использован для недетерминированного порождения целых чисел в интервале. Это

factorial (N,F)-

F равно факториалу целого числа N.

factorial(N,F)  factorial (N,1,F).

factorial(N,T,F) 

N>0,T1:= T*N,N1 := N-l, factorial(N1,T1,F).

factorial (0,F,F).

Программа 8.4. Другой вариант итерационного вычисления факториала.

between(I,J,К)-

число К лежит между I и J включительно.

between (I,J,I)IJ.

between (I, J, К)  I<J, I1 := I+ 1, between (I1,J,К).

Программа 8.5. Порождение множества чисел,

полезно в программах порождения и проверки, рассматриваемых в разд. 14.1, и циклах, управляемых отказом, рассматриваемых в разд. 12.5.

Итерационные программы можно также использовать в работе со списками целых чисел. Рассмотрим отношение sumlist(IntegerList,Sum), в котором Sum есть сумма элементов списка IntegerList, состоящего из целых чисел. Приведем две программы, вычисляющие данное отношение. Программа 8.6а представляет собой рекурсивное описание. Чтобы просуммировать список, следует просуммировать хвост списка и к результату прибавить голову списка. Программа 8.6б использует накопитель для подсчета текущей суммы, подобно тому как программа 8.3, задающая отношение factorial, использовала накопитель для подсчета текущего произведения. Вводится вспомогательный предикат sumlist/3 с дополнительным аргументом для накопления, начальное значение 0 которого устанавливается при исходном обращении к процедуре sumlist/3. Сумму сопоставляют результату вычислений в заключительном вызове процедуры при унификации с базисным фактом. Единственное различие между программой 8.6б и итерационными версиями программы factorial состоит в том, что управление итерациями происходит с помощью рекурсивной структуры списка, а не с помощью счетчика.

Рассмотрим еще один пример. Скалярным произведением двух векторов Хi и Yj называется сумма Х1Y1, +...n •Yn. Если векторы представляются в виде списков, то нетрудно написать программу, задающую отношение inner_product (Xs,Ys,IP), где IP-скалярное произведение векторов Xs и Ys. Программы 8.7а и 8.76 являются соответственно рекурсивным и итерационным вариантами.

Взаимосвязь итерационного и рекурсивного вариантов программы inner_product аналогична взаимосвязи программ 8.6а и 8.6б.

Программы 8.7а и 8.76 корректны, если аргументы Xs и Ys цели inner_pro-ducl(Xs,Ys,Z) являются списками равной длины, состоящими из целых чисел. В программах имеется проверка равенства длин. Вычисление приведет к отказу, если Xs и Ys имеют разную длину.

Аналогия между взаимосвязью программ 8.6а и 8.66 и взаимосвязью программ 8.7а и 8.7б наводит на мысль, что одна программа может быть автоматически преобразована в другую. Эквивалентное преобразование рекурсивных программ в итерационные представляет интересную область исследований. Ясно, что это может быть сделано для приведенных здесь простых примеров.

Сложность программы на Прологе зависит от исходных логических отношений,

sumlist (Is,Sum)

число Sum равно сумме всех элементов списка Is, состоящего из целых чисел.

sumlist([I | Is]),Sum) 

sumlist(Is,IsSum), Sum:= I+IsSum. sumlist([ ],0).

Программа 8.6а. Суммирование списка целых чисел.

sumlist (Is, Sum)

число Sum равно сумме всех элементов списка Is, состоящего из целых чисел.

sumlist (Is, Sum)  sumlist(Is,0,Sum). sumlist([I | Is],Temp, Sum) 

Temp1:= Temp+I,sumlist(Is,Templ,Sum). sumlist ([ ], Sum, Sum).

Программа 8.6б. Итерационный вариант суммирования списка целых чисел с использованием накопителя.

inner_product(Xs,Ys,Value)

Value есть скалярное произведение векторов, представленных списками целых чисел Xs и Ys.

iimer_product([X | Xs], [Y | Ys],IP) 

inner_product(Xs,Ys,IPl), IP:=X*Y+IP1. inner_product([ ],[ ],0).

Программа 8.7а. Вычисление скалярного произведения векторов.

inner_product (Xs, Ys, Value)

Value есть скалярное произведение векторов, представленных списками целых чисел Xs и Ys.

inner_product(Xs,Ys,IP)  inner_product(Xs,Ys,0,IP).

inner_product([X | Xs],[Y | Ys],Temp,IP) 

Temp1 := X*Y + Temp,inner_product(Xs,Ys,Temp1,IP). inner_product([ ],[ ],IP,IP).

Программа 8.7б. Итерационное вычисление скалярного произведения векторов.

аксиоматизируемых в программе. Приведем пример простой программы на Про­логе, решающей сложную задачу.

Рассмотрим следующий вопрос: дан плоский замкнутый многоугольник 12,,...,Рn}. Требуется найти площадь ориентированного многоугольника. Площадь вычисляется с помощью линейного интеграла

l/2xdy - ydx,

где интегрирование производится по границе многоугольника.

Решением является программа 8.8, задающая отношение area (Chain,Area). Chain является списком координат вершин, например [(4,6),(4,2),(0.8),(4,6)]. Значением переменной Area будет площадь многоугольника с данными вершинами. Площадь положительна, если многоугольник обходится против часовой стрелки, и отрицательна, если обход по часовой стрелке. Вопрос area([(4,6),(4,2),(0,8),(4,6)], Аrea}? имеет решение Area = - 8. Ориентация многоугольника изменяется, если пары перечисляются в обратном порядке. Решением вопроса аrea([(4,6),(0,8),(4,2),(4,6)]rea)? будет Area = 8. Приведенная программа не итерационна. Преобразование

area (Points,Area)-

число Area равно площади, ограниченной замкнутой ломаной линией, вершины ломаной представлены списком Points, каждая точка в списке представлена парой целых чисел (X,Y), где X, Y - координаты точки.

area([Tuple],0).

area([(X1,Yl),(X2,Y2) | XYs],Area)

area([(X2,Y2) | XYs],Area1), Area:=(Xl*Y2-Yl*X2)/2+Area1.

Программа 8.8 Вычисление площади многоугольника.

данной программы в итерационную составляет задание упражнения (5) в конце раздела,

Можно написать итерационную программу, находящую максимальный элемент в списке целых чисел. Реляционная схема отношения - maximum(Xs,Мах), программа 8.9 представляет собой такую программу. Вспомогательный предикат maximum (Xs,X, Мах) истинен, если число Мах является максимумом числа Х и элементов списка Xs. Исходное значение переменной Х совпадает с первым элементом списка Xs. Заметьте, что максимум пустого списка в программе не определен.

maximum (Xs, N)

N равно максимальному элементу списка Xs.

maximum([X | Xs],M)  maximum(Xs,X,M).

maximum ([X | Xs],Y,M)XY, maximum(Xs,Y,M).

maximum ([X | Xs],Y,M) X>Y, maximum (Xs, Y, M).

maximum([ ],M,M).

Программа 8.9. Нахождение максимального элемента в списке целых чисел.

Стандартная рекурсивная программа нахождения максимального элемента списка основана на несколько ином алгоритме. Рекурсивный алгоритм состоит в нахождении максимального элемента хвоста списка и сравнении его с головой списка. В отличие от этого программа 8.9 запоминает текущий максимум пройденного начального отрезка списка.

Программа 3.17, определяющая длину списка, интересна потому, что на ее примере можно продемонстрировать несколько способов преобразования логической программы в программу на Прологе со своими свойствами. Одной из таких программ является итерационная программа 8.10. Вопрос length(Xs,N)? решается данной программой правильно, если N - натуральное число. В этом случае программа или проверяет длину списка или порождает список из N неопределенных элементов или приводит к безуспешным вычислениям. Однако программа не предназначена для нахождения длины списка. Вопрос вида length([1,2,3],N)? приводит к сообщению об ошибке.

length (Xs.N)

Xs-список длины N.

length ([X | Xs],N)  N>0,N1:= N-1, length (Xs.N1), length([ ],0).

Длину списка можно найти с помощью программы 8.11. Однако эта программа не может быть использована для порождения списка из N элементов. В отличие от программы 8.10 вычисление программы 8.11 не завершается, если первый аргумент -неполный список. Таким образом, разные использования требуют наличия разных программ length.

length (Xs.N)

N- длина списка Xs.

length([X | Xs],N)lenglh(Xs,Nl),N:= Nl+1. length([ ],0).

Программа 8.11 Нахождение длины списка.

При определении отношения range (M,N,Ns), выполненного, если Ns; список целых чисел, находящихся между М и N включительно, следует, как и в случае предыдущей программы, рассмотреть предполагаемые использования предиката. Программа 8.12 имеет специфическое использование-порождение списка чисел в заданном интервале. Программа тотально корректна в области всех целей range (M,N,Ns) с определенными значениями переменных М и N. Однако эта программа непригодна для нахождения верхней и нижней грани набора чисел, так как в программе имеется проверка М < N. Удаление этой проверки позволит решать с помощью программы вопросы вида range(M,N,[1,2,3])?, но теперь возникнут незавершающиеся вычисления при решении вопросов, для которых программа была предназначена первоначально (таких, как range(1,3,Ns)?.

range(М, N, Ns)

Ns-список целых чисел, расположенных между М и N включительно.

range(M,N,[M | Ns]) M<N,M1:= M+1, range(M1,N,Ns). range(N,N,[N]).

Программа 8.12 Построение списка целых чисел в заданном интервале.

Упражнения к разд. 8.3

1. Напишите итерационный вариант программы triangle(N.T), описанной в упр. 8.2(1).

2. Напишите итерационный вариант программы power(X,N.V). описанной в упр. 8.2(2).

3. Перепишите программу 8.5 так, чтобы последовательные целые числа появлялись в убывающем порядке.

4. Напишите итерационную программу, задающую предикат timeslist(IntegerList, Product). Этот предикат вычисляет произведение (Product) элементов списка IntegerList. состоящего из целых чисел. Данная программа аналогична программе sumlist - программе 8.16б.

5. Перепишите программу 8.8, вычисляющую площадь многоугольника так, чтобы программа стала итерационной.

6. Напишите программу нахождения минимального элемента в списке целых чисел.

7. Перепишите программу 8.11, вычисляющую длину списка так, чтобы она стала итерационной. (Указание: используйте счетчик, как это сделано в программе 8.3.)

8. Перепишите программу 8.12 так, чтобы список целых чисел строился не восходящим, а нисходящим методом.

Соседние файлы в папке 1-13