Каковы соображения, чтобы определить, можете ли вы использовать рекурсию для решения проблемы?


10

Иногда в интервью я могу использовать рекурсию для решения проблемы (например, добавление 1к целому числу с бесконечной точностью), или когда проблема представляется пригодной для использования рекурсии. Иногда это может быть связано с использованием рекурсии для решения проблем, поэтому, не задумываясь, рекурсия используется для решения проблемы.

Однако каковы соображения, прежде чем вы решите, что для решения проблемы целесообразно использовать рекурсию?


Некоторые мысли у меня были:

Если мы используем рекурсию для данных, которые каждый раз делятся пополам, кажется, что это не проблема при использовании рекурсии, поскольку все данные, которые могут поместиться в 16 ГБ ОЗУ или даже на жесткий диск 8 ТБ, могут обрабатываться рекурсией всего на 42 уровня. (поэтому переполнение стека не возникает (я думаю, что в некоторых средах глубина стека может составлять 4000, то есть больше 42, но в то же время это также зависит от того, сколько локальных переменных у вас, так как каждый стек вызовов занимает больше памяти) если существует много локальных переменных, и размер памяти, а не уровень, определяет переполнение стека)).

Если вы вычисляете числа Фибоначчи с использованием чистой рекурсии, вам действительно придется беспокоиться о сложности времени, если вы не кешируете промежуточные результаты.

А как насчет добавления 1к целому числу с бесконечной точностью? Может быть , это спорно, так как, вы работать с числами , которые имеют длину 3000 цифр или длиной 4000 цифр, настолько большой , что может вызвать переполнение стека? Я не думал об этом, но, возможно, ответ - нет, мы не должны использовать рекурсию, а просто использовать простой цикл, потому что, если в каком-то приложении число действительно должно быть длиной 4000 цифр, чтобы проверить некоторые свойства числа, такие как, является ли число простым или нет.

Главный вопрос: каковы соображения, прежде чем вы решите использовать рекурсию для решения проблемы?


7
На самом деле все довольно просто: «Является ли решение тривиальным, если я могу предположить, что решение немного меньшей проблемы известно?»
Килиан Фот

а как насчет числа Фибоначчи или добавления 1к бесконечной точности целого числа? Можно сказать, да, они сводятся к меньшим проблемам, но чистая рекурсия для этого не подходит
неполярность

Вы можете найти это полезным - stackoverflow.com/questions/3021/…
Кишор Кундан

Ответы:


15

Одно из соображений заключается в том, предназначен ли ваш алгоритм как абстрактное решение или как практическое исполняемое решение. В первом случае вам нужны следующие атрибуты: правильность и простота понимания для вашей целевой аудитории 1 . В последнем случае производительность также является проблемой. Эти соображения могут повлиять на ваш выбор.

Второе соображение (для практического решения) заключается в том, используется ли используемый вами язык программирования (или, если быть более точным, его реализация) устранением хвостовых вызовов? Без устранения хвостовых рекурсий рекурсия медленнее, чем итерация, и глубокая рекурсия может привести к проблемам переполнения стека.

Обратите внимание, что (правильное) рекурсивное решение может быть преобразовано в эквивалентное нерекурсивное решение, поэтому вам не обязательно делать трудный выбор между двумя подходами.

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


1 - Это включает в себя такие соображения, как, например, целевая аудитория ... и это могут включать программисты, читающие практический код ..., будет ли один стиль решения "более естественным", чем другой. Понятие «естественный» будет варьироваться от человека к человеку, в зависимости от того, как они изучили программирование или алгоритмику. (Я бросаю вызов любому, кто предлагает «естественность» в качестве основного критерия для решения использовать рекурсию (или нет) для определения «естественности» в объективных терминах; т.е. как бы вы ее измерили.)


2
Некоторые проблемы просто более естественно выражаются с помощью рекурсии. Например, обход дерева.
Фрэнк Хайлеман

Обновил мой ответ до этой точки,
Стивен С.

1
Что касается "естественности": например, обход дерева без рекурсии приводит к созданию более крупного и менее универсального кода. Рассмотрим, например, использование полиморфных вызовов для обхода дерева с различным поведением для конечных и составных узлов. Это невозможно без рекурсии.
Фрэнк Хайлеман

1) Ты уже принял мой вызов, чтобы определить «естественный»? 2) Поскольку возможно моделировать рекурсию с использованием стековой структуры данных, можно также реализовать обход дерева. Это может быть не самый эффективный способ ... и он не даст вам наиболее читаемый код ... но это определенно возможно и практично сделать.
Стивен С

Кстати, первый язык программирования, который я выучил (FORTRAN 4), вообще не поддерживал рекурсию.
Стивен С

1

Как программист C / C ++, мое главное внимание - производительность. Мой процесс принятия решения что-то вроде:

  1. Какова максимальная глубина стека вызовов? Если слишком глубоко, избавьтесь от рекурсии. Если мелко, переходите к 2.

  2. Может ли эта функция стать узким местом моей программы? Если да, перейдите к 3. Если нет, сохраните рекурсию. Если не уверены, запустите профилировщик.

  3. Какая доля процессорного времени затрачивается на рекурсивные вызовы функций? Если вызовы функции занимают значительно меньше времени, чем остальная часть тела функции, можно использовать рекурсию.


0

Однако каковы соображения, прежде чем вы решите, что для решения проблемы целесообразно использовать рекурсию?

Когда я пишу функции в Scheme, я считаю естественным писать хвостовые рекурсивные функции, не слишком задумываясь.

Когда я пишу функции на C ++, я начинаю спорить, прежде чем использовать рекурсивную функцию. Вопросы, которые я задаю себе:

  • Можно ли выполнить вычисление с использованием итерационного алгоритма? Если да, используйте итеративный подход.

  • Может ли глубина рекурсии увеличиваться в зависимости от размера модели? Недавно я столкнулся со случаем, когда глубина рекурсии выросла почти до 13000 из-за размера модели. Мне пришлось преобразовать функцию, чтобы использовать итеративный алгоритм после спешки.

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

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


0

Для чисел Фибоначчи наивная «рекурсия» просто глупа. Это потому, что это приводит к тому, что одна и та же подзадача решается снова и снова.

На самом деле существует тривиальная вариация чисел Фибоначчи, где рекурсия очень эффективна: для числа n ≥ 1 рассчитайте как fib (n), так и fib (n-1). Итак, вам нужна функция, которая возвращает два результата, давайте вызовем эту функцию fib2.

Реализация довольно проста:

function fib2 (n) -> (fibn, fibnm1) {
    if n ≤ 1 { return (1, 1) }
    let (fibn, fibnm1) = fib2 (n-1)
    return (fibn + fibnm1, fibn)
}

Как вы думаете, вы можете написать программу на общем языке? и ваш fib2возвращает пару чисел, и ваш fib2()не соответствует интерфейсу fib(), который, учитывая номер, возвращает число. Кажется, вы fib(n)должны вернуться, fib2(n)[0]но, пожалуйста, будьте конкретны
неполярность
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.