js演算法初窺04(演算法模式01 遞迴)

2022-07-12 09:03:11 字數 2798 閱讀 2615

終於來到了有點意思的地方——遞迴,在我最開始學習js的時候,基礎課程的內容就包括遞迴,但是當時並不知道遞迴的真正意義和用處。我只是知道,哦...遞迴是自身呼叫自身,遞迴要記得有乙個停止呼叫的條件。那時,我還不了解遞迴的內在含義,好在現在知道了一點。

有些問題的本身就是遞迴的,我們想乙個程式問題,也是比較經典的面試問題——有乙個物件a,我們不知道它有多少層級,如何複製對這個物件?你可能會說,直接宣告乙個變數var b = a不就可以了嘛?但是,如果我改動了a中的乙個屬性,b中的屬性也跟著改變了。因為你只是將b得到指標指向了a,並沒有開闢一塊新的空間來儲存「儲存在a中的屬性」。也就是我們所謂的淺拷貝。那麼如何改變a中的屬性,b的屬性還是原來的樣子呢?我們可以利用遞迴來解決這樣的問題。

我記得前面的文章(用js來實現那些資料結構05(棧02-棧的應用))例舉了用棧解決問題的例項。其中最後乙個問題是漢諾塔問題,也需要用遞迴來解決。那麼就漢諾塔問題來說,如果不用遞迴,是否還有其它的可行的演算法得以解決這樣的問題呢?

很多人會覺得遞迴是低效率的,只不過是因為人腦的有限性不得不讓計算機去更忙碌一點,其實這種想法實在是片面的。因為有些問題本身就是遞迴的,比如我們上面所舉例子。再比如,有些問題或許可以遞迴,可以迴圈,還可以用其他方法來解決,但是遞迴更容易讓我們的**簡潔易懂,於是我們選擇了遞迴。

好了,說了很多,我們還是回到遞迴本身吧,遞迴說到底是一種解決問題的方法,它解決問題的各個小部分,直到解決最初的大問題。那麼,遞迴通常都會呼叫自身,就像下面這樣:

function

a()

當然,這樣寫也是一樣的:

function

a()

function

b()

當然,上面**只是舉個例子,沒有什麼實際意義。

在我們在最開始試著去實現乙個遞迴的時候,往往會出現stack overflow error等類似棧溢位的錯誤。因為我們的遞迴無限的執行下去以至於瀏覽器不得不強制停止遞迴,然後告訴你,出錯了。我們可以寫一點簡單的**來測試一下:

var i = 0;

function

recursivefn()

try

catch

(err)

//google

//15710 "error is:rangeerror: maximum call stack size exceeded"

//firefox

//65657 error is:internalerror: too much recursion

//qq

//41756 "error is:rangeerror: maximum call stack size exceeded"

//ie

//8225 error is:error: 堆疊溢位

//edge

//15466 error is:error: out of stack space

我們發現似乎每乙個瀏覽器,棧溢位的上限都是不一樣的。因為每一種瀏覽器廠商都為其自己的瀏覽器設定了不同的限度。甚至包括一些js原生api的內部實現方式,在不同的瀏覽器上都是不一樣的。

我們發現遞迴是如此的簡單,就是自身呼叫自身,再加乙個限制條件,就可以實現遞迴了。上面我們所寫的**在一定程度上只是為了解釋遞迴這個概念。沒有太多的實際意義。那麼,下面我們看看用遞迴來解決斐波那契數列問題。

那麼我們先來看這樣乙個問題,經典的兔子繁殖問題。一般而言,兔子在出生兩個月後,就有繁殖能力,一對兔子每個月能生出一對小兔子來。如果所有兔子都不死,那麼一年以後可以繁殖多少對兔子?

我們不妨拿新出生的一對小兔子分析一下:第乙個月小兔子沒有繁殖能力,所以還是一對,兩個月後,生下一對小兔,對數共有兩對,三個月以後,老兔子又生下一對,因為小兔子還沒有繁殖能力,所以一共是三對。依次類推:

這就是斐波那契數列了,在生活中,也有許多斐波那契數列存在的地方。

那麼我們可以提取一下:1和2的斐波那契數是1,3的斐波那契數是2,4的斐波那契數是3。換句話說,在n>2的情況下,f(n) = f(n-1) + f(n - 2)——這裡的n代表著在斐波那契數列中的第幾個斐波那契數。那麼,我們再用語言描述一下——除開最開始的兩項以外,以後的每一項都是前兩項的和,這就是我們的遞迴體和遞迴終止條件,我們來看下**:

function

fibonacci(num)

return fibonacci(num - 1) + fibonacci(num - 2);

}console.log(fibonacci(6))

要注意,不要試超過50的數噢,因為越往後相加的計算量就會越來越巨大。那麼我們畫個圖來看看,我們遞迴算出第6項的斐波那契數時,遞迴是如何進行的:

我們看上圖一步一步的解釋:

每乙個方塊中「/」後面的是當前呼叫的計算結果。我們從第一次fib(6)開始,由於6既不是1也不是2所以停止條件不符合,我們直接return了兩次呼叫但是這兩次呼叫又對num引數做了減一和減二的操作。所以就到了下一層。直到最後每一層的呼叫都執行到了num=1或者num=2的情況時。遞迴最終終止。那麼,在遞迴終止的時候,結果是由遞迴到最底層條件一點一點向上返回的。所以,遞迴的執行時由上至下但是遞迴結果的返回則是由下至上的。這樣我們就完成了一次整個遞迴的過程。

最後,由於本人水平有限,能力與大神仍相差甚遠,若有錯誤或不明之處,還望大家不吝賜教指正。非常感謝!

通過尋找醜數 初窺dp演算法

編寫乙個程式,找出第 n 個醜數。醜數就是質因數只包含 2,3,5 的正整數。示例 輸入 n 10 輸出 12 解釋 1,2,3,4,5,6,8,9,10,12 是前 10 個醜數。說明 1.1 是醜數。2.n 不超過1690。class solution i return i 1 public i...

初窺記憶體管理(三)夥伴演算法

假設系統的可利用記憶體空間容量為2m個字 位址從0到2m 1 則在開始執行時,整個記憶體區是乙個大小為2m的空閒塊,在執行了一段時間之後,被分隔成若干占用塊和空閒塊。為了在分配時查詢方便起見,我們將所有大小相同的空閒塊建於一張子表中。每個子表是乙個雙重鍊錶,這樣的鍊錶可能有m 1個,將這m 1個表頭...

BFPRT演算法(中位數之中位數)初窺 五

bfprt演算法的作者是5位真正的大牛 blum floyd pratt rivest tarjan 該演算法入選了在stackexchange上進行的當今世界十大經典演算法,而演算法的簡單和巧妙頗有我們需要借鑑學習之處。bfprt解決的問題十分經典,即從某n個元素的序列中選出第k大 第k小 的元素...