Журнал о программированнии на языках Blitz3D, BlitzPlus, BlitzMax

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

Материал из Blitz Et Cetera

Версия от 04:23, 25 февраля 2008; Matt Merkulov (Обсуждение | вклад)
(разн.) ← Предыдущая | Текущая версия (разн.) | Следующая → (разн.)
Перейти к: навигация, поиск

Предполагается, что ты уже понимаешь основные Блитзовские функции и программный поток.(Оператор If, циклы For...Next; While...Wend и т.п. )

Содержание

Что такое рекурсия?

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

Простой пример - Функция факториала

Мы будем писать функцию, которая будет возвращать факториал определенного числа. Факториал числа (n), это про произведение всех целых чисел от 1 до n. Например: Факториал 5 = 1 x 2 x 3 x 4 x 5, что равно 120. Не беспокойся сильно об этом, ведь дело не в том, ЧТО мы делаем, а КАК мы это делаем. Если мы будем писать функцию факториала по стандартному пути, то у нас получится что-то вроде этого:

Print factorial(5)


Function factorial(number)
 For n = 1 To number
  If n = 1 Then
   result = 1
  Else
   result = n * result
  End If
 Next
 Return result

End Function

Используя рекурсию, наш код будет выглядеть так:

Print factorial(5)


Function factorial(n)
 If n = 1 Then
  Return 1
 Else
  Return n * factorial(n - 1)
 End If

End Function

Большой разницы между двумя видами функций нет, зато это показывает технику рекурсии.

Ну и как это работает?

Рекурсия просто заменила цикл For n=1 to number...Next . Если мы будем проверять, что происходит на каждом шагу, то мы увидим:

возвращает значение 5 * factorial(4)

factorial(4) возвращает значение 4 * factorial(3)
возвращает значение 3 * factorial(2)
возвращает значение 2 * factorial(1)
factorial(1) просто возвращает 1 и больше не вызывает функцию factorial()

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

factorial(1) = 1
factorial(2) = 2 x 1 = 2, 1 - это значение, полученное из factorial(1)
factorial(3) = 3 x 2 = 6, 2 - это значение, полученное из factorial(2)
factorial(4) = 4 x 6 = 24, 6 - это значение, полученное из factorial(3)

factorial(5) = 5 x 24 = 120, 24 - это значение, полученное из factorial(4)

Бесконечность: не катит!

В вышеприведенном примере ты заметил, что функция зовет себя, только если n не равно 1 и, так как n всегда уменьшается, мы знаем, что будет конечное число вызовов. Однако если не впихнуть это условие, то функция будет звать себя бесконечное число раз и Blitz скажет тебе тихим и поставленным голосом: "Stack Overflow" .

Советую всегда вставлять такие условия в рекурсивные функции, чтобы они звали себя максимальное количество раз(а не бесконечное). Это показано в следующем примере:

A Навороченный пример - Ковер Серпинского(Sierpinski)

Мы изучили основы, пора бы забабахать что-нибудь поинтересней. Ковер Серпинского назван так в честь польского математика:

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

Схема построения ковра Серпинского.

  • Шаг 1. Начнем с рисования белого, закрашенного квадрата размером с наш ковер.
  • Шаг 2. Поделим этот квадрат на 9 равных квадратиков и закрасим средний черным цветом.
  • Шаг 3. Повторим Шаг 2 для оставшихся восьми квадратиков. Каждая итерация (повторение) создаст 9 квадратиков поменьше вокруг "родителя".

Диаграмма внизу показывает 3 итерации. Заготовка растет экспоненциально на каждом уровне.

  • Итак, на первом куске диаграммы мы поделили квадрат на девять равных квадратов и закрасили центральный черным цветом
  • После следующей итерации(2-ой кусок диаграммы) у нас появилось ещё 8 черных квадратиков вокруг здорового, полученного после первой итерации.
  • Ну а после третей итерации(3-ий кусок диаграммы) появляется по 8 квадратиков для каждого из восьми квадратов, полученных после 2-ой итерации.

Надеюсь, ты понял, как это всё получается. Позырим теперь, как оно делается: А теперь КОД!

;устанавливаем экран и высчитываем его центр

Const screenWidth = 800, screenHeight = 600
Graphics screenWidth, screenHeight, 16, 2
centerX = screenWidth / 2
centerY = screenHeight / 2

;Задаем размер ковра и порог для рекурсивной фунуции
Const carpetSize = 600                  ;задаем размер ковра
Const threshold = 4                             ;задаем порог рекурсии

;Рисуем белый квадрат посередине экрана, размером с ковер
offset = carpetSize / 2
Rect centerX - offset, centerY - offset, carpetSize, carpetSize

;ставим цвет рисования черным и приступаем к рекурсии...
Color 0, 0, 0
carpet(centerX, centerY, carpetSize, threshold)

;СЁ, готово! После нажатия на любую кнопку прога завершается
WaitKey
End


 
;рекурсивная функция
Function carpet(x, y, size, threshold)
        newSize = size / 3              ;1/3 предыдущего размера
        offset = newSize / 2            ;новое значение отступа

        ;рисуем черный квадрат в центре(это и есть "родитель", центральный)
        Rect x - offset, y - offset, newSize, newSize

        If threshold > 0 Then
                newThreshold = threshold - 1    ;уменяшаем порог - не забудь про это!
               
                ;зовем функцию ковра 8 раз для каждого квадратика вокруг центрального
                carpet(x - newSize, y - newSize, newSize, newThreshold)
                carpet(x, y - newSize, newSize, newThreshold)
                carpet(x + newSize, y - newSize, newSize, newThreshold)
                carpet(x + newSize, y, newSize, newThreshold)
                carpet(x + newSize, y + newSize, newSize, newThreshold)
                carpet(x, y + newSize, newSize, newThreshold)
                carpet(x - newSize, y + newSize, newSize, newThreshold)
                carpet(x - newSize, y, newSize, newThreshold)
        End If

End Function

Как оно работает?

Каждый раз, когда вызывается функция, рисуется новый квадрат и запрашивается 4 переменные: позиция по x&y нового квадрата, его размер (это будет размер всего ковра, если функция вызывается первый раз) и текущий порог рекурсии.

  • Для начала функции надо поделить текущий квадрат (определяется параметром size) на 9 равных квадратов, чтобы закрасить центральный. Делается это делением size на 3. Т.к. мы знаем позицию центра, мы легко можем закрасить центральный квадрат. Обрати внимание, как значение offset используется при рисовании квадрата.
  • После этого нужно проверить значение порога рекурсии.
  • Если мы достигли порога (threshold = 0), то мы закончили. В обратном случае мы ставим новое значение порога. Затем просто зовем функцию 8 раз, используя новые значения size и threshold. Нам также необходимо определить новые значения x и y для оставшихся 8-ми квадратов.

Ограничения

Как объяснялось выше, должно быть конечное число итераций рекурсивных функций, чтобы получалось то, что тебе нужно сделать. Я бы не рекомендовал ставить порог рекурсии более нескольких тысяч.

Вывод?

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

Хорошо провести время.:


Автор: Ghost Dancer (e-mail: ghost_sobaka_aurora-soft.co.uk, сайт: http://www.aurora-soft.co.uk)
Дата создания: 8th February 2003г.
Перевод: MANIAK dobrii (e-mail: MANIAK_dobrii_sobaka_list.ru, сайт: http://www.maniak-dobrii.nm.ru/)
Дата перевода: 11 октября 2004г.

Другие

Друзья