Опубликован: 06.09.2005 | Уровень: для всех | Доступ: свободно
Лекция 9:

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

< Лекция 8 || Лекция 9: 1234 || Лекция 10 >
Аннотация: Динамические структуры данных: стек, очередь, дек. Рекурсивные процедуры и функции. Сравнение рекурсивных и нерекурсивных алгоритмов. Быстрая сортировка массива.

Динамические структуры данных

Динамические структуры данных служат полезным дополнением к стандартным структурам, уже определенным в языке Pascal. Динамическими они называются потому, что их элементы создаются и уничтожаются "на ходу" - в процессе работы программы.

Стек

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

Последовательность обработки элементов стека хорошо отражают аббревиатуры LIFO ( L ast I n F irst O ut - "последним вошел, первым вышел") и FILO ( F irst I n L ast O ut - "первым вошел, последним вышел").

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

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

Операции

Для стека должны быть определены следующие операции:

empty(<нач_стека>):boolean - проверка стека на пустоту;
add(<нач_стека>,<новый_элемент>):<нач_стека> - добавление элемента в стек ;
take(<нач_стека>):<тип_элементов_стека> - считывание значения верхнего элемента;
del(<нач_стека>):<нач_стека>. - удаление верхнего элемента из стека.

Очередь

Очередью называется динамическая структура, у которой в каждый момент времени доступны два элемента: первый и последний. Из начала очереди элементы можно удалять, а к концу - добавлять. Примером очереди программистcкой вполне может служить очередь обывательская: скажем, в продуктовом магазине.

Последовательность обработки элементов очереди хорошо отражают аббревиатуры LILO ( L ast I n L ast O ut - "последним вошел, последним вышел") и FIFO ( F irst I n F irst O ut - "первым вошел, первым вышел").

Реализовать очередь также можно при помощи массива, хотя здесь уже не удастся полностью избежать перемещения его компонент. Пусть k -я компонента массива хранит начало очереди, а ( k+s )-я - ее конец. Тогда можно приписать новый элемент очереди в ( k+s+1 )-ю компоненту массива, а при удалении элемента из начала очереди ее голова сдвинется в ( k+1 )-ю компоненту. В процессе работы может оказаться, что вся очередь "сдвинулась" к концу массива, и ее снова нужно вернуть к началу. В этом случае и потребуется s перемещений компонент массива ( s - это текущая длина очереди ).

Однако наиболее эффективной снова будет реализация при помощи односвязного линейного списка (см. лекцию 10).

Операции

Для очереди должны быть определены следующие операции:

empty(<нач_очереди>):boolean - проверка очереди на пустоту;
add(<кон_очереди>,<нов_эл-т>):<кон_очереди> - добавление элемента в конец очереди ;
take_beg(<нач_очереди>):<тип_эл-тов_очереди> - считывание значения первого элемента;
take_end(<кон_очереди>):<тип_эл-тов_очереди> - считывание значения последнего элемента;
del(<нач_очереди>):<нач_очереди> - удаление элемента из начала очереди.

Дек

Дональд Кнут1См. Д. Кнут. Искусство программирования для ЭВМ, любое издание. Т.1 Основные алгоритмы. ввел понятие усложненной очереди, которая называется дек (deque - D ouble- E nded QUE ue - двухконцевая очередь ). В каждый момент времени у дека доступны как первый, так и последний элемент, причем добавлять и удалять элементы можно и в начале, и в конце дека. Таким образом, дек - это симметричная двусторонняя очередь.

Реализация дека при помощи массива ничем не отличается от реализации обычной очереди, а вот в терминах списков дек удобнее представлять двусвязным (разнонаправленным) линейным списком (см. лекцию 10).

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

Рекурсия

В математике, да и не только в ней одной, часто встречаются объекты, определяемые при помощи самих себя. Они называются рекурсивными.

Например, рекурсивно определяется функция факториал:

0! =1
n! = n*(n-1)!, для любого натурального n.

Другим примером рекурсивного определения может послужить определение арифметического выражения, приведенное в лекции 2.

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

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

Если подпрограмма просто вызывает сама себя, то такая рекурсия называется прямой. Например:

procedure rec1(k: byte);      function rec2(k: byte): byte;
    begin                     begin
        ...                       ...
        rec1(k+1);                x:= rec2(k+1);
        ...                       ...
    end;                      end;

Если же несколько подпрограмм вызывают друг друга, но эти вызовы "замкнуты в кольцо", то такая рекурсия называется косвенной.

В случае косвенной рекурсии возникает проблема с описанием подпрограмм: по правилу языка Pascal, нельзя использовать никакой объект раньше, чем он был описан. Следовательно, невозможно написать в программе:

procedure rec_А(k: byte);
begin
	...
	reс_В(k+1);
	...
end;
procedure rec_В(k: byte);
begin
	...
	rec_А(k+1);
	...
end;

И здесь полезной оказывается возможность отрывать объявление подпрограммы от ее описания (см. лекцию 8). Например, для косвенной рекурсии в случае двух процедур, вызывающих друг друга ( rec_A -> rec_B -> rec_A ), нужно такое описание:

procedure rec_А(k: byte); forward;
procedure rec_В(k: byte);
begin
	...
	reс_А(k+1);
	...
end;

procedure rec_A;
begin
	...
	rec_В(k+1);
	...
end;
< Лекция 8 || Лекция 9: 1234 || Лекция 10 >
Евгения Поздеева
Евгения Поздеева
Ольга Стебакова
Ольга Стебакова

Вот фрагмент лекции 5 (статья 4):

Проверка множества на пустоту может быть осуществлена довольно просто:

pusto:= true;   for i:= 1 to N do 

if set_arr[i] then begin pusto:= false; break end; {мне кажется здесь должно быть так:

if set_arr[i]<>0 then begin pusto:= false; break end;}

Хотелось бы знать это ошибка в теории или я просто не поняла лекцию?