Иногда в интервью я могу использовать рекурсию для решения проблемы (например, добавление 1
к целому числу с бесконечной точностью), или когда проблема представляется пригодной для использования рекурсии. Иногда это может быть связано с использованием рекурсии для решения проблем, поэтому, не задумываясь, рекурсия используется для решения проблемы.
Однако каковы соображения, прежде чем вы решите, что для решения проблемы целесообразно использовать рекурсию?
Некоторые мысли у меня были:
Если мы используем рекурсию для данных, которые каждый раз делятся пополам, кажется, что это не проблема при использовании рекурсии, поскольку все данные, которые могут поместиться в 16 ГБ ОЗУ или даже на жесткий диск 8 ТБ, могут обрабатываться рекурсией всего на 42 уровня. (поэтому переполнение стека не возникает (я думаю, что в некоторых средах глубина стека может составлять 4000, то есть больше 42, но в то же время это также зависит от того, сколько локальных переменных у вас, так как каждый стек вызовов занимает больше памяти) если существует много локальных переменных, и размер памяти, а не уровень, определяет переполнение стека)).
Если вы вычисляете числа Фибоначчи с использованием чистой рекурсии, вам действительно придется беспокоиться о сложности времени, если вы не кешируете промежуточные результаты.
А как насчет добавления 1
к целому числу с бесконечной точностью? Может быть , это спорно, так как, вы работать с числами , которые имеют длину 3000 цифр или длиной 4000 цифр, настолько большой , что может вызвать переполнение стека? Я не думал об этом, но, возможно, ответ - нет, мы не должны использовать рекурсию, а просто использовать простой цикл, потому что, если в каком-то приложении число действительно должно быть длиной 4000 цифр, чтобы проверить некоторые свойства числа, такие как, является ли число простым или нет.
Главный вопрос: каковы соображения, прежде чем вы решите использовать рекурсию для решения проблемы?
источник
1
к бесконечной точности целого числа? Можно сказать, да, они сводятся к меньшим проблемам, но чистая рекурсия для этого не подходитОтветы:
Одно из соображений заключается в том, предназначен ли ваш алгоритм как абстрактное решение или как практическое исполняемое решение. В первом случае вам нужны следующие атрибуты: правильность и простота понимания для вашей целевой аудитории 1 . В последнем случае производительность также является проблемой. Эти соображения могут повлиять на ваш выбор.
Второе соображение (для практического решения) заключается в том, используется ли используемый вами язык программирования (или, если быть более точным, его реализация) устранением хвостовых вызовов? Без устранения хвостовых рекурсий рекурсия медленнее, чем итерация, и глубокая рекурсия может привести к проблемам переполнения стека.
Обратите внимание, что (правильное) рекурсивное решение может быть преобразовано в эквивалентное нерекурсивное решение, поэтому вам не обязательно делать трудный выбор между двумя подходами.
Наконец, иногда выбор между рекурсивными и нерекурсивными формулировками мотивируется необходимостью доказать (в формальном смысле) свойства алгоритма. Рекурсивные формулировки более прямо позволяют доказательство по индукции.
1 - Это включает в себя такие соображения, как, например, целевая аудитория ... и это могут включать программисты, читающие практический код ..., будет ли один стиль решения "более естественным", чем другой. Понятие «естественный» будет варьироваться от человека к человеку, в зависимости от того, как они изучили программирование или алгоритмику. (Я бросаю вызов любому, кто предлагает «естественность» в качестве основного критерия для решения использовать рекурсию (или нет) для определения «естественности» в объективных терминах; т.е. как бы вы ее измерили.)
источник
Как программист C / C ++, мое главное внимание - производительность. Мой процесс принятия решения что-то вроде:
Какова максимальная глубина стека вызовов? Если слишком глубоко, избавьтесь от рекурсии. Если мелко, переходите к 2.
Может ли эта функция стать узким местом моей программы? Если да, перейдите к 3. Если нет, сохраните рекурсию. Если не уверены, запустите профилировщик.
Какая доля процессорного времени затрачивается на рекурсивные вызовы функций? Если вызовы функции занимают значительно меньше времени, чем остальная часть тела функции, можно использовать рекурсию.
источник
Когда я пишу функции в Scheme, я считаю естественным писать хвостовые рекурсивные функции, не слишком задумываясь.
Когда я пишу функции на C ++, я начинаю спорить, прежде чем использовать рекурсивную функцию. Вопросы, которые я задаю себе:
Можно ли выполнить вычисление с использованием итерационного алгоритма? Если да, используйте итеративный подход.
Может ли глубина рекурсии увеличиваться в зависимости от размера модели? Недавно я столкнулся со случаем, когда глубина рекурсии выросла почти до 13000 из-за размера модели. Мне пришлось преобразовать функцию, чтобы использовать итеративный алгоритм после спешки.
По этой причине я бы не рекомендовал писать алгоритм обхода дерева с использованием рекурсивных функций. Вы никогда не знаете, когда дерево становится слишком глубоким для вашей среды выполнения.
Может ли функция стать слишком запутанной при использовании итерационного алгоритма? Если да, используйте рекурсивную функцию. Я не пробовал писать
qsort
с использованием итеративного подхода, но у меня есть ощущение, что рекурсивная функция более естественна для этого.источник
Для чисел Фибоначчи наивная «рекурсия» просто глупа. Это потому, что это приводит к тому, что одна и та же подзадача решается снова и снова.
На самом деле существует тривиальная вариация чисел Фибоначчи, где рекурсия очень эффективна: для числа n ≥ 1 рассчитайте как fib (n), так и fib (n-1). Итак, вам нужна функция, которая возвращает два результата, давайте вызовем эту функцию fib2.
Реализация довольно проста:
источник
fib2
возвращает пару чисел, и вашfib2()
не соответствует интерфейсуfib()
, который, учитывая номер, возвращает число. Кажется, выfib(n)
должны вернуться,fib2(n)[0]
но, пожалуйста, будьте конкретны