尾遞歸優化

關於循環優化的理論比較複雜,咱們如今暫時切換到一些簡單些的優化吧
咱們常常在寫代碼時會使用遞歸調用,咱們知道遞歸調用一般代價比較大,
可是遞歸調用比較符合咱們的思考方式,寫代碼比較容易。
其實實際上對於一種比較特殊的遞歸調用,編譯器能夠直接幫助咱們作優化,
將遞歸調用直接優化爲非遞歸調用,這一類遞歸調用在編譯器裏面稱爲尾遞歸調用
(Tail Recursive Call).
好比函數
  1. int primes(int a, int b){
  2.     if(a==0)return b==1;
  3.     if(b==0)return a==1;
  4.     return primes(b,a%b);
  5. }
複製代碼
像上面的一個代碼,只有在代碼的最後一行調用函數自身的代碼稱爲尾遞歸,大部分現代
的編譯器都可以對上面的代碼進行優化,將遞歸優化成一個循環,由於其作法只須要修改全部
函數輸入值,而後跳到函數入口就能夠了。
有些尾遞歸調用可能不是很容易看出來,好比
  1. int primes(int a, int b){
  2.     if(a==0)return b==1;
  3.     if(b==0)return a==1;
  4.     if(a<b){
  5.         return primes(a,b%a);
  6.     }else{
  7.         return primes(a%b,b);
  8.     }
  9. }
複製代碼
雖然上面代碼有兩次遞歸調用,可是兩次都是尾遞歸,它們分別在兩個不一樣分支的最後面。
此外另一種比較常見的尾遞歸調用形式是:
  1.    double honic(int n){
  2.         if(n==1)return 1.0;
  3.         else return 1.0/n+honic(n-1);
  4.    }
複製代碼
這裏,遞歸調用在一個表達式中間,雖然它是最後一次計算(+操做)的一個參數,可是因爲加法
知足結合率,咱們能夠改變全部累加的順序,因此上面的編譯器也能夠自動轉化爲循環。
一樣,計算
  1.    double honic(int n){
  2.         if(n==1)return 1.0;
  3.         else return honic(n-1)+1.0/(n*n);
  4.    }
複製代碼
編譯器也能夠自動優化,(雖而後面還有計算1.0/(n*n),可是編譯器能夠自動優化,加法知足交換率).
像下面代碼
  1.    double Fib(int n){
  2.         if(n<=1)return 1.0;
  3.         else return Fib(n-1)+Fib(n-2);
  4.    }
複製代碼
這個代碼中,兩次遞歸調用中,只有後面那次調用(Fib(n-2))能夠轉化爲循環的一部分,可是前面那次遞歸調用沒法轉化。
因此對這樣的代碼,最好仍是經過手工優化。(其實,這裏的函數Fib不含有任何指針之類會引發歧異的代碼,從理論上,
編譯也應該可以優化掉,可是實際上如今一般的編譯器不會作這樣的優化)
相關文章
相關標籤/搜索