В последовательности Фибоначчи я видел обычные реализации, которые рекурсивно называют один и тот же метод дважды:
public void fibonacci(int i)
{
fibonacci(1) + fibonacci(2);
}
Теперь этот метод не является точной копией того, что я видел, или правильным способом решения проблемы, но я видел два метода, добавленных вместе, как описано выше. Таким образом, метод не рекурсивно называется, а рекурсивно называется дважды. Что именно происходит при написании кода на С#? Оба метода работают на отдельных потоках? Что происходит под капотом?
Это один из тех случаев, когда полезно подумать о том, как компьютер это делает.
Начнем с функции. Я собираюсь записать его в псевдокоде с ароматизированным питоном, потому что я хочу, чтобы вы уклонились от размышления о LANGUAGE на секунду.
def fib(n):
if n == 0: return 0
if n == 1: return 1
if n > 1: return fib(n-2) + fib(n-1)
Теперь пройдите через это для fib (2)
fib(2)
имеет n > 1, поэтому берет третью ветвь,
return fib(2-2) + fib(2-1)
поэтому он вызывает fib() с 0, который равен 0
return 0 + fib(2-1)
вызывает fib() с 1
return 0 + 1
и мы видим результат 1. Теперь рассмотрим fib (3):
n > 1, поэтому
return fib(3-2)+fib(3-1)
а так как 3-2 равно 1, то для первого слагаемого получаем fib (1), который равен 1:
return 1 + fib(3-1)
и теперь, когда мы называем fib (3-1), а именно fib (2), у нас еще нет прямого ответа; п > 1. Таким образом, он становится
return 1 +
return fib(2-2) + fib(2-1)
который становится
return 0 + 1
ans мы возвращаемся к предыдущему вызову
return 1 + 1
и получить значение 2. Итак, вы видите, что нет отдельного потока, он просто работает через выражение. Я оставлю это как упражнение, чтобы выработать пример для fib (4); Бьюсь об заклад, если вы это сделаете, вы его пригвоздите.
Pop quiz: почему итеративная версия настолько эффективнее?
Нет, их называют один за другим. Никакие дополнительные потоки не создаются, если вы явно не просите об этом (с помощью материала System.Threading). Я не уверен в том порядке, в котором они вызваны, но я бы догадался, что слева направо. Спецификация С# определенно имеет его в нем.
В С# вы можете легко реализовать метод, как вы предлагаете, - он может выглядеть примерно так.
public static int Fib(int i) {
if (i == 0) return 0;
if (i == 1) return 1;
return Fib(i - 2) + Fib(i - 1);
}
Однако здесь нет никакой магии. Это только один раз рекурсивный вызов, за которым следует другой. То есть весь код запускается последовательно в одном потоке.
Производительность также очень плоха из-за двух рекурсивных вызовов для каждой итерации, поэтому даже при довольно малых значениях i
эта реализация может оказаться непригодной. Если вам нужна производительность, вам лучше избегать рекурсивных вызовов, просто управляя самим состоянием.
Они оцениваются последовательно. т.е. fibonacci(1)
оценивается до fibonacci(2)
. Если вы хотите визуализировать его, это случай спускания первого дерева вызовов (с его собственной "раздвоенной рекурсией" ), резервное копирование, прежде чем спуститься во второй три.
Как и в стороне, это часто используется в качестве примера того, как не использовать рекурсию, так как это очевидный, но неэффективный способ оценки последовательности фибоначчи. Предпочтительным вариантом является либо итеративный подход (разработка первого числа, затем следующего и т.д. До желаемого), или, что еще лучше, замкнутого уравнения формы.