數據結構——斐波那契數列的那點事

引言

0、1、1、2、3、5、8、13、21

說來慚愧,第一次認識斐波那契數列是在一次面試題中,當時稍微觀察了一下,就果斷地寫出了迭代版本的斐波那契數列…當時,還覺得這個算法題的數學模型也太簡單了吧,殊不知有時候問題並不是你解決了就可以,毫無疑問對於斐波那契數列有多種解法,對於迭代版本的斐波那契數列來說它可能是多個版本中效率最高,但是我們並不能被迭代版本所限制住我們用編程解決問題的思維。

迭代版

既然最先提及迭代版本的斐波那契數列,那麼我們就來看看它的實現:

function fibonacciInterative(n) {
		if (n < 1) return 0
		if (n <= 2) return 1

		let fibNMinus2 = 1
		let fibNMinus1 = 1
		let fibN = n
		for (let i = 3; i <= n; i++) {
			fibN = fibNMinus1 + fibNMinus2
			fibNMinus2 =fibNMinus1
			fibNMinus1 = fibN
		}
		return fibN
	}

其實,迭代版本的思路最白話地貼近我們的思路,首先對於索引爲 0 1 的數值並沒有規律,所以我們可以在方法中單獨判斷這種情況,返回對應的值(這裏把索引爲 2 的也加入,是因爲都是返回 1,所以也無傷大雅一起返回),然後,對於其他的數學模型就很簡單了,當前索引的值等於前兩個索引的值之和,所以 for 循環遍歷當前索引之前的數,一直求和以及賦值,最終求出當前索引下標對應的值。

遞歸版

對於遞歸,我想很多從事編程的同學都瞭解,但是目前我見到的很少場景會用到遞歸,除了早期在學數據結構的時候樹用的比較多。不過,對於前端開發的同學我想對遞歸應該很是熟悉,因爲 DOM 樹 和 CSSOM 的緣故,在一些框架中設計 VNode 以及一些對象的時候都是樹形結構,這個時候就避免不了遞歸遍歷的過程。那麼,我們來看看遞歸版的斐波那契數列:

function fibonacciRecursion(n) {
	if (n < 1) return 0
	if (n <= 2) return 1

	return fibonacciRecursion(n-2) + fibonacciRecursion(n - 1)
}

對比起來迭代版本,是不是感覺遞歸版本看起來簡潔了許多。但是,仔細思考,會發現遞歸版本,會發生一種情況,由於它並沒有存計算過的值,所以會出現重複計算相同索引地情況出現,這個過程無疑會浪費性能。

迭代+記憶版

瞭解了迭代,對於突然出現的名詞記憶,我想大家可能會覺得有點突兀,其實對於記憶可以理解爲緩存,通常是用一個數組來存一些值,然後避免重複計算或執行的過程(例如,在 Vue 中會緩存組件,避免組件的重複創建)。所以,迭代+記憶版的斐波那契數列會是這樣的:

function fabonacciMemoization(n) {
	const memo = [0, 1]
	const fibonacci = (n) => {
		if (memo(n)) return memo(n)
		return memo[n] = fibonacci(n-1, memo) + fibonacci(n-2, memo)
	}
	return fibonacci
}

對比起迭代版,記憶版避免了相同索引值的重複計算,並且和迭代版比起來,它也非常簡潔,指得推薦。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章