В колледже каждый раз, когда я изучал новый язык программирования, меня просили заново реализовать алгоритм последовательности Фибоначчи. В то время обычно использовались методы рекурсии и рекурсии. В то время меня интересовали только результаты, пока появлялись результаты, остальное казалось неважным.
Теперь, став фронтенд-инженером, глядя на эту проблему, ситуация для рассмотрения стала шире, и есть больше методов, которые можно использовать. Итак, теперь я надеюсь применить то, что знаю, и снова вычислить последовательность Фибоначчи.
Во-первых, последовательность Фибоначчи начинается с 0, т.е.
0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233……
Итак, согласно этому правилу, верните n-е число Фибоначчи
Рекурсия
Прежде всего, перед первыми рекурсивными точками, а затем реализуйте их снова.
function fibonacci(n){
if(n === 1 || n === 0 ) return n;
return fibonacci(n-1) + fibonacci(n-2);
}
Идея рекурсии очень проста, то есть она продолжает вызывать собственный метод до тех пор, пока n не станет равным 1 или 0, а затем начинает возвращать данные слой за слоем.
При использовании рекурсии для вычисления больших чисел производительность будет особенно низкой по двум причинам:
① В рекурсивном процессе каждый раз, когда создается новая функция, интерпретатор создает новый фрейм стека функций и помещает его в фрейм стека текущей функции, который формирует стек вызовов. Поэтому, когда количество рекурсивных слоев слишком велико, это может привести к тому, что стек вызовов займет слишком много памяти или переполнится.
Кроме того, мы добавляем следующее утверждение перед возвратом для печати процесса рекурсивного расчета.
console.log(`fibonacci(${n-1}) + fibonacci(${n-2})`)
Когда n равно 6, полученный результат
fibonacci(5) + fibonacci(4)
fibonacci(4) + fibonacci(3)
fibonacci(3) + fibonacci(2)
fibonacci(2) + fibonacci(1)
fibonacci(1) + fibonacci(0)
fibonacci(1) + fibonacci(0)
fibonacci(2) + fibonacci(1)
fibonacci(1) + fibonacci(0)
fibonacci(3) + fibonacci(2)
fibonacci(2) + fibonacci(1)
fibonacci(1) + fibonacci(0)
fibonacci(1) + fibonacci(0)
② Анализ можно найти, рекурсивное вызвало большое количество двойного подсчета.
Эти два недостатка рекурсии мы можем использоватьОптимизация вызова хвостаа такжерешать.
Во-первых, что такое хвостовой вызов.
хвостовой вызовОтносится к ситуации, когда последнее действие в функции является функцией вызова: то есть возвращаемое значение вызова напрямую возвращается текущей функцией. Википад[1]
С кодом значения возвращаемого функции B - это функция возврата.
function B() {
return 1;
}
function A() {
return B(); // return 1
}
В ES6 в строгом режиме оптимизация хвостовых вызовов будет включена при соблюдении следующих условий: в это время движок не будет создавать новый фрейм стека, а будет очищать данные текущего фрейма стека и использовать его повторно:[2]
-
Функция хвостового вызова не требует доступа к переменным в текущем кадре стека.
-
После возврата хвостового вызова функция не имеет инструкции для продолжения выполнения.
-
Результатом хвостового вызова является возвращаемое значение функции
Например:
Следующая функция может включить оптимизацию хвостового вызова
"use strict";
function doA() {
return doB();
}
Следующие функции не могут включить оптимизацию хвостового вызова
"use strict";
function doC() {
doD(); // 尾调用的结果不是函数的返回值
}
"use strict";
function doE() {
return 1 + doF(); // 尾调用返回后,函数仍然有语句要运行
}
Мы используем оптимизацию вызова хвоста, переписывая функцию.
'use strict'
function fibonacci(n, current, next) {
if(n === 1) return next;
if(n === 0) return 0;
return fibonacci(n - 1, next, current + next);
}
Мы можем вызвать эту функцию следующим образом
fibonacci(6, 0, 1);
В это время, когда функция выполняется, потому что引擎不会创建一个新的栈帧,而是清除当前栈帧的数据并复用
, использование памяти не будет слишком большим.
Благодаря ES2015默认参数
'use strict'
function fibonacci(n, current = 0, next = 1) {
if(n === 1) return next;
if(n === 0) return 0;
return fibonacci(n - 1, next, current + next);
}
Таким образом, при вызове вам нужно передать только один параметр.
fibonacci(6);
В это время, прежде чем мы вернем оператор, который вызывает процесс печати
console.log(`fibonacci(${n}, ${next}, ${current + next})`);
Вы обнаружите, что процесс вызова значительно сократился.
fibonacci(6, 1, 1)
fibonacci(5, 1, 2)
fibonacci(4, 2, 3)
fibonacci(3, 3, 5)
fibonacci(2, 5, 8)
Уведомление:
Идея рекурсивного метода очень согласуется с идеей расчета, то есть f(0) запускается и вычисляется по одному, пока не будет добавлено нужное нам значение.
function fibonacci(n) {
const aFi = new Array(n+1);
aFi[0] = 0; aFi[1] = 1;
for(let i=2; i<= n; i++){
aFi[i] = aFi[i-1] + aFi[i-2];
}
return aFi[n];
}
Здесь мы определяем массив для хранениявсеРасчеты, но на самом деле нам нужно толькоf(n-1)
а такжеf(n-2)
два значения, поэтому мы можем хранить эти два значения в двух переменных, чтобы уменьшить накладные расходы на память.
function fibonacci(n) {
let current = 0;
let next = 1;
let temp;
for(let i = 0; i < n; i++){
temp = current;
current = next;
next += temp;
}
return current;
}
Основываясь на этой идее, мы перепишем это, используя другой подход.
Первый вариант метода присвоения структуры ES2015
Структурное задание[3]Позволяет нам извлекать значения непосредственно из массива в разные переменные. Таким образом, мы можем использовать структуру назначение, пропуская промежуточную перемему Temp.
function fibonacci(n) {
let current = 0;
let next = 1;
for(let i = 0; i < n; i++){
[current, next] = [next, current + next];
}
return current;
}
Вариант 2 --> форма while
function fibonacci(n) {
let current = 0;
let next = 1;
while(n --> 0){
[current, next] = [next, current + next];
}
return current;
}
здесь-->
Это не предельный оператор, это просто аббревиатура двух операторов. то есть - и >.
здесь
while(n --> 0){}
можно переписать как
while(n>0) {n--}
Вот это то, почему это так.
возвращаемое значениеВозвращаемое значение n – это n.
Итак, покаn>0
, то он будет выполнятьсяn--
Вариант три расширенных функции
function fibonacci(n){
let seed = 1;
return [...Array(n)].reduce(p => {
const temp = p + seed;
seed = p;
return temp;
},0)
}
Здесь мы используем расширенную функцию сокращения[5]Первый параметр — это значение последнего вычисления, поэтому здесь pp сохраняет значение F(n-1), а seed сохраняет значение F(n-2).
Вариант Четыре Генератора Генератор
Генератор — это новая функция ES 2015. Благодаря этой функции мы можем использовать метод генератора для создания генератора последовательности Фибоначчи.
function* fibonacci(){
let current = 0;
let next = 1;
yield current;
yield next;
while(true) {
[current, next] = [next, current + next];
yield next;
}
}
Способ использования
const fibo = fibonacci();
for(let i=0; i< 10;i ++){
console.log(fibo.next().value);
}
Но этот генератор не является функцией, которая может генерировать указанное n. Подробные методы реализации и возможные ямки можно найти в этой статье.7 удивительных вещей, которые я узнал, написав генераторы Фибоначчи на JavaScript.
формула общего члена
Доказательство формулы общего члена Фибоначчи см.Энциклопедия Байду. CF Формула, следующий код может быть достигнут[8]:
function fibonacci(n) {
const SQRT_FIVE = Math.sqrt(5);
return Math.round(1/SQRT_FIVE * (Math.pow(0.5 + SQRT_FIVE/2, n) - Math.pow(0.5 - SQRT_FIVE/2, n)));
}
Вышеупомянутое решение я сейчас изучаю. Если у вас есть лучшее решение или у вас есть какие-либо возражения против некоторых методов, я надеюсь, что смогу просветить меня в области комментариев.
использованная литература
[1] Крики хвоста — Википедия, свободная энциклопедия
[2] «Понимание ES6» Отделочное чтение: функция (функции) (восемь) Оптимизация вызова хвоста
[5] Array.prototype.reduce() - JavaScript | MDN
[6] Столбец Фибоначчи _ Baidu Encyclopedia
[7] 7 удивительных вещей, которые я узнал, написав генераторы Фибоначчи на JavaScript
[8] Схема JS и оптимизация суммирования рядов Фибоначчи