數據結構與算法系列之遞歸(GO)

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"以下完整代碼均可從這裏獲取"}]},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"https://github.com/Rain-Life/data-struct-by-go/tree/master/recursion/step"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"理解遞歸"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"已經不知道是第幾次被遞歸阻斷我學習數據結構的道路了,每次學到遞歸,我都自我懷疑,是我腦子有問題嗎?我是真的學不明白它!"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"發現之前理解遞歸過於刻板和傳統,看遞歸的時候總是按照機器的執行順序一直的往每一層遞歸裏邊進,不斷的下一層、下一層、下一層,直到自己徹底崩潰,自己的CPU也沒把一個完整的遞歸給走完"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們的腦子總是想着跟着機器的執行,不停的往每一層裏邊進。其實完全可以不用關心每一層,只要假定下一層能夠正確的返回,然後該怎麼走就怎麼走,保證最深的一層遞歸邏輯正確就行,也就是遞歸終止的條件"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"因爲不管是遞歸的哪一層,他們執行的都是一樣的代碼,唯一不一樣的只是數據而已,保證了遞歸的邏輯是正確的,每一層的的結果就是正確的,直到遞歸的終止條件被滿足,然後每一層都會得到正確的結果"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"下邊進入主題"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"遞歸應用十分廣泛,可以說,如果沒法完全的理解遞歸的思想,後邊的一些進階的數據結構根本沒法學,或者非常喫力。比如深度優先搜索、二叉樹的遍歷等等"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"基本上大多數的關於數據結構的書,在介紹遞歸的時候都是拿斐波那契數列來舉例的,其實我個人覺得雖然題目經典,但對我瞭解遞歸幫助不是很大,下邊分享一個生活中的示例幫助理解遞歸"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"假設你現在在爬山,已經爬到了山腰上,假設你現在想知道自己在第多少級臺階上應該怎麼辦"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"此時遞歸就派上用場了,那如果你知道你前邊一級的臺階是第多少級就行了,知道了它是第多少級,在它的級數上加一就知道你所在的位置是第幾級臺階了。但是你前邊的這一級也不知道是第幾級,畢竟離山底有點遠,沒法知道。那就繼續的往前推,前一級的前一級臺階是第幾級臺階,直到第一級臺階,然後就可以一級一級的把數字傳回來,直到你的前一級臺階告訴你他在第幾級,你就知道了自己在第幾級了"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"整個過程就是一個遞歸的過程,往前推的過程是”遞“,回傳的時候就是”歸“。所有的遞歸問題都是可以用遞歸來表示的,對於上邊的例子,用公式來表示就是"}]},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"f(n) = f(n-1) + 1,其中f(1)=1"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"f(n)是你想自己自己在哪一級,f(n-1)就是你的前一級臺階是第幾級,f(1)表示第一級臺階我們知道它是第一級。有了公式寫遞歸代碼就很輕鬆了"}]},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"func Recursion(int n) int {\n if n==1 {\n return 1\n }\n \n return f(n-1) + 1\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"什麼情況下適合使用遞歸"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"只要一個問題可以滿足下邊三個條件,那就可以考慮使用遞歸"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"一個問題的解可以分解成幾個子問題的解"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"子問題就是規模更小的問題,比如前邊的求自己在哪一級臺階的問題,可以分解成”前邊一級是哪一級“這樣的子問題"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"子問題除了數據規模不一樣,求解思路必須完全一樣"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"還是以上邊的例子爲例,求自己在哪一級臺階,和求解前一級臺階在哪一級的思路是完全一樣的"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"存在遞歸終止條件"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"把問題分解爲子問題,把子問題再分解爲子子問題,一層一層分解下去,不能存在無限循環,這就需要有終止條件"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"還是上邊的例子,第一級臺階是明確知道是第一級的也就是 f(1)=1,這就是遞歸的終止條件"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"編寫遞歸代碼"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"寫遞歸的代碼,最關鍵的就是"},{"type":"text","marks":[{"type":"strong"}],"text":"寫出遞歸公式、找到遞歸終止條件"},{"type":"text","text":",剩下的將遞歸公式轉化成代碼就簡單了"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"示例"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"以Leetcode上邊的一道題爲例"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":">假如這裏有n個臺階,每次你可以跨1個臺階或者2個臺階,請問走這n個臺階有多少種走法?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果有7個臺階,你可以 2,2,2,1 這樣子上去,也可以 1,2,1,1,2 這樣子上去,總之走法有很多,下邊就是考慮如何通過代碼來實現"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"經過思考我們可以知道,實際上,可以根據第一步的走法,把所有的走法分爲兩類"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第一類是第一步走了1個臺階"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第二類是第一步走了2個臺階"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所以,"},{"type":"text","marks":[{"type":"strong"}],"text":"n個臺階的走法就等於先走1階後,n-1個臺階的走法加上先走2階後,n-2個臺階的走法"},{"type":"text","text":"。用公式表示就是:"}]},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"f(n) = f(n-1) + f(n-2)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"有了遞歸公式,現在就差終止條件。首先,當只有一個臺階的時候,那肯定就只有一種走法,所以f(1) = 1"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"但是,只有這一個遞歸終止條件足夠嗎?肯定是不夠的,比如現在考慮n=2和n=3的情況,靠這一個終止條件是否能夠求出來"}]},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"n=2\nf(2) = f(1) + f(0)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果遞歸終止條件只有一個f(1)=1,那 f(2)就無法求解了。所以除了f(1)=1 這一個遞歸終止條件外,還要有f(0)=1,表示走0個臺階有一種走法,不過這樣子看起來就不符合正常的邏輯思維了。所以,可以把f(2)=2作爲一種終止條件,表示走2個臺階,有兩種走法,一步走完或者分兩步來走"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所以,最終得到的遞歸終止條件就是(可以找幾個數字驗證一下)"}]},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"f(1)=1,f(2)=2"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"有了公式和遞歸終止條件,代碼就很容易了"}]},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"func StepEasy(n int) int {\n\tif n==1 {\n\t\treturn 1\n\t}\n\tif n==2 {\n\t\treturn 2\n\t}\n\n\treturn StepEasy(n-1) + StepEasy(n-2)\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"分析"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上邊的這個例子,人腦幾乎沒辦法把整個“遞”和“歸”的過程一步一步都想清楚"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":">計算機擅長做重複的事情,所以遞歸正和它的胃口。而我們人腦更喜歡平鋪直敘的思維方式。當我們看到遞歸時,我們總想把遞歸平鋪展開,腦子裏就會循環,一層一層往下調,然後再一層一層返回,試圖想搞清楚計算機每一步都是怎麼執行的,這樣就很容易被繞進去"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":">"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":">《數據結構與算法之美》"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果一個遞歸問題想試圖去通過大腦去走一遍遞歸過程,實際上是進入了一個思維誤區。很多時候,理解起來比較喫力,主要原因就是自己給自己製造了這種理解障礙"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果一個問題 A 可以分解爲若干子問題 B、C、D,你可以假設子問題 B、C、D 已經解決,在此基礎上思考如何解決問題 A。而且,你只需要思考問題 A 與子問題 B、C、D 兩層之間的關係即可,不需要一層一層往下思考子問題與子子問題,子子問題與子子子問題之間的關係。屏蔽掉遞歸細節,這樣子理解起來就簡單多了"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"因此,*"},{"type":"text","marks":[{"type":"strong"}],"text":"編寫遞歸代碼的關鍵是,只要遇到遞歸,我們就把它抽象成一個遞推公式,不用想一層層的調用關係,不要試圖用人腦去分解遞歸的每個步驟"},{"type":"text","text":"*"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"總結"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"*"},{"type":"text","marks":[{"type":"strong"}],"text":"寫遞歸代碼的關鍵就是找到如何將大問題分解爲小問題的規律,並且基於此寫出遞推公式,然後再推敲終止條件,最後將遞推公式和終止條件翻譯成代碼"},{"type":"text","text":"*"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"遞歸防坑指南"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"1、防止堆棧溢出"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"爲什麼遞歸代碼容易造成堆棧溢出?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"分享一個自己實際遇到的一個情況,之前公司舉辦過一次黑客馬拉松的程序比賽,是一道求解數獨的題,求解數獨的過程就用到了遞歸,給你一些已知數,求解數獨"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果給的已知數太少,就會導致你的解數獨過程的遞歸次數特別多"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在“"},{"type":"link","attrs":{"href":"https://mp.weixin.qq.com/s?_biz=MzU5MjA1MzcyMA==&mid=2247485074&idx=1&sn=83daca776e0efcb08a0c048016aa2ee0&chksm=fe24d225c9535b33f8215db78dfeb58fe7f1b075617139432551a68fb0394874b9cd55f4c48a&token=2027085948&lang=zhCN#rd","title":""},"content":[{"type":"text","text":"棧"}]},{"type":"text","text":"”那一篇文章中有分享到,函數調用會使用棧來保存臨時變量。每調用一個函數,都會將臨時變量封裝爲棧幀壓入內存棧,等函數執行完成返回時,纔出棧。"},{"type":"text","marks":[{"type":"strong"}],"text":"系統棧或者虛擬機棧空間一般都不大"},{"type":"text","text":"。如果遞歸求解的數據規模很大,調用層次很深,一直壓入棧,就會有堆棧溢出的風險"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"那麼如何防止呢?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"如何預防堆棧溢出?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"也很簡單,可以通過在代碼中限制遞歸調用的最大深度的方式來解決這個問題。遞歸調用超過一定深度(比如 1000)之後,我們就不繼續往下再遞歸了,直接返回報錯"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"以上邊的那個臺階爲例"}]},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"var depth = 0\nfunc StepEasy(n int) int {\n depth++\n\tif depth > 100 {\n\t\tpanic(\"遞歸次數太多\")\n\t}\n \n\tif n==1 {\n\t\treturn 1\n\t}\n\tif n==2 {\n\t\treturn 2\n\t}\n\n\treturn StepEasy(n-1) + StepEasy(n-2)\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"但這種做法並不能完全解決問題,因爲"},{"type":"text","marks":[{"type":"strong"}],"text":"最大允許的遞歸深度跟當前線程剩餘的棧空間大小有關,事先無法計算"},{"type":"text","text":"。如果實時計算,代碼過於複雜,就會影響代碼的可讀性。所以,如果最大深度比較小,比如 10、50,就可以用這種方法,否則這種方法並不是很實用"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"2、避免重複計算"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"使用遞歸時還會出現重複計算的問題,還是以走臺階的例子爲例,假設n=6,遞歸分解一下的話,大概如下:"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/3c/3c54b0bd589bc890b81bdc3670cb6568.png","alt":null,"title":"","style":[{"key":"width","value":"50%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"從圖中,可以直觀地看到,想要計算 f(5),需要先計算 f(4) 和 f(3),而計算 f(4) 還需要計算 f(3),因此,f(3) 就被計算了很多次,這就是重複計算問題"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"爲了避免重複計算,我們可以通過一個數據結構(比如散列表)來保存已經求解過的 f(k)。當遞歸調用到 f(k) 時,先看下是否已經求解過了。如果是,則直接從散列表中取值返回,不需要重複計算,這樣就能避免剛講的問題了"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上邊那個走臺階的問題,最終就可以優化成這樣"}]},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"package step\n\nimport \"fmt\"\n\n//假如這裏有 n 個臺階,每次你可以跨 1 個臺階或者 2 個臺階,請問走這 n 個臺階有多少種走法?如果有 7 個臺階,\n//你可以 2,2,2,1 這樣子上去,也可以 1,2,1,1,2 這樣子上去,總之走法有很多,那如何用編程求得總共有多少種走法呢?\n\nvar depth = 0\n\nvar mapList = map[int]int{}\n\nfunc Step(n int) int {\n\tdepth++\n\tif depth > 100 {\n\t\tpanic(\"遞歸次數太多\")\n\t}\n\tif n == 1 {\n\t\treturn 1\n\t} else if n==2 {\n\t\treturn 2\n\t}\n\n\tif mapList[n] != 0 {\n\t\treturn mapList[n]\n\t}\n\tres := Step(n-1)+ Step(n-2)\n\tmapList[n] = res\n\tfmt.Printf(\"step(%d) = %d\", n, mapList[n])\n\tfmt.Println()\n\treturn res\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"總結"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"除了堆棧溢出、重複計算這兩個常見的問題。遞歸代碼還有很多別的問題"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在時間效率上,遞歸代碼裏多了很多函數調用,當這些函數調用的數量較大時,就會積聚成一個可觀的時間成本。在空間複雜度上,因爲遞歸調用一次就會在內存棧中保存一次現場數據,所以在分析遞歸代碼空間複雜度時,需要額外考慮這部分的開銷,比如我們前面講到的電影院遞歸代碼,空間複雜度並不是 O(1),而是O(n)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/60/60253caecc31facc1adc2fff12bf5090.png","alt":null,"title":"","style":[{"key":"width","value":"50%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"*"},{"type":"text","marks":[{"type":"strong"}],"text":"以上內容參考《數據結構與算法之美》-遞歸篇,對裏邊的例子進行了簡單的改變,代碼通過go來實現,總結性文字皆來自原文"},{"type":"text","text":"*"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章