資料結構與演算法-4-Recursion
前言
pic from AlogDaily
遞迴(Recursion)是一個說起來可以很簡單但實際用起來很難的東西。
有多少的機會我們會用到它呢?XD
限制與運用
非常簡略說起來,我們可以說地回的定義就是: 某個函數呼叫他自己(Something calling itself)。但如果單純如此,那就會變成無限迴圈了是嗎?所以通常Recursion會包括一個目標條件,英文直翻是基底條件(Base case),當遞迴完成目標條件後即會返回一個結果。
如果從JS的call stack 來看,當我們執行遞迴的時候,若沒有到達目標條件時會不斷產生新的函式,直到最後一個函式抵達目標條件時return,接著回到倒數第二個,持續執行直到抵達目標條件時return回到倒數第三個,以此類推。
我個人覺得遞迴最好的應用思路是樹狀圖的流程,像是檔案系統,一個資料夾裡會有幾個次要資料夾,每個次要資料夾又有多少的資料夾呢?當然或許可以用別的方式處理這個流程,比如每進入一層資料夾就先計算數量,並紀錄,直到抵達最底,但這樣會讓程式變得複雜,使用遞迴會讓這樣的過程大大簡化。
很多的指令都會有–recursive的參數,像是前陣子剛好用到aws s3 cp的指令,讓你可以透過指令下載資料夾下所有的檔案。
另外遞迴最大的缺點就在於對於記憶體的使用,這也是剛剛前面提到JS call stack的原因,如果你的base case太難達到,最後可能就會造成memory超過最大限制而導致錯誤。
實作
課程中提到兩個案例:
- base: foo case
- advance: path resolver
Part I - foo case
1 | function foo(n) { |
可以理解他如何使用遞迴,但通常都會有一個困惑,有必要嗎?
如果我們想計算n + n-1 + n-2 + … + 1會怎麼做?
就算我們不知道公式好了,用for迴圈都比用遞迴來的直觀多了?
1 | function bar(n) { |
事實上這個例子太簡單理解到實在不需要使用遞迴來處理。
所以講師提到了,可以用for、while迴圈思考的東西基本上都是不需要遞迴的。
Part II - path solver
這邊要先描述問題,這邊有個以String[]畫成的迷宮,我們需要寫出一個可以告訴我們如何從Start通過迷宮,順利從End離開的「路徑」。
迷宮範例:
1 | const maze = |
因為我們的目標是「走」,於是我們首先來分析「不再走」(base case)的條件:
- 離開地圖
- 撞牆(#)
- 去過重複的地方
- 抵達終點(E)
接著我們知道在一個點只有4個方向可以移動: 上下左右,這就是遞迴條件(recursive case)。
來寫看看~~~
1 | const directs = [ |
內容大致如此了~
相關的知識點備註、補充也都寫在comment上了。
小結
「Always think about base case」 - ThePrimeagen
大概是這堂課中最重要的部分,當我們在思考如何使用或是分析一個遞迴的時候,先找base case會是理解他的有效途徑。
此文章同步發表於部落格,歡迎來逛逛~