不知道你們發現沒有,執行遞歸算法,特別是遞歸執行層數多的時候,結果極其的慢,並且遞歸層數達到必定的值,還可能出現內存溢出的狀況。本文就要將爲你解釋緣由和對應的解決方案。算法
你們都知道遞歸的實現是經過調用函數自己,函數調用的時候,每次調用時要作地址保存,參數傳遞等,這是經過一個遞歸工做棧實現的。具體是每次調用函數自己要保存的內容包括:局部變量、形參、調用函數地址、返回值。那麼,若是遞歸調用N次,就要分配N局部變量、N形參、N調用函數地址、N返回值,這勢必是影響效率的,同時,這也是內存溢出的緣由,由於積累了大量的中間變量沒法釋放。函數
遞歸與循環是兩種不一樣的解決問題的典型思路。固然也並非說循環效率就必定比遞歸高,遞歸和循環是兩碼事,遞歸帶有棧操做,循環則不必定,兩個概念不是一個層次,不一樣場景作不一樣的嘗試。性能
首先,看一下系統棧和用戶棧的用途。優化
優勢:代碼簡潔、清晰,而且容易驗證正確性。(若是你真的理解了算法的話,不然你更暈)ui
缺點:它的運行須要較屢次數的函數調用,若是調用層數比較深,須要增長額外的堆棧處理(還有可能出現堆棧溢出的狀況),好比參數傳遞須要壓棧等操做,會對執行效率有必定影響。可是,對於某些問題,若是不使用遞歸,那將是極端難看的代碼。操作系統
優勢:速度快,結構簡單。code
缺點:並不能解決全部的問題。有的問題適合使用遞歸而不是循環。若是使用循環並不困難的話,最好使用循環。排序
1) 通常遞歸調用能夠處理的算法,也能夠經過循環去解決,常須要額外的低效處理。遞歸
2)如今的編譯器在優化後,對於屢次調用的函數處理會有很是好的效率優化,效率未必低於循環。進程
3) 遞歸和循環二者徹底能夠互換。若是用到遞歸的地方能夠很方便使用循環替換,而不影響程序的閱讀,那麼替換成遞歸每每是好的。(例如:求階乘的遞歸實現與循環實現。)
是內存中屬於操做系統空間的一塊區域,其主要用途爲:
1)保存中斷現場,對於嵌套中斷,被中斷程序的現場信息依次壓入系統棧,中斷返回時逆序彈出;
2)保存操做系統子程序間相互調用的參數、返回值、返回點以及子程序(函數)的局部變量。
是用戶進程空間中的一塊區域,用於保存用戶進程的子程序間相互調用的參數、返回值、返回點以及子程序(函數)的局部變量。
咱們編寫的遞歸程序屬於用戶程序,所以使用的是用戶棧。
以上初略介紹了遞歸與循環的實現機理,彷佛代碼簡潔和效率不能共存。那麼有沒有一種方法能擁有遞歸代碼簡潔的好處,同時給咱們帶來更快的速率麼?算法的世界會告訴你,一切皆有可能。它的名字叫作尾遞歸。
讓遞歸和尾遞歸來作一個對比吧。
用線性遞歸實現Fibonacci函數,程序以下所示:
int FibonacciRecursive(int n) { if( n < 2) return n; return (FibonacciRecursive(n-1)+FibonacciRecursive(n-2)); }
遞歸寫的代碼很是容易懂,徹底是根據函數的條件進行選擇計算機步驟。例如如今要計算n=5時的值,遞歸調用過程以下圖所示,能夠看出,程序向下遞歸,向上返回,因此每一步都須要存儲中間變量和過程。
顧名思義,尾遞歸就是從最後開始計算, 每遞歸一次就算出相應的結果, 也就是說, 函數調用出如今調用者函數的尾部, 由於是尾部, 因此根本沒有必要去保存任何局部變量。直接讓被調用的函數返回時越過調用者, 返回到調用者的調用者去。尾遞歸就是把當前的運算結果(或路徑)放在參數裏傳給下層函數,深層函數所面對的不是愈來愈簡單的問題,而是愈來愈複雜的問題,由於參數裏帶有前面若干步的運算路徑。
尾遞歸是極其重要的,不用尾遞歸,函數的堆棧耗用難以估量,須要保存不少中間函數的堆棧。好比f(n, sum) = f(n-1) + value(n) + sum,會保存n個函數調用堆棧,而使用尾遞歸f(n, sum) = f(n-1, sum+value(n)),這樣則只保留後一個函數堆棧便可。
採用尾遞歸實現Fibonacci函數,程序以下所示:
int FibonacciTailRecursive(int n,int ret1,int ret2) { if(n==0) return ret1; return FibonacciTailRecursive(n-1,ret2,ret1+ret2); }
例如如今要計算n=5時的值,尾遞歸調用過程以下圖所示:
從圖能夠看出,尾遞歸不須要向上返回了,可是須要引入額外的兩個空間來保持當前的結果,這樣減小了中間變量的存儲和返回,大大提高了效率,並且避免了內存溢出。
相信不少讀者對於快速排序都耳熟能詳,不知道各位還記得快速排序的實現就是基於遞歸實現的麼,因而這裏就提供了一種優化快速排序的方案,固然尾遞歸不能改變快速排序的時間複雜度,可是提高性能仍是沒問題的。筆者再也不作詳細介紹,只貼上實現代碼,留給各位獨立思考的空間。
int Partition(int *p,int len,int start,int last) { int flag=*(p+start); int i=start; int j=last; while(i<j) { while(i<j && *(p+j)>flag) --j; *(p+i)=*(p+j); while(i<j && *(p+i)<=flag) ++i; *(p+j)=*(p+i); } *(p+i)=flag; return i; } void QuickSort(int *p,int len,int start,int last) { if(NULL=p) return; int index; while(start<last) { index=Partition(p,len,start,last); QuickSort(p,len,start,index-1); //QuickSort(p,len,index+1,last); /**遞歸調用*/ start=index+1; /**尾遞歸調用*/ } }