2010-10-21 1 views
16

Во многих функциональных языках использование рекурсии считается хорошей практикой. Я думаю, что это хорошо из-за того, как компилятор оптимизирует код функционального языка.В C# хорошо ли использовать рекурсивные функции в алгоритмах?

Но хорошо ли использовать рекурсию в C# при создании алгоритма? Правильно ли говорить в отношении C#, что рекурсивные алгоритмы приведут к тому, что ваш стек будет расти довольно резко (если количество вызовов очень велико), и это не будет вообще быстрым и может привести к переполнению стека. Или есть какая-то оптимизация, чтобы сделать рекурсивные функции эффективными?

Я был бы признателен, если бы вы дали некоторое сравнение (скорость, память, читаемость) между алгоритмами, использующими рекурсию в функциональных языках и C#.

+5

По крайней мере, используйте рекурсию, когда это имеет смысл (и вы можете, то есть когда это не приведет к переполнению стека) - например. для обхода дерева и для алгоритмов, которые становятся очень уродливыми/сложными при преобразовании в итерацию. – delnan

+0

http://stackoverflow.com/questions/491376/why-doesnt-net-c-eliminate-tail-recursion –

ответ

16

Не использовать рекурсию в любом случае заставит вас переписать свой алгоритм, используя свой собственный «стек», который в конечном итоге будет подвергнут аналогичным условиям во время выполнения.

Вы можете настроить размер стека в зависимости от потребности в вашем алгоритме, но если вы посмотрите на алгоритмы WPF/Silverlight и обычного UI, все они рекурсивны по своей природе, и каждый клик, каждое нажатие клавиши и каждое уведомление проходят через множество рекурсивных методов.

Заканчивать Creating Thread with Custom Stack Size,

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

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

+3

-1 для полного отсутствия точки. Проблема с рекурсией в C# заключается в том, что в зависимости от того, какой размер стека вы решаете, вы все равно можете записаться за него. Рекурсия (которую мы понимаем как функцию, которая прямо или косвенно вызывает себя) в общем случае неограничена: вы не знаете заранее, насколько глубока рекурсия. – harms

+0

@harms, это очевидно, и все это знают, где еще, если происходит stackoverflow, это может даже произойти в ваших нерекурсивных алгоритмах, также учитывая, что каждому вызову потребуется какое-то толкающее состояние алгоритма на стеке, очереди или списке и которые могут перерасти слишком. –

+4

+1 для указания того, что состояние алгоритма должно идти * где-то *. – 2010-10-21 21:22:48

5

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

Тем не менее, читаемость является большим плюсом, поэтому я лично буду использовать рекурсию, если мне не нужна каждая капля производительности, например, при обработке изображений, или я ожидаю, что мои стеки будут расти очень большими - хотя переполнение стека почти исключительно из-за ошибок.

+0

Итак, если в ваших алгоритмах нет бесконечных циклов, у вас не будет нехватки из памяти стека, хотя это C# не повторно использует стек стека функции при создании рекурсивного вызова? – Vitalij

+1

Нет ... И да. Если язык поддерживает рекурсию хвостового вызова, то для первого вызова не существует стека (который существует и в итеративной версии). C# не является таким языком, поэтому итеративная версия будет больше памяти/скорости. –

+0

Фактически, в то время как C# явно не поддерживает хвостовую рекурсию, оптимизатор пытается использовать его вместо стандартной рекурсии, если после рекурсивного вызова дальнейших действий не будет. Я столкнулся с этим, пытаясь воспроизвести ошибку в этом вопросе: http://stackoverflow.com/questions/3915636/linqpad-just-crashed-on-me-is-my-code-anywhere-on-the-disk Опираясь на это поведение не рекомендуется. – spender

2

Этот пост Recursive iterator performance подробно объясняет разницу между рекурсивной версией и нерекурсивной операцией. Результаты интересны. Отъезд

4

В текущей реализации Microsoft компилятора C# оптимизация хвостовых вызовов не производится. Это делает функциональные алгоритмы, которые рекурсивно переполняют стек. Хотя я бы не рекомендовал использовать глубоко рекурсивные алгоритмы в C#, методы, которые не требуют глубокого анализа, не должны вызывать никаких проблем.

+1

почти, но не совсем корректно. См. Мой комментарий к ответу @ Aliostad. – spender

+0

Оптимизация звонков выполняется только тогда, когда вы не компилируете для 32-разрядных процессоров явно. http://goo.gl/AofK –

1

Когда вам нужна рекурсия, вам это нужно, например, для обработки дерева по глубине или рекурсивного спуска.

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

1

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

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

Смежные вопросы