Чтение онлайн

на главную - закладки

Жанры

Интернет-журнал "Домашняя лаборатория", 2007 №9
Шрифт:

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

Для

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

public long factorial (int n)

{

if (n<=1) return(1);

else return(n*factorial(n-1));

}//factorial

Функция factorial является примером прямого рекурсивного определения — в ее теле она сама себя вызывает. Здесь, как и положено, есть нерекурсивная ветвь, завершающая вычисления, когда n становится равным единице. Это пример так называемой "хвостовой" рекурсии, когда в теле встречается ровно один рекурсивный вызов, стоящий в конце соответствующего выражения. Хвостовую рекурсию намного проще записать в виде обычного цикла. Вот циклическое определение той же функции:

public long fact(int n)

{

long res =1;

for (int i = 2; i <=n; i + +) res* = i;

return(res);

}//factorial

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

public void TestTailRec

{

Hanoi han = new Hanoi (5);

long time1, time2;

long f=0;

time1 = getTimeInMilliseconds;

for (int i = 1; i <1000000; i + +)f =han.fact(15);

time2 =getTimeInMilliseconds ;

Console.WriteLine(" f= {0}, " + "Время работы циклической процедуры:

{1}",f,time2 — time1);

time1 = getTimeInMilliseconds ;

for(int i = 1; i <1000000; i + +)f =han.factorial (15);

time2 =getTimeInMilliseconds ;

Console.WriteLine(" f= {0}, " + "Время работы рекурсивной процедуры:

1}",f,time2 — time1);

Каждая из функций вызывается в цикле, работающем 1000000 раз. До начала цикла и после его окончания вычисляется текущее время. Разность этих времен и дает оценку времени работы функций. Обе функции вычисляют факториал числа 15.

Проводить сравнение эффективности работы различных вариантов — это частый прием, используемый при разработке программ. И я им буду пользоваться неоднократно. Встроенный тип DateTime обеспечивает необходимую поддержку для получения текущего времени. Он совершенно необходим, когда приходится работать с датами. Я не буду подробно описывать его многочисленные статические и динамические методы и свойства. Ограничусь лишь приведением функции, которую я написал для получения текущего времени, измеряемого в миллисекундах. Статический метод Now

класса DateTime возвращает объект этого класса, соответствующий дате и времени в момент создания объекта. Многочисленные свойства этого объекта позволяют извлечь требуемые характеристики. Приведу текст функции getTimeInMilliseconds:

long getTimeInMilliseconds

{

DateTime time = DateTime.Now;

return(((time.Hour*60 + time.Minute)*60 + time.Second)*1000

+ time.Millisecond);

}

Результаты измерений времени работы рекурсивного и циклического вариантов функций слегка отличаются от запуска к запуску, но порядок остается одним и тем же. Эти результаты показаны на рис. 10.1.

Рис. 10.1. Сравнение времени работы циклической и рекурсивной функций

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

Классическим примером являются методы сортировки. Известно, что время работы нерекурсивной пузырьковой сортировки имеет порядок с*n2, где с — некоторая константа. Для рекурсивной процедуры сортировки слиянием время работы — q*n*log(n), где q — константа. Понятно, что для больших n сортировка слиянием работает быстрее, независимо от соотношения значений констант. Сортировка слиянием — хороший пример применения рекурсивных методов. Она демонстрирует известный прием, называемый "разделяй и властвуй". Его суть в том, что исходная задача разбивается на подзадачи меньшей размерности, допускающие решение тем же алгоритмом. Решения отдельных подзадач затем объединяются, давая решение исходной задачи. В задаче сортировки исходный массив размерности n можно разбить на два массива размерности n/2, для каждого из которых рекурсивно вызывается метод сортировки слиянием. Полученные отсортированные массивы сливаются в единый массив с сохранением упорядоченности.

На примере сортировки слиянием покажем, как можно оценить время работы рекурсивной процедуры. Обозначим через T(n) время работы процедуры на массиве размерности n. Учитывая, что слияние можно выполнить за линейное время, справедливо следующее соотношение:

Т(n) = 2Т(n/2) + сn

Предположим для простоты, что п задается степенью числа 2, то есть n = 2к. Тогда наше соотношение имеет вид:

Т(2k) = 2Т(2k—1) + с2k

Полагая, что T(1) =с, путем несложных преобразований, используя индукцию, можно получить окончательный результат:

T(2k) = с*k*2k = c*n*log(n)

Известно, что это — лучшее по порядку время решения задачи сортировки. Когда исходную задачу удается разделить на подзадачи одинаковой размерности, то, при условии существования линейного алгоритма слияния, рекурсивный алгоритм имеет аналогичный порядок сложности. К сожалению, не всегда удается исходную задачу разбить на к подзадач одинаковой размерности n/k. Часто такое разбиение не представляется возможным.

Рекурсивное решение задачи "Ханойские башни"

Поделиться:
Популярные книги

Безумный Макс. Поручик Империи

Ланцов Михаил Алексеевич
1. Безумный Макс
Фантастика:
героическая фантастика
альтернативная история
7.64
рейтинг книги
Безумный Макс. Поручик Империи

Надуй щеки! Том 5

Вишневский Сергей Викторович
5. Чеболь за партой
Фантастика:
попаданцы
дорама
7.50
рейтинг книги
Надуй щеки! Том 5

Обгоняя время

Иванов Дмитрий
13. Девяностые
Фантастика:
попаданцы
5.00
рейтинг книги
Обгоняя время

Истребители. Трилогия

Поселягин Владимир Геннадьевич
Фантастика:
альтернативная история
7.30
рейтинг книги
Истребители. Трилогия

Энциклопедия лекарственных растений. Том 1.

Лавренова Галина Владимировна
Научно-образовательная:
медицина
7.50
рейтинг книги
Энциклопедия лекарственных растений. Том 1.

Вечный. Книга V

Рокотов Алексей
5. Вечный
Фантастика:
боевая фантастика
попаданцы
рпг
5.00
рейтинг книги
Вечный. Книга V

Измена. (Не)любимая жена олигарха

Лаванда Марго
Любовные романы:
современные любовные романы
5.00
рейтинг книги
Измена. (Не)любимая жена олигарха

Город воров. Дороги Империи

Муравьёв Константин Николаевич
7. Пожиратель
Фантастика:
боевая фантастика
5.43
рейтинг книги
Город воров. Дороги Империи

Warhammer 40000: Ересь Хоруса. Омнибус. Том II

Хейли Гай
Фантастика:
эпическая фантастика
5.00
рейтинг книги
Warhammer 40000: Ересь Хоруса. Омнибус. Том II

Пекло. Дилогия

Ковальчук Олег Валентинович
Пекло
Фантастика:
боевая фантастика
6.17
рейтинг книги
Пекло. Дилогия

Хорошая девочка

Кистяева Марина
Любовные романы:
современные любовные романы
эро литература
5.00
рейтинг книги
Хорошая девочка

Тайны ордена

Каменистый Артем
6. Девятый
Фантастика:
боевая фантастика
попаданцы
7.48
рейтинг книги
Тайны ордена

Соблазны бытия

Винченци Пенни
3. Искушение временем
Проза:
историческая проза
5.00
рейтинг книги
Соблазны бытия

Кодекс Крови. Книга ХIII

Борзых М.
13. РОС: Кодекс Крови
Фантастика:
попаданцы
аниме
фэнтези
5.00
рейтинг книги
Кодекс Крови. Книга ХIII