瞭解尾遞歸之前,先了解一下尾調用。
在計算機科學裏,尾調用是指一個函數裏的最後一個動作是一個函數調用的情形:即這個調用的返回值直接被當前函數返回的情形。這種情形下該調用位置爲尾位置。(摘自維基百科)
以上的解釋來自維基百科。介紹了什麼叫尾調用。例如:
1
2
3
4
|
function foo(data) {
a(data);
return b(data);
}
|
這裏的a(data)和b(data)都是函數調用,但是b(data)是函數返回前的最後運行的東西,所以也是所謂的尾位置。例如:
1
2
3
4
5
6
7
8
9
10
11
|
function foo1(data) {
return a(data) + 1;
}
function foo2(data) {
var ret = a(data);
return ret;
}
function foo3(data) {
var ret = a(data);
return (ret === 0) ? 1 : ret;
}
|
這種就不是尾調用,對於foo1,最後一個動作是+1操作,並非是直接函數調用;對於foo3,是經過計算返回的結果,也不是尾調用。,foo2也不是尾調用
尾調用很重要的特性就是它可以不在調用棧上面添加一個新的堆棧幀,而是更新它。
接下來說一下什麼是尾遞歸:
若一個函數在尾位置調用本身(或是一個尾調用本身的其他函數等),則稱這種情況爲尾遞歸,是遞歸的一種特殊情形。而形式上只要是最後一個return語句返回的是一個完整函數,它就是尾遞歸。這裏注意:尾調用不一定是遞歸調用,但是尾遞歸一定是尾調用。
接下來通過斐波那契數列和階乘來進一步理解尾遞歸的含義。
斐波那契數列
常規的斐波那契數列算法可能是這樣的:
1
2
3
4
5
6
7
|
int fib( int n) {
if (n <= 2) {
return 1;
}
return fib(n - 1) + fib(n - 2);
}
|
上面的這種遞歸計算最終的return操作是加法操作。所以不是尾遞歸。
如果用尾遞歸就是這樣的:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
/**
計算第n位斐波那契數列的值
@param n 第n個數
@param acc1 第n個數
@param acc2 第n與第n+1個數的和
@return 返回斐波那契數列值
*/
int tailfib( int n, int acc1, int acc2) {
if (n < 2) {
return acc1;
}
return tailfib(n-1,acc2,acc1 + acc2);
}
|
比如我們想計算第10位斐波那契數列的值,只需要fib(10,1,1)即可。
看一下測試效果,測試程序如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
int main( int argc, const char * argv[]) {
clock_t start,finish;
start = clock();
printf( "計算結果:%d\n" , fib(45));
finish = clock();
printf( "花費時間--------%lu\n" ,finish - start);
start = clock();
printf( "計算結果:%d\n" , tailfib(45,1,1));
finish = clock();
printf( "花費時間--------%lu\n" ,finish - start);
return 0;
}
|
計算結果如下:
1
2
3
4
5
|
計算結果:1134903170
花費時間--------5540692
計算結果:1134903170
花費時間--------4
Program ended with exit code: 0
|
效率可想而知。
階乘
常規的計算階乘的方法可能是這樣的:
1
2
3
4
5
6
|
int fac( int n) {
if (n == 1) {
return 1;
}
return fac(n-1) * n;
}
|
複雜度爲O(n)
尾遞歸的算法是這樣的:
1
2
3
4
5
6
|
int tailfac( int n, int sum) {
if (n == 1) {
return sum;
}
return fac(n-1, n * sum);
}
|
複雜度爲O(1)
測試一下效率,測試程序如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
int main( int argc, const char * argv[]) {
clock_t start,finish;
start = clock();
printf( "計算結果:%d\n" , fac(16));
finish = clock();
printf( "花費時間--------%lu\n" ,finish - start);
start = clock();
printf( "計算結果:%d\n" , tailfac(16,1));
finish = clock();
printf( "花費時間--------%lu\n" ,finish - start);
return 0;
}
|
測試結果:
1
2
3
4
|
計算結果:2004189184
花費時間--------31
計算結果:2004189184
花費時間--------2
|
尾遞歸效率比較高,但是個人覺得有尾遞歸算法理解起來會比較困難,你需要標註一下每個傳入參數的作用,否則剛接觸不一定會用這個算法。
參考資料:阮一峯的網絡日誌 維基百科
轉載請標註來源:http://www.cnblogs.com/zhanggui/p/7722541.html