Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Михалкович С.С. Основы программирования

.pdf
Скачиваний:
74
Добавлен:
19.05.2015
Размер:
556.43 Кб
Скачать

procedure QuickSort(var A: IArr; n: integer);

procedure QuickSort0(l,r: integer); var i,j,x: integer;

begin

i:=l; j:=r; x:=A[(l+r) div 2]; repeat

while A[i]<x do Inc(i); while A[j]>x do Dec(j); if i<=j then

begin

Swap(A[i],A[j]);

Inc(i); Dec(j); end

until i>j;

if l<j then QuickSort0(l,j); if i<r then QuickSort0(i,r);

end;

begin

QuickSort0(1,n); end;

Здесь l и r – левая и правая границы сортируемой части массива, (l+r) div 2 – индекс среднего элемента.

2.8 Рекурсия в модулях

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

Пусть подпрограмма р вызывает подпрограмму q, а подпрограмма q вызывает подпрограмму р, то есть имеет место косвенная рекурсия. Наша задача – поместить р в модуль Unit1.pas, а q – в модуль Unit2.pas.

Циклическая зависимость между модулями запрещена, если обе ссылки на модули в секции uses содержатся в разделе interface. То есть, следующие описания приведут к ошибке:

Unit1.pas

Unit2.pas

unit Unit1;

unit Unit2;

interface

interface

uses Unit2;

uses Unit1;

procedure p;

procedure q;

...

...

 

33

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

Unit1.pas

Unit2.pas

unit Unit1;

unit Unit2;

interface

interface

procedure p;

procedure q;

implementation

implementation

uses Unit2;

uses Unit1;

procedure p;

procedure q;

begin

begin

...

...

q;

p;

end;

end;

end.

end.

При подключении модулей обычно придерживаются следующего правила: если это возможно, стремятся указать подключаемый модуль в разделе implementationon, и только если нет, то в разделе interface.

2.9 Интерпретатор формул

Реализуем разбор и вычисление выражения, заданного следующей граммати-

кой:

Expr ::= Term { "+" Term | "-" Term } Term ::= Factor { "*" Factor }

Factor ::= num | ( Expr )

num ::= "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"

Формула представляет собой выражение со скобками, знаками операций + - * и целыми числами от 0 до 9. Формула будет разбираться и тут же вычисляться. Посуществу, мы напишем простейший интерпретатор формул. Правилам грамматики Expr, Term и Factor поставим в соответствие функции, возвращающие целое значение. Заметим, что определение грамматики содержит косвенную рекурсию: Expr определяется через Term, Term – через Factor, Factor – вновь через Expr. Поэтому перед описанием функции Expr следует использовать forward-объявления функций Term и Factor.

34

Пусть формула хранится в строке s и является правильной, номер текущей позиции в строке s хранится в целой переменной ns. Перед тем как проанализировать следующий символ, вызывается процедура MoveNext, переходящая на следующую лексему в строке s и записывающая ее в символьную переменную CurSym. Если строка закончилась, то в переменную CurSym записывается нулевой символ. Чтобы уменьшить количество глобальных переменных, поместим все функции грамматики и процедуру MoveNext внутрь функции-оболочки Calc и сделаем ns и CurSym ее локальными переменными. Функция Calc будет принимать строку s в качестве параметра и возвращать вычисленное выражение.

Приведем далее полный текст программы. function Calc(s: string): integer; var

ns: integer; CurSym: char;

procedure MoveNext; begin

Inc(ns);

if ns<=Length(s) then CurSym:=s[ns]

else CurSym:=#0; end;

function Num: integer; begin

Result:=Ord(CurSym)-Ord('0'); end;

function Term: integer; forward; function Factor: integer; forward;

function Expr: integer; begin

Result:=T;

while CurSym in ['+','-'] do begin

if CurSym='+' then begin

MoveNext;

Result:=Result+Term; end

else begin

MoveNext; Result:=Result-Term;

35

end end;

end;

function Term: integer; begin

Result:=Factor; while CurSym='*' do begin

MoveNext;

Result:=Result*Factor; end;

end;

function Factor: integer; begin

if CurSym='(' then begin

MoveNext;

Result:=Expr end

else Result:=Num; // CurSym является цифрой MoveNext;

end;

begin // Calc ns:=0; MoveNext; Result:=Expr;

end;

begin // основная программа writeln(Calc('1+2-4'));

end.

Обратим внимание на то, что процедура MoveNext должна быть вызвана перед каждой функцией грамматики и, в частности, в начале функции Calc.

2.10 Перевод формулы в префиксную форму

Обычной формой записи выражений является инфиксная. В ней знак бинарной операции op записывается между операндами: A op B. Если знак бинарной операции записывается перед операндами, то такая форма запись называется префиксной: op A B. Если же знак бинарной операции записывается после операндов, то форма записи выражения называется постфиксной: A B op.

36

Например:

 

 

Инфиксная форма

Префиксная форма

Постфиксная форма

1+2*4

+1*24

124*+

(1+2)*4

*+124

12+4*

Обратим внимание, что ни префиксная, ни постфиксная форма не содержат скобок; по этой причине их часто называют префиксной бесскобочной и постфиксной бесскобочной формой записи выражения.

Реализуем компилятор, переводящий выражение в инфиксной форме в префиксную бесскобочную форму. В отличие от предыдущей программы, функции Expr, Term, Factor будут возвращать строковое представление префиксной бесскобочной формы. Как и в предыдущем примере, поместим функции Expr, Term, Factor и процедуру MoveNext внутрь функции Prefix, принимающей в качестве параметра строку s – выражение в инфиксной форме.

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

function Prefix(s: string): string; var

ns: integer; CurSym: char;

procedure MoveNext; begin

Inc(ns);

if ns<=Length(s) then CurSym:=s[ns]

else CurSym:=#0; end;

function Term: string; forward; function Factor: string; forward;

function Expr: string; begin

Result:=Term;

while CurSym in ['+','-'] do begin

Result:=CurSym+Result;

MoveNext;

37

Result:=Result+Term; end;

end;

function Term: string; begin

Result:=Factor; while CurSym='*' do begin

Result:='*'+Result;

MoveNext;

Result:=Result+Factor; end;

end;

function Factor: string; begin

if CurSym='(' then begin

MoveNext;

Result:=Expr;

if CurSym=')' then MoveNext else error;

end

else if CurSym in ['0'..'9'] then begin

Result:=CurSym; MoveNext

end

else error end;

begin // Prefix MoveNext; Result:=Expr;

end;

function Calc(s: string): integer; var i: integer;

function Calc0: integer; var o1,o2: integer; begin

Inc(i);

case s[i] of

'+': begin o1:=Calc0; o2:=Calc0; Result:=o1+o2; end;

38

'-': begin o1:=Calc0; o2:=Calc0; Result:=o1-o2; end; '*': begin o1:=Calc0; o2:=Calc0; Result:=o1*o2; end; '0'..'9': Result:=Ord(s[i])-Ord('0');

end; end;

begin // Calc i:=0; Result:=Calc0;

end;

var res: string;

begin res:=Prefix('(1+2)*3');

writeln(res); writeln(Calc(res)); end.

2.11 Алгоритм перебора с возвратом

Алгоритм перебора с возвратом используется, когда решение представляет собой некоторую последовательность элементов: a1, a2 ,..., an . Суть его заключает-

ся в следующем. На каждом этапе имеется некоторое частичное решение a1, a2 ,..., ak , к которому мы пытаемся добавить следующий элемент из множества

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

Пусть глобальная логическая переменная Success получает значение True как только будет найдено решение. До начала работы алгоритма она должна быть инициализирована в False. Пусть также i – переменная, характеризующая глубину рекурсии и передаваемая как параметр рекурсивной процедуре. Запишем алгоритм в «крупных командах».

Алгоритм перебора с возвратом для нахождения одного решения procedure Try0(i: integer);

begin

формирование списка кандидатов repeat

выбор очередного кандидата (перебор) if подходит then

begin

39

запись кандидата

if решение неполное then begin

Try0(i+1);

if not Success then

стирание кандидата (возврат)

end;

else Success:=True end;

until Success or кандидатов больше нет end;

Отметим, что как только переменная Success получает значение True, мы выходим из всех рекурсивных вызовов процедуры Try0, не совершая никаких дополнительных действий.

Рассмотрим реализацию указанного выше алгоритма на примере задачи обхода конем шахматной доски размера n ×n . Составим вначале внутреннюю процедуру Try0, для которой переменная Success (удача) и массив Solution (решение) будут глобальными.

Всего из данной клетки конь может совершить максимум 8 ходов, которые мы пронумеруем так, как показано на рисунке.

Для каждого из этих ходов необходимо проверить, находится ли клетка в пределах шахматной доски, и не ходил ли конь в эту клетку раньше. Если клетка

– в пределах доски и конь в нее еще не ходил, то мы пробуем совершить в нее ход, помечая клетку номером хода, после чего, если все клетки обойдены, то мы устанавливаем переменную Success в True (решение найдено), в противном случае вызываем процедуру Try0 повторно с координатами новой клетки в качестве параметра и проверяем, решена ли задача. Если задача не решена, то ход следует признать неудачным и стереть его из списка ходов (возврат).

Далее приводится код решения. program KnightWay; const

n=8;

dx: array [1..8] of integer=(-1,1,2,2,1,-1,-2,-2); dy: array [1..8] of integer=(2,2,1,-1,-2,-2,-1,1);

var Solution: array [1..n,1..n] of integer; // массив ходов

40

procedure TryM(x,y: integer; var Success: boolean); procedure Try0(i,x,y: integer);

var k,

//

номер кандидата

u,v: integer; //

координаты следующего хода коня

begin k:=0; repeat

Inc(k);

u:=x+dx[k]; v:=y+dy[k];

if (u>0) and (u<=n) and (v>0) and (v<=n) and (Solution[u,v]=0) then

begin

Solution[u,v]:=i; if i<n*n then begin

Try0(i+1,u,v);

if not Success then

Solution[u,v]:=0;

end

else Success:=True; end

until Success or (k=8); end;

var i,j: integer;

begin

// TryM

 

do

for

i:=1

to n

for

j:=1

to

n

do

Solution[i,j]:=0;

Solution[x,y]:=1;

Success:=False;

Try0(2,x,y); end; // TryM

var x0,y0: integer; Success: boolean;

begin // основная программа

read(x0,y0);

TryM(x0,y0,Success); if Success then

WriteArr(Solution,n); // вывод массива ходов else writeln('решений нет');

end.

41

В глобальной процедуре TryM, содержащей Try0, обнуляется массив Solution и совершается первый ход, после чего переменная Success полагается равной False и вызывается Try0 с координатами первой клетки в качестве параметров.

Алгоритм перебора с возвратом для нахождения всех решений

Алгоритм построения всех решений проще предыдущего, поскольку в нем отсутствует флаг Success и после нахождения решения оно сразу же обрабатывается (например, выводится на экран), и поиск оставшихся решений продолжается.

procedure Try0(i: integer); begin

Формирование списка кандидатов repeat

выбор очередного кандидата if подходит then begin

запись кандидата

if решение неполное then

Try0(i+1)

else печать решения (или его обработка)

стирание кандидата end

until кандидатов больше нет end;

Упражнение. Вывести все решения в задаче об обходе конем шахматной доски.

Литература

1.Зеленяк О. Практикум программирования на Turbo Pascal / О. Зеленяк. – М.: DiaSoft, 2002. – 310 с.

2.Ставровский А. Турбо Паскаль 7.0 / А.Ставровский. – Киев: BHV, 2001. – 400 с.

3.Вирт Н. Алгоритмы и структуры данных / Н. Вирт. – М.: Мир, 1989. – 360 с.

42