摘要:大名鼎鼎的斐波那契數(shù)列,,,,,,,,使用數(shù)學(xué)歸納法可以看出其規(guī)律為。對(duì)于斐波那契數(shù)列的求解,有自頂向下的記憶化搜索遞歸和自下向上的迭代法,他們都使用了動(dòng)態(tài)規(guī)劃的思想。
大名鼎鼎的斐波那契數(shù)列:0,1,1,2,3,5,8,13,21...使用數(shù)學(xué)歸納法可以看出其規(guī)律為:f(n) = f(n-1) + f(n-2)。
遞歸下面首先直接使用遞歸(JavaScript實(shí)現(xiàn))來(lái)求解第 n 項(xiàng):f(n)
// 直接使用遞歸 let num = 0; // 用來(lái)記錄fib函數(shù)執(zhí)行次數(shù),執(zhí)行一次加一 function fib(n) { num ++; if(n === 0) { return 0; } if(n === 1) { return 1; } return fib(n-1) + fib(n-2); } console.time("time used"); console.log(`result is: ${fib(40)}`); console.log(`fib() runned ${num} times`); console.timeEnd("time used");
以 n = 40 為例,這里我們記錄了 fib 函數(shù)總共調(diào)用的次數(shù)以及運(yùn)算總共耗時(shí),結(jié)果如下:
可以看出,即便僅僅是計(jì)算第 40 項(xiàng),fib 函數(shù)調(diào)用的次數(shù)高達(dá)3億多次,時(shí)間是2477ms。因?yàn)槊恳淮?fib 函數(shù)的調(diào)用都會(huì)有兩個(gè)子 fib 函數(shù)調(diào)用,那么時(shí)間復(fù)雜度是 O(2^n) ,呈指數(shù)級(jí)增長(zhǎng),這顯然不是一個(gè)好算法。怎么優(yōu)化呢?以一個(gè)簡(jiǎn)單的例子畫圖分析一下:
上圖是 n = 5 時(shí)的遞歸樹,可以看出虛線框中 f(2) 的計(jì)算用到了三次,同樣的 f(3) 的計(jì)算用到了兩次,顯然我們執(zhí)行了非常多的重復(fù)運(yùn)算。那么很自然的想到,把每一個(gè)狀態(tài)的計(jì)算結(jié)果都存起來(lái),后面遇到相同的狀態(tài)就可以直接使用了。
記憶化搜索遞歸(自頂向下)代碼如下,這里第三行 new 了一個(gè)長(zhǎng)度為 n+1 的數(shù)組,用于存放 f(0) 到 f(n) 這 n+1 個(gè)狀態(tài)的計(jì)算結(jié)果:
// 記憶化搜索,記錄每次計(jì)算的結(jié)果 let num = 0; // 用來(lái)記錄fib函數(shù)執(zhí)行次數(shù),執(zhí)行一次加一 let totalnum = 40; let memory = new Array(totalnum).fill(-1); function fib(n) { num++; if(n === 0) { return 0; } if(n === 1) { return 1; } if(memory[n] === -1) { memory[n] = fib(n-1) + fib(n-2); // 如果前面已經(jīng)得到,直接使用 } return memory[n]; } console.time("timer"); console.log(`result is: ${fib(totalnum)}`); console.log(`fib() runned ${num} times`); console.timeEnd("timer");
同樣 n = 40,結(jié)果如下:
可以看處出優(yōu)化是十分可觀的,記錄下每一次子調(diào)用的結(jié)果,讓算法復(fù)雜度從 O(2^n) 變成了 O(n)。這其實(shí)就是動(dòng)態(tài)規(guī)劃的思想。什么是動(dòng)態(tài)規(guī)劃?
Dynamic programming is when you use past knowledge to make solving a future problem easier.(動(dòng)態(tài)規(guī)劃是用已知項(xiàng)去更好的求解未知項(xiàng))
Dynamic programming is a technique used to avoid computing multiple time the same subproblem in a recursive algorithm.
將原問(wèn)題拆解成若干子問(wèn)題,同時(shí)保存子問(wèn)題的答案,使得每個(gè)子問(wèn)題只求解一次,最終獲得原問(wèn)題的答案。
以上是我看到的兩個(gè)很好的定義。記憶化搜索遞歸求斐波那契數(shù)列顯然是使用了動(dòng)態(tài)規(guī)劃的思想,并且,這是一種自頂向下的求解方式(我們沒(méi)有從最基本的問(wèn)題開(kāi)始求解,對(duì)于 f(n) = f(n-1) + f(n-2) ,先假定 f(n-1) 和 f(n-2) 是已知的)。另外我們可以采用另一種自下向上的方式求解,即迭代,這也是一種動(dòng)態(tài)規(guī)劃的思想。
迭代法(自下向上)代碼如下,我們使用迭代,f(2) = f(1) + f(0),f(3) = f(2) + f(1),...,顯然這是一種從基礎(chǔ)問(wèn)題開(kāi)始的自下向上的解決方法:
let num = 0; function fib(n) { num++; let memory = new Array(n); memory[0] = 1; memory[1] = 1; for(let i = 2; i <= n; i++) { memory[i] = memory[i-1] + memory[i-2]; } return memory[n]; } console.time("timer"); console.log(`result is: ${fib(40)}`); console.log(`fib() runned ${num} times`); console.timeEnd("timer");
結(jié)果如下,顯然使用迭代的方法復(fù)雜度也為 O(n):
小結(jié)動(dòng)態(tài)規(guī)劃就是:將原問(wèn)題拆解成若干子問(wèn)題,同時(shí)保存子問(wèn)題的答案,使得每個(gè)子問(wèn)題只求解一次,最終獲得原問(wèn)題的答案。對(duì)于斐波那契數(shù)列的求解,有自頂向下的記憶化搜索遞歸和自下向上的迭代法,他們都使用了動(dòng)態(tài)規(guī)劃的思想。
參考鏈接:
https://stackoverflow.com/que...
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/95942.html
摘要:遞歸算法是一種直接或者間接調(diào)用自身函數(shù)或者方法的算法。因此,分治策略一般用來(lái)解決子問(wèn)題相互對(duì)立的問(wèn)題,稱為標(biāo)準(zhǔn)分治,而動(dòng)態(tài)規(guī)劃用來(lái)解決子問(wèn)題重疊的問(wèn)題。調(diào)用棧尾遞歸和手動(dòng)優(yōu)化尾調(diào)用就是一個(gè)函數(shù)執(zhí)行的最后一步是將另外一個(gè)函數(shù)調(diào)用并返回。 輸出 斐波那契數(shù)列的四種寫法 讀參考文章列表 算法復(fù)雜度中的O(logN)底數(shù)是多少 從斐波那契數(shù)列談?wù)劥a的性能優(yōu)化 冰與火之歌:時(shí)間與空間復(fù)雜度 ...
摘要:根據(jù)該規(guī)則,返回第個(gè)斐波那契數(shù)。尾遞歸函數(shù)調(diào)用自身,稱為遞歸。一個(gè)前端眼中的斐波那契數(shù)列解斐波那契數(shù)列的實(shí)用解法調(diào)用棧尾遞歸和手動(dòng)優(yōu)化尾調(diào)用優(yōu)化譯我從用寫斐波那契生成器中學(xué)到的令人驚訝的件事 斐波那契數(shù)列是以下一系列數(shù)字: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, ... 在種子數(shù)字 0 和 1 ...
摘要:動(dòng)態(tài)規(guī)劃有時(shí)被稱為遞歸的相反的技術(shù)。動(dòng)態(tài)規(guī)劃方案通常使用一個(gè)數(shù)組來(lái)建立一張表,用于存放被分解成眾多子問(wèn)題的解。今天我們先從我們最熟的斐波那契數(shù)列數(shù)列開(kāi)始。斐波那契數(shù)列在很多地方都會(huì)用上,我是從一個(gè)臺(tái)階問(wèn)題發(fā)現(xiàn)的,同時(shí)知道了動(dòng)態(tài)規(guī)劃的解法。 前段時(shí)間一直寫了幾個(gè)算法題目,發(fā)現(xiàn)有個(gè)很牛逼的算法,動(dòng)態(tài)規(guī)劃,雖然有的解題思路和動(dòng)態(tài)規(guī)劃很像,但是當(dāng)時(shí)不知道其中的原理和一些通用性,接下來(lái)的幾天,通...
摘要:在上做了一道斐波那契數(shù)列求和的題目,做完之后做了一些簡(jiǎn)單的優(yōu)化和用另一種方法實(shí)現(xiàn)。動(dòng)態(tài)規(guī)劃解決方案斐波那契數(shù)列求和除了可以用遞歸的方法解決,還可以用動(dòng)態(tài)規(guī)劃的方法解決。 在codewars上做了一道斐波那契數(shù)列求和的題目,做完之后做了一些簡(jiǎn)單的優(yōu)化和用另一種方法實(shí)現(xiàn)。 題目 function fibonacci(n) { if(n==0 || n == 1) r...
摘要:這是一個(gè)簡(jiǎn)單的遞歸函數(shù),你可以使用它來(lái)生成數(shù)列中指定序號(hào)的數(shù)值這個(gè)函數(shù)的問(wèn)題在于它的執(zhí)行效率非常低有太多值在遞歸調(diào)用中被重新計(jì)算。 本章內(nèi)容銜接上一章 數(shù)據(jù)結(jié)構(gòu)與算法:二分查找 內(nèi)容提要 兩種基本數(shù)據(jù)結(jié)構(gòu): 數(shù)組 常見(jiàn)操作: 數(shù)組降維、數(shù)組去重 鏈表 遞歸:遞歸是很多算法都使用的一種編程方法 - 如何將問(wèn)題分成基線條件和遞歸條件 - 分而治之策略解決棘手問(wèn)題 ...
閱讀 2696·2021-09-22 15:58
閱讀 2238·2019-08-29 16:06
閱讀 906·2019-08-29 14:14
閱讀 2815·2019-08-29 13:48
閱讀 2459·2019-08-28 18:01
閱讀 1504·2019-08-28 17:52
閱讀 3328·2019-08-26 14:05
閱讀 1622·2019-08-26 13:50