重新認識遞迴

2021-08-08 13:30:21 字數 2665 閱讀 8828

我記得以前在上生物時有學過一句話,「結構決定功能」,我覺得這裡用這句話,再適合不過了。遞迴的作用正是因為它在計算機的邏輯結構決定了它的功能的。

首先,我們用乙個簡單的遞迴過程來看一下遞迴是怎樣的

int digui(int a)
這是乙個簡單的遞迴過程,就是將1到10的遞迴相加。

我們從引數為10的函式開始,此時函式在記憶體中大致有下圖來表示(【】內的數字代表函式儲存的變數)

接下來呼叫引數為9的函式,此時在方法棧大概就是這樣的

下面依次執行後,方法棧中就變成了下圖所示

此時,不用再繼續執行函式,開始收縮,就是歸的過程

後續是這樣的

直到最後

最終返回

從這個過程中,可以發現,每次進行遞迴時,它都儲存了它當前的變數數值為後續的返回值做準備。這就是遞迴的最大的特點,儲存變數環境。

但是遞迴儲存的不僅僅是變數環境還有程式計數器,下面我們用乙個樹的深度遍歷再來看下遞迴。

static

void f(node root)

上面這個函式在進行時,就跟上述的不一樣了,單純的用->->->來模擬是有點問題,因為前兩個的執行跟後兩個的執行邏輯不一樣,這裡就有問題了?所以這裡要引入乙個程式計數器的概念,我們用【4,[0|1|2]】來表示乙個函式狀態,[0|1|2]就是函式要執行第幾行的標誌,那麼上面的情況就是

【4,1】【2,1】->

【4,1】【2,1】【1,0】->

【4,1】【2,1】【1,1】->

【4,1】【2,1】【1,2】->

【4,1】【2,1】->

【4,1】【2,2】->

【4,1】【2,2】【3,0】

所以這樣來表示,就明朗了。

現在明白了遞迴是什麼了,這裡再引入迴圈來模擬遞迴(畢竟都說迴圈可以代替遞迴,而且兩者也是有點關係的)

現在已經知道樹的深度遞迴是上面那樣,那麼就用迴圈的方式來模擬這個過程

這裡先引入乙個模擬函式狀態類

static class diguiinfo 

}

stackss = new stack<>();

diguiinfo cur = new diguiinfo(root);

do case 1:

cur.state++;

system.out

.println(cur.n

.value);

if (cur.n

.right != null)

case 2:

ss.pop();

if (!ss.empty())

}while(!ss.empty());

上述就是我對這個過程的模擬(雖然這段**寫得不是很好,請見諒)

所以,這時候我們來對比一下遞迴的寫法和迴圈的不同之處:

1、寫法上,遞迴函式的寫法無疑簡單了超多,因為迴圈中要注意的細節都在函式呼叫的特質中解決了。

2、空間方面,在前面對遞迴在記憶體中的模擬,對比上述迴圈的寫法中,可以發現兩者所佔的記憶體是差不多的。

3、時間方面,這個我不敢斷言,但以我目前對函式呼叫的理解,每次切換方法,其實也就是指標的改變,可能還有對應其他的操作吧,但是這個時間在一定範圍內是不會有多大影響的。

所以,從上面的比較,我對迴圈好過遞迴的說法不是很贊同的。但是也不是全部,在尾遞迴的模型中,迴圈確實比遞迴來得更好,最明顯的就是空間複雜度這一點。

在研究完遞迴這一過程之後,我想到了如果每次遞迴呼叫的不是自身函式,而是其他函式呢,我驚訝的發現我們寫的程式其實都是在遞迴這一狀態下執行的。這時候我對遞迴的理解就不再是呼叫自身函式這一特點,而是呼叫任意函式的函式過程本身就是遞迴)。(重新整理了我的程式設計觀)

同時,用遞迴的寫法,其實就是在使用棧模型,(而且還是乙個能記錄程式執行到哪的棧)那麼,有時在寫程式時就可以不用棧,而是用遞迴來寫了,這時的遞迴看起來更像是一種語法糖,還是乙個從函式誕生就出現的語法糖呢!

其實在之前我了解到有些語言會有尾遞迴的優化(這個確實是有必要的)。那時我就想那麼其他的遞迴就不能優化,在研究完上面的這些之後,我才明白一般遞迴並不是不能優化,而是優化的價值不大,尾遞迴之所以能優化,那是因為空間複雜度上可以優化,一般遞迴的空間複雜度是必要的,所以優化嘛,要優化**呢,也就沒有優化一般遞迴這一特點了。

最後,遞迴有什麼用呢,寫到這裡的時候,我覺得我們其實不用去知道遞迴是什麼,其實遞迴就在我們身邊,只是不知道那叫做遞迴而已。還有一點吧,應該就是簡化對棧的使用吧。(這個還沒實踐過)

(這裡對遞迴的定義是對任意函式的函式呼叫過程)

在函式式程式語言中,函式是沒有***的,所以也就沒有全域性變數這一概念了,但是,全域性變數在程式設計中,有時是必不可少的,那要怎麼解決這個問題呢?所以這裡就用遞迴來解決,因為遞迴的時候,會有儲存變數這一特質,所以這就可以實現全域性變數了,這些變數寫在最外層就可以了。

同時,在函式式程式語言中,還有乙個地方會用到遞迴,那就是迴圈的時候,這個原因也很好理解。因為尾遞迴可以用迴圈來優化,那麼迴圈就可以用尾遞迴來寫了,之後編譯成彙編的時候優化成迴圈就可以(這不是智障嗎)。至於為什麼要這樣呢?我個人覺得函式式程式語言,它自身在設計的時候,應該就是遵從大道至簡的道理吧,函式可以解決的問題,就沒必要引入其他的東西(這裡感受到函式式程式語言中函式的一種優越感。。。這種優越感還可以從「閉包模擬物件」看出來)。

重新認識container

我還清楚的記得,第一次從 那兒聽說container這個詞 結果他給我解釋了半天還是似懂非懂的。今天,偷閒翻了下posa4,發現裡面對container的解釋特別清楚。粗略的理解下來是,為了分離關注點,而實現的對系統資源的封裝。豁然開朗的發現,os就是應用程式的container。突發奇想的,開發乙...

重新認識測試

以前總覺得測試是軟體開發的邊緣職位,開發人員才是軟體生命週期的核心人員。隨著對網際網路公司的了解,逐步了解到測試的重要性。以bat為例,三家公司均設定了測試開發工程師崗位,該崗位的主要職責就是編寫自動化測試案例,通過對 的邏輯進行分析,設計出能夠覆蓋大部分 的測試用例。如阿里的測試開發工程師的崗位描...

重新認識ARC

雖然用了很久的arc,感受了 簡潔。但是對arc底層實現並不了解。今天抽空研究了下,做些簡單地總結。一 strong 1.區域性變數 對於區域性變數來說,在超出作用域的地方由編譯器自動插入release。大概轉化為 在非arc返回的autorelease型別的方法 在blog手碼大概 如有錯誤還望指...