最后總結
其實,遞歸不一定總是從上往下,也是有很多是從下往上的,例如 n = 1 開始,一直遞歸到 n = 1000,例如一些排序組合。對於這種從下往上的,也是有對應的優化技巧,不過,我就先不寫了,后面再慢慢寫。這篇文章寫了很久了,脖子有點受不了了,,,,頸椎病?害怕。。。。
說實話,對於遞歸這種比較抽象的思想,要把他講明白,特別是講給初學者聽,還是挺難的,這也是我這篇文章用了很長時間的原因,不過,只要能讓你們看完,有所收獲,我覺得值得!
另外,我正在整理一份計算機類書單,只為讓大家更加方便找到自己想要的書籍,目前已經收集了幾百本了,貢獻給需要的人: 計算機的書籍很貴?史上最全計算機類電子書整理(持續更新),截圖了部分 數據結構與算法的書籍如下:
鏈表的重要性不言而喻,如果你把我分享的這10道題都搞懂了,那么你在鏈表方面算過關的了:
【鏈表問題】如何優雅着反轉單鏈表就不一道道列出來了,一共挑選了10還不錯的文章
十道鏈表打卡匯總我還講解了一些常用數據結構與算法思想,每篇都通俗易懂着講解了,被各種號所轉發
1、十大排序重要性不言而喻,文章還附帶了動畫、講解文章,代碼
必學十大經典排序算法,看這篇就夠了(附完整代碼/動圖/優質文章)(修訂版)2、總結了刷題過程中常用的技巧,推薦閱讀:
一些常用的算法技巧總結3、用漫畫的形式講解了AVL樹:
【漫畫】以后在有面試官問你AVL樹,你就把這篇文章扔給他。4、大量圖講解了堆的各種操作:
【算法與數據結構】堆排序是什么鬼?索性把寫的一些文章鏈接都分享一波,大家可以挑感興趣的看
算法與數據結構系列文章---------------------------------干貨整理-------------------------------------------
另外,我正在整理一份計算機類書單,只為讓大家更加方便找到自己想要的書籍,目前已經收集了幾百本了,貢獻給需要的人:
iamshuaidi/CS-Book部分書籍截圖
如果你覺得這篇內容對你挺有啟發,我想邀請你幫我三個忙,讓更多的人看到這篇文章:
遞歸要和迭代比較來看。
迭代是重復反饋過程的活動,其目的通常是為了逼近所需目標或結果。每一次對過程的重復稱為一次“迭代”,而每一次迭代得到的結果會作為下一次迭代的初始值,因此迭代是從前往后計算的。
遞歸則是一步一步往前遞推,直到遞歸基礎,尋找一條路徑, 然后再由前向后計算。
迭代是從前往后計算的,而遞歸則是先從后往前推,然后再由前往后計算,有“遞”又有“歸”。
通俗來講:引自@lishichengyan
一個小朋友坐在第10排,他的作業本被小組長扔到了第1排,小朋友要拿回他的作業本,可以怎么辦?
他可以拍拍第9排小朋友,說“幫我拿第1排的本子”,而第9排的小朋友可以拍拍第8排小朋友,說“幫我拿第1排的本子”...如此下去,消息終於傳到了第1排小朋友那里,於是他把本子遞給第2排,第2排又遞給第3排...終於,本子到手啦!
這就是遞歸,拍拍小朋友的背可以類比函數調用,而小朋友們都記得要傳消息、送本子,是因為他們有記憶力,這可以類比棧。
更嚴謹一些,遞歸蘊含的思想其實是數學歸納法:為了求解問題p(n),首先解決基礎情形p(1),然后假定p(n-1)已經解決,在此基礎上若p(n)得解,那所有問題均得解。
遞歸三要素
遞歸的定義:接受什么參數,返回什么值,代表什么意思 。當函數直接或者間接調⽤⾃⼰時,則發⽣了遞歸
遞歸的拆解:每次遞歸都是為了讓問題規模變⼩
遞歸的出⼝:必須有⼀個明確的結束條件。因為遞歸就是有“遞”有“歸”,所以必須又有一個明確的點,到了這個點,就不用“遞下去”,而是開始“歸來”。
遞歸的過程
下面這個求 n! 的例子中,遞歸出口(確定遞歸什么時候結束)是fun(1)=1,遞歸體(確定遞歸求解時的遞歸關系)是fun(n)=n*fun(n-1),n>1。
int fun(int n){ if(n==1) return 1; else return n*fun(n-1); }
遞歸一般用在可能無限循環下去的操作里,舉個例子,像電腦里的文件夾系統,打開一個文件夾,里面有很多的文件或文件夾,里面這些文件夾又可以打開,然后重復上面的過程,你是不知道你在什么位置結束這個循環的。
所以你不能按順序一句句代碼的寫下去,因為你根本不知道什么時候結束。這怎么辦?
用到遞歸的時候,基本上我們想要進行操作都是由一個簡單的小操作重復完成的(實際上是這樣的環境時才用遞歸),那這是就可以寫一個函數來完成這個小操作然后循環就可以了。
比如有個需求,你要計算一個文件夾里總共有多少個文件,這里的操作單元就是:一個文件夾只計算它下一級的文件數,不用去管它的子文件夾的子文件夾等等更下一層的情況。這樣每個文件夾把它下一級的文件數統計上來,它自身的文件數就是對的。
或者舉個更生活的例子,學校要統計去參加XX活動的人數,從上至下是學校->院系->專業->年級->班,雖然有很多層,但是對於系里面來說,它只要把專業統計的人員加起來就可以了,不用去管年級、班級的人數,其他層也是一樣,它只管它下一級的人數。
所以我覺得很重要的是怎么把一個任務合理的拆分成可以重復的單元,然后函數本身只要實現這個重復的這段操作就可以了。
這個和數學里面一個證明方法很像,方法思路大概是:
1.參數為0的時候是正確的
2.假如參數為n的時候是正確的,可以得出n+1的時候是正確的 ,
滿足這兩個條件,那么對於任何參數都是正確的。
這個過程是開始是固定的,但是沒有結尾,是從開始想遠處推倒。
遞歸呢,是整個過程有一個結尾,比如文件夾一直打開最后你肯定會遇到一個空文件夾或者全部是文件的文件夾,循環終止,是一個從任意處向終點推倒的過程。函數完成后的效果肯定是你選擇任意一個文件夾,它都可以計算出總共的文件數,所以看似是起點已知,但實際起點是任意的。
貼個示例代碼://文件 struct file { char* name; }; //文件夾 struct folder{ struct folder* subfolders; int folderNum; struct file* files; int fileNum; }; int countFiles(struct folder fol) { int num = fol.fileNum; //循環子文件夾,把文件數加起來 for (int i = 0; i< fol.folderNum; ++i) { struct folder subFolder = fol.subfolders[i]; num += countFiles(subFolder); //在這個位置形成遞歸;但是你要計算的是fol的數量,你只需要直接使用subFolder的數量 //就像是接力賽,你要相信對subFolder的計算式正確的,在這個基礎上計算當前這個層級數量 } return num; }











