從彙編看尾遞迴
尾遞迴尾遞迴自然也是一種遞迴的形式,百度百科的定義是:如果一個函式中所有遞迴形式的呼叫都出現在函式的末尾,我們稱這個遞迴函式是尾遞迴的。當遞迴呼叫是整個函式體中最後執行的語句且它的返回值不屬於表示式的一部分時,這個遞迴呼叫就是尾遞迴。尾遞迴函式的特點是在迴歸過程中不用做任何操作,這個特性很重要,因為大多數現代的編譯器會利用這種特點自動生成優化的程式碼。(注意紅字)
還是那階乘來舉個例子,雖然階乘最好的解法不是遞迴。
下面的是普通的遞迴程式碼
int func(int n) { if(n<=1) return 1; return func(n-1)*n; } int main() { int a[3]; func(5); return 1; }
下面來看看它的彙編程式碼
下圖是呼叫的fun函式中的彙編程式碼,如圖中綠圈所示,每一次得到的值都會放到rbp中,再在再次呼叫時被壓入棧中,最後當n==1時在一次一次出棧,這也是為什麼說遞迴比較消耗空間的原因
接下來豬腳登場,尾遞迴,尾遞迴上面已經闡述了,就是讓我的表示式中不含返回值,這樣的話計算機就不需要把你的返回值放入棧中了。到底編譯器會不會又把你的這個不需要東西放入棧中呢?
我試了試(用的dev),它的優化方式是這樣的如果你棧的空間夠用的話,我就不管你用不用都給你存進去,如果空間不夠不夠用的話,我就不會存。在不停的呼叫函式時,後一個函式也會的直接把後一個函式的覆蓋掉,而不是又重新開闢空間儲存這個函式。
int tail_func(int n, int res)
{
if (n <= 1) return res;
return tail_func(n - 1, n * res);//將需要的東西傳入,而不是在表示式上加上一個函式的返回值
}
int main()
{
int a[3];
tail_func(5,1);
return 1;
}
下面是彙編程式碼:
並且遠方親戚也確實進行了不斷儲存的操作
現在我們把資料擴大,再不考慮溢位的情況下:
int main() { int a[1024*1024]; tail_func(1024*1024,1); return 1; }
正如下圖所示,這次程式沒有再去呼叫遠方親戚了,而是把遠方親戚請了過來,並且也沒有push,pop等壓棧入棧的操作達到對空間的重複利用
相關推薦
從彙編看尾遞迴
尾遞迴尾遞迴自然也是一種遞迴的形式,百度百科的定義是:如果一個函式中所有遞迴形式的呼叫都出現在函式的末尾,我們稱這個遞迴函式是尾遞迴的。當遞迴呼叫是整個函式體中最後執行的語句且它的返回值不屬於表示式的一
從斐波那契開始瞭解尾遞迴
從斐波那契開始瞭解尾遞迴 尾遞迴(tail recursive),看名字就知道是某種形式的遞迴。簡單的說遞迴就是函式自己呼叫自己。那尾遞迴和遞迴之間的差別就只能體現在引數上了。 尾遞迴wiki解釋如下: 尾部遞迴是一種程式設計技巧。遞迴函式是指一些會在函式內呼叫自己的函式,如果在
Javascript的尾遞迴及其優化
在平時的程式碼裡,遞迴是很常見的,然而它可能會帶來的呼叫棧溢位問題有時也令人頭疼:我們知道, js 引擎(包括大部分語言)對於函式呼叫棧的大小是有限制的,如下圖(雖然都是很老的瀏覽器,但還是有參考價值):為了解決遞迴時呼叫棧溢位的問題,除了把遞迴函式改為迭代的形式外,改為尾遞迴的形式也可以解決(雖然目前大部分
5.4.2 序列化解釋與尾遞迴
5.4.2 序列化解釋與尾遞迴 顯式控制的直譯器在ev-sequence的部分與元迴圈的直譯器的eval-sequence 程式是很相似的。它處理在程式體中的表示式的序列或者是顯式的begin表示式。 通過把要解釋的表示式的序列放入unev,在棧上儲存continue,跳轉到ev-sequenc
尾遞迴優化
尾遞迴優化是利用上面的第一個特點“呼叫同一個方法”來進行優化的 尾遞迴優化其實包括兩個東西:1)尾遞迴的形式;2)編譯器對尾遞迴的優化 尾遞迴的形式 尾遞迴其實只是一種對遞迴的特殊寫法,這種寫法原本並不會帶來跟遞迴不一樣的影響,它只是寫法不一樣而已,寫成這樣不會有任何優化效果,該
尾遞迴呼叫 高階函式 map filter reduce
#!/user/bin/env python# -*- coding:utf-8 -*-# 1.函式遞迴呼叫,函式返回值如果是另一個函式,而不是一個確切值,返回的則是這個函式的地址,需要我們加上()後才可以呼叫使用,# name="ceshi"# def hs1(x):# print(x)#
棧與尾遞迴優化
來自我的部落格 JavaScript的ES2015標準已經被普及了很久了,眾多的前後端應用也已經爭先恐後地支援了這一標準,其中有一條也是最後一條很有意思,叫做尾遞迴優化 棧 不得不先說一下棧 棧是一個比較基礎的資料結構,大家也廣為熟悉。不過可能使用起來
c++ 成員函式尾遞迴
#include <iostream> int factorial_tail(int n, int first, int second) { if (n == 1) { return second; } else {
python 學習彙總36:遞迴函式(尾遞迴)( tcy)
遞迴函式(尾遞迴) 2018/11/15 用途: 遞迴函式常用於檢索大量資料,替代for迴圈。 1.遞迴深度設定: sys.getrecursionlimit() #返回
js尾遞迴函式
普通遞迴: function fac(n) { if (n === 1) return 1; return n * fac(n - 1); } fac(5) // 120 這是個階乘。但是佔用記憶體,因為: fac(5) (5*fac(4)) (5*(4*fac(
高階函式與尾遞迴優化
高階函式特點 1.函式接收的引數是一個函式 2.函式的return值中包含函式 高階函式例子 某個函式的return值可以是任何函式,包括自己 ef test1(): print("from test1") def test2(): print("from test2
scala實戰學習-尾遞迴函式
求 $$ \Sigma\sideset{^b_a}f(x) $$ object sumfunc{ def sum(f: Int => Int)(a: Int)(b:Int): Int = { @annotation.tailrec def loop(n: Int
尾遞迴(簡要)
舉個栗子 --- 簡單粗暴 function fac(n) { if(n===1) return 1; return n * fac(n -1); } fac(5); n有幾次就會呼叫幾次,eg: fac(5
LeetCode刷題Easy篇斐波那契數列問題(遞迴,尾遞迴,非遞迴和動態規劃解法)
題目 斐波那契數列: f(n)=f(n-1)+f(n-2)(n>2) f(0)=1;f(1)=1; 即有名的兔子繁衍問題 1 1 2 3 5 8 13 21 .... 我的解法 遞迴 public static int Recursion
JavaScript 普通遞迴和尾遞迴函式
遞迴函式是自己呼叫自己的函式。 遞迴函式執行時會形成一個呼叫記錄,當子一層函式程式碼執行完成之後父一層函式才會銷燬呼叫記錄,這就形成了呼叫棧。棧的疊加可能會產生記憶體溢位。 n的階乘 //案例一 普通遞迴function factorial(n){ if( n === 1) return
Java遞迴發實現Fibonacci數列,尾遞迴實現Fibonacci數列,並獲取計算所需時間
遞迴法計算Fibonacci數列: 它可以遞迴地定義為: 第n個Fibonacci數列可遞迴地計算如下: int fibonacci(int n) { if (n <= 1) return 1; return fibon
尾呼叫與尾遞迴
1、什麼是尾呼叫? 尾呼叫用一句話說清楚就是,指某個函式的最後一步是呼叫另一個函式。 例1:function f(x) { return g(x); } 上面程式碼中,函式f最後一步是呼叫函式g,這就是尾呼叫了。
將遞迴函式改為尾遞迴,或者是遞推函式,求第45,46,47,48個Fibonacci數所花費的時間,觀察效率是否得到提高。
遞推: package 實驗二; public class Fi數列遞推 { public static void main(String args[]){ 遞推 f=new 遞推(); for(int i=45;i<=48;i++){ long st
js實現遞迴,尾遞迴(遞迴優化),防止棧溢位
一、一版的遞迴實現 n!,比如 5!= 5 * 4 * 3 * 2 *1 function fact(n) { if(n == 1) {
《笨辦法學Python》(13)---函式概念:迴圈VS遞迴,尾遞迴
參考文件 http://www.cnblogs.com/liunnis/articles/4604967.html https://blog.csdn.net/tianpingxian/article/details/80821504 https://blog.csdn.net/tc