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