Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
REC(3S)X.DOC
Скачиваний:
5
Добавлен:
14.08.2019
Размер:
167.42 Кб
Скачать

Самитов р.К.

Рекурсивное программирование

  • Factorial(n) = Factorial(n-1)*n, Factorial(1) = 1

PROGRAM pp;

FUNCTION Factorial(n:INTEGER):INTEGER;

BEGIN IF n<2 THEN Factorial:=1 ELSE

Factorial:= Factorial(n-1)*n END;

BEGIN WRITELN(Factorial(4)) END.

  • Семантика: дерево подзадач; два этапа - построение плана (дерева), вычисление по плану; важность требования - конечный обрыв всех ветвей.

  • Рекурсивный анализ: параметризация задачи, поиск базового случая и его решения, декомпозиция общего случая и обоснование конечного обрыва.

  • Рекурсия - специфическая структура управления: может устранить необходимость использования оператора цикла; ... всегда может - реализация WHILE-оператора рекурсией.

WHILE E DO S ~ ProcWhile

PROCEDURE ProcWhile;

BEGIN IF E THEN BEGIN S ; ProcWhile END END

  • Интеллектуальность+Неэкономность: первый этап (построение плана) иногда мог бы провести программист и не перегружать ЭВМ этой работой.

  • FILE OF REAL вывести в обратном порядке (определение - опираясь на развертку цикла)

Revers <Файла f> =

R EAD(f,x’); READ(f,x”); ... WRITE(g,x”); WRITE(g,x’)

= Revers <Остатка файла f>

PROGRAM pp; VAR f,g:FILE OF REAL;

PROCEDURE Revers; VAR x:REAL;

BEGIN IF NOT EOF(f) THEN

BEGIN READ(f,x); Revers; WRITE(g,x) END END;

BEGIN RESET(f); REWRITE(g); Revers END.

  • Дерево подзадач в виде вызовов процедур (с возвратами и отображением локальных переменных).

  • Рекурсия+Локализация данных - специфическая структура данных: может устранить необходимость использования массивов.

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

  • Factorial(n) = Fi(1,1,n), где

Fi(y,i,n) = Fi(y*(i+1), i+1,n), Fi(y,n,n)=y

Рассуждения в связи с заморочкой о смысле Fi().

( 1) Fact(n)= 1*2*...(n-1)*n.

Fact(n-1)

(2) Invers<f,f,...> =

READ(f); READ(f);...WRITE(f); WRITE(f)

Invers<f,...>

F i(1,1,n)

( 3) Fact(n)= 1*2*...i*(i+1)*(i+2)*...(n-1)*n.

y

F i(y,i,n) = y*(i+1)*(i+2)*...n.

y

Fi(y,i+1,n) = Fi(y*(i+1), i+1,n)

Fi(y,n,n)=y

  • происхождение идеи - рассуждение при написании традиционной императивной программы в терминах преобразования состояний (y,i,n); использование дополнительных аргументов в качестве дополнительной памяти - накапливающие параметры (возможность их использования для более изощренных целей - явного хранения фрагментов плана).

PROGRAM pp;

FUNCTION Fi(y,i,n:INTEGER):INTEGER;

BEGIN IF i=n THEN Fi:=y ELSE

Fi:= Fi(y*(i+1),i+1,n) END;

FUNCTION Factorial(n:INTEGER):INTEGER;

BEGIN Factorial:= Fi(1,1,n) END;

BEGIN WRITELN(Factorial(4)) END.

  • Дерево подзадач и возможность отсечения второго этапа.

  • Редуктивный и индуктивный стили рекурсивного программирования; «неэкономичность» рекурсии - как следствие неудачного использования этого инструмента программистом или неудачной реализации семантики этого инструмента транслятором (на данной ЭВМ).

  • Невырожденное применение рекурсии - задача о синтаксическом анализе на «Арифметическое выражение»; рекурсивность диаграммы Вирта и адекватность соответствующей рекурсивной программы. Программа с использованием функций типа BOOLEAN (ExprA, Variable, Symbol) и глобальных переменных - входной файл и текущий символ.

PROGRAM Prj8;{PROGRAM\RECURS\PRJ08\PRJ8.DPR}

{$B-}

uses SysUtils,Dialogs;

TYPE TSetOfChar= SET OF CHAR;

VAR Vh{входной текст}: FILE OF CHAR;

x{текущий символ}: CHAR; bb:BOOLEAN;

FUNCTION Symbol(Prm:TSetOfChar): BOOLEAN;

BEGIN IF (x IN Prm) THEN BEGIN Symbol:=TRUE; READ(Vh,x) END

ELSE Symbol:=FALSE

END;

FUNCTION Variable: BOOLEAN;

BEGIN Variable:=Symbol(['a'..'z']) END;

FUNCTION Operation: BOOLEAN;

BEGIN Operation:=Symbol(['+','-','*','/']) END;

FUNCTION ExprA: BOOLEAN;

BEGIN ExprA:=Variable OR

Symbol(['(']) AND ExprA AND Operation

AND ExprA AND Symbol([')'])

END;

BEGIN AssignFile(Vh,'p8Vh.TXT'); RESET(Vh); bb:=FALSE;

IF NOT EOF(Vh) THEN BEGIN READ(Vh,x); bb:=ExprA END;

IF bb AND (x='$') THEN ShowMessage('Правильное АВ')

ELSE ShowMessage('Неправильное АВ');

CloseFile(Vh)

END.

  • Функциональный вид и адекватность программы; структура арифметического выражения и дерево подзадач, порядок его разворачивания (обхода), итеративный характер процесса построения фрагментов плана и соответствующих вычислений по плану.

  • Замечания о необходимости аккуратно добавить охрану к оператору READ в связи с возможностью EOF.

  • Использование функций с «побочным эффектом», что не приветствует современная методология программирования.

  • Использование неклассической процедурной семантики логических выражений.

  • Причины отмеченных недостатков и проблемы их устранения с сохранением функционального вида программы:

  • рекурсивное программирование требует внимательного отношения к проектированию состава и вида опорных функций;

  • язык Pascal допускает рекурсию, но не является ориентированным на функциональное рекурсивное программирование.

  • Задача «Вычислить значение арифметического выражения». Выбор способа представления данных и набора базовых функций - пока безотносительно к способу их реализации.

  • Представление данных:

СПИСОК (S1,S2,...Sk) - последовательность элементов, где элемент - либо АТОМ (неделимое на составные части данное), либо СПИСОК.

  • Представление данных для задачи «Вычислить значение арифметического выражения»:

Пример для (A+(B-C))/((D/E)*F)

  • (операция, (аргумент1, аргумент2))

( /,( (+,(A, (-,(B,C)) )) , (*,( (/,(D,E)) ,F)) ))

  • (операция, аргумент1, аргумент2)

( /, (+, A, (-,(B,C)) ) , (*, (/,(D,E)) ,F ) )

  • линейное (посимвольное) представление (атомы подчеркнуты, чтобы отличить от метасимволов)

( (,A,+,(,B,-,C,),),/,(,(,D,/,E,),*,F,) )

  • ниже будем использовать только первое

  • Список - универсальная структура данных, позволяет представить традиционные - ARRAY, RECORD, FILE

  • Базовые функции

  • Селекторы компонентов списка (в роли компонентных переменных языка Pascal)

  • CAR(x) или HEAD(x) - первый (головной) элемент списка x : CAR( (S1,S2,...) )=S1

  • CDR(x) или TAIL(x) – «хвост» списка x : CDR( (S1,S2,...) )= (S2,S3,...)

  • Пример селекции компонента:

пусть Expr=(/,((+,(A,(-,(B,C)))),(*,((/,(D,E)),F))))

тогда CDR(CDR(CAR(CDR(Expr))))= (-,(B,C))

  • Константа NIL - пустой список; CDR( (S1) )=NIL, CAR(NIL)=NIL, т.е. одновременно NIL - пустой атом

  • Логические функции (предикаты)

  • ATOM(x) - x является атомом; значением функции CAR может быть список или атом, аргументы у CAR и CDR должны быть списками (атомы не допускаются)

  • EQ(x,y) - x и y одинаковые атомы, хотя бы один аргумент должен быть атомом - реализация более общего более сложного случая возлагается на программиста; символ = применим только к атомам

  • Конструктор списков (в роли описателя типов данных языка Pascal)

  • CONS(x,y) - список получающийся из списка y добавлением x в качестве первого элемента; x - может быть атомом или списком, но y - только списком; CONS( S, (S1,S2,...) )=(S,S1,S2,...)

NB. CAR(CONS(x,y))=x, CDR(CONS(x,y))=y, CONS(CAR(x),CDR(x))=x (!!! точнее, копия x).

  • Отметим, что список – это не стек, а много более общая структура данных, но приведенный набор базовых функций обеспечивает именно стековый доступ к компонентам списка.

CAR – позволяет просматривать значение элемента в вершине стека. CDR – соответствует операции «Вытолкнуть верхний элемент из стека». CONS – операции «Положить элемент в вершину стека».

  • Программа «Вычислить значение арифметического выражения»:

FUNCTION Вычислить(L:LIST):REAL; VAR m,n:LIST;

BEGIN IF ATOM(L) THEN Вычислить:=Значение(L) ELSE

BEGIN m:=CAR(CAR(CDR(L))) {NB.CDR(L)=((арг1,арг2))};

n:=CAR(CDR(CAR(CDR(L))));

CASE CAR(L) OF

’+’: Вычислить:=Вычислить(m) + Вычислить(n);

’-’: Вычислить:=Вычислить(m) - Вычислить(n);

’*’: Вычислить:=Вычислить(m) * Вычислить(n);

’/’: Вычислить:=Вычислить(m) / Вычислить(n) END END END

Получили весьма компактную и прозрачную программу для совсем не примитивной задачи... благодаря чему?

  • Воспользовались функцией «Значение», позволяющей извлечь значение переменной. Для реализации этой функции придется разработать структуру данных для хранения этих значений...

  • Воспользовались рекурсией.

  • Воспользовались хорошо структурированным подходящим входным представлением арифметических выражений. Построить такое представление по обычному текстовому – тоже совсем не примитивная задача.

  • Еще раз рассмотрим прием использования накапливающих параметров для рекурсивного определения функций.

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]