
- •Программирование
- •В языках программирования
- •Примеры и контрпримеры
- •Примеры и контрпримеры
- •Определение
- •[Править]Примеры
- •8) Абстрактный автомат
- •Устройство машины Тьюринга
- •[Править]Описание машины Тьюринга
- •Пример машины Тьюринга
- •Метод "пузырька"
- •Сортировка вставками
- •Сортировка посредством выбора
- •Алгоритмы поиска
- •I, j: integer; { индексы массива }
- •16) В программировании [править]Функции
- •17) Перебор с возвратом
- •3.1 Использование рекурсии для записи алгоритма
- •3.2 Примеры решения задач при помощи перебора с возвратом
- •3.3 Возврат
- •Терминология и свойства
- •24) Топологическая сортировка — упорядочивание вершин бесконтурного ориентированного графа согласно частичному порядку, заданному ребрами орграфа на множестве его вершин. Пример
- •[Править]Алгоритм
- •[Править]Пример работы алгоритма
16) В программировании [править]Функции
В программировании рекурсия —
вызов функции (процедуры)
из неё же самой, непосредственно (простая
рекурсия)
или через другие функции (сложная или косвенная
рекурсия),
например, функция
вызывает
функцию
,
а функция
—
функцию
.
Количество вложенных вызовов функции
или процедуры называется глубиной
рекурсии.
Преимущество рекурсивного определения объекта заключается в том, что такое конечное определение теоретически способно описывать бесконечно большое число объектов. С помощью рекурсивной программы же возможно описать бесконечное вычисление, причём без явных повторений частей программы.
Реализация рекурсивных вызовов функций в практически применяемых языках и средах программирования, как правило, опирается на механизм стека вызовов — адрес возврата и локальные переменные функции записываются в стек, благодаря чему каждый следующий рекурсивный вызов этой функции пользуется своим набором локальных переменных и за счёт этого работает корректно. Оборотной стороной этого довольно простого по структуре механизма является то, что на каждый рекурсивный вызов требуется некоторое количество оперативной памяти компьютера, и при чрезмерно большой глубине рекурсии может наступить переполнение стека вызовов. Вследствие этого, обычно рекомендуется избегать рекурсивных программ, которые приводят (или в некоторых условиях могут приводить) к слишком большой глубине рекурсии.
Имеется специальный тип рекурсии, называемый «хвостовой рекурсией». Интерпретаторы и компиляторы функциональных языков программирования, поддерживающие оптимизацию кода (исходного или исполняемого), автоматически преобразуют хвостовую рекурсию к итерации, благодаря чему обеспечивается выполнение алгоритмов с хвостовой рекурсией в ограниченном объёме памяти. Такие рекурсивные вычисления, даже если они формально бесконечны (например, когда с помощью рекурсии организуется работа командного интерпретатора, принимающего команды пользователя), никогда не приводят к исчерпанию памяти. Однако, далеко не всегда стандарты языков программирования чётко определяют, каким именно условиям должна удовлетворять рекурсивная функция, чтобы транслятор гарантированно преобразовал её в итерацию. Одно из редких исключений — язык Scheme (диалект языка Lisp), описание которого содержит все необходимые сведения.
17) Перебор с возвратом
Вариант алгоритма перебора, на котором мы остановились в подразделе 2.1, называется перебором с возвратом. Слово ``возврат'' выражает то, что есл происходит возврат к последнему выбранному элементу и выбирается для него следующее значение.
3.1 Использование рекурсии для записи алгоритма
Приведенная нами в подразделе 2.1 схема алгоритма довольно проста, однако она обычно неприемлема для реализации. Действительно, вложенные друг в друга циклы – очень жесткая структура и если нам надо решать задачу для 100 ферзей, то нам придется писать 100 вложенных друг в друга циклов. Более того, для каждого n нам придётся писать свой вариант алгоритма, отличающийся количеством вложенных циклов. Поэтому вариант с вложенными циклами обычно бывает неприемлемым для реализации перебора с возвратом. В книге [2] приведён алгоритм перебора с возвратом основанный на интерпретации перебора как последовательности переходов вперед-назад. Мы сейчас приведем схему алгоритма перебора с возвратом, построенную на принципах структурного программирования (т.е. без переходов) с использованием рекурсии.
Посмотрим на текст алгоритма. Сам текст алгоритма рекурсивен! Текст для n=k состоит из текста для n=k-1, помещённого в цикл. Таким образом, алгоритм легко переписать в рекурсивном виде. Сначала мы приведём рекурсивный вариант алгоритма полного перебора.
procedure полный_перебор(m : integer);
var i : integer;
begin
if m > n then
<проверка построенной позиции>
else
for i := 1 to n do begin
ферзь[m] := i;
полный_перебор(m+1);
end;
end;
Теперь – рекурсивный вариант алгоритма перебора с возвратом.
procedure перебор_с_возвратом(m : integer);
var i : integer;
begin
if m > n then
<найдено решение>
else
for i := 1 to n do begin
ферзь[m] := i;
if <ферзь[m] не бьёт предыдущих> then
перебор_с_возвратом(m+1);
end;
end;
Чем отличаются данные алгоритмы ? Тем, что в первом проверка позиции происходит на самом позднем (``глубоком'') этапе перебора, а во втором на каждом этапе перебора происходит частичная проверка. (Если же все частичные проверки завершились успешно, получившуюся позицию уже можно не проверять.) Таким образом, перебор с возвратом исключает из перебора большое количество вариантов по сравнению с полным перебором.