遞歸,數學裏面叫recursion,其實就是遞推關係. 中學數學有一部分其實就是遞歸的很是典型的作法,不過老師們都沒怎麼擴展,新課標必修五第二章數列應該算是咱們第一次接觸遞推的概念了.
其實說到遞歸,大夥都知道就是本身調本身,這樣其實你們都明白,可是說來怎麼調?如何控制?又如何看獲得結果是想要的呢?相信仍是很暈,下面從中學數學裏面來看看吧.ios
第一部分、兩個典型的例子,等差數列與等比數列編程
其實這其實是一個例子,教書的時候我經常會問學生:「什麼是等差數列?」固然同窗們都會回答:「就是後一項總比前一項多一個數,這個數不變...」也有的說:「就是後一項減去前一項爲必定常數...」
因此咱們常常用表達式表示爲
a(n)=a(n-1)+d
TeX語法爲
\[a_n=a_{n-1}+d\]
那又有問題了,這裏肯定了一個數列沒有呢?固然沒有,我說後一項比前一項多2,這是什麼數列?
是1,3,5,7,9, ...仍是2,4,6,8,10, ...
固然弄不清楚,爲何呢?由於咱們談到的數列須要有一個首項,即第一項的值,因此一旦談到解遞推數列,就應該有兩個內容,一個是連續項間的關係,另外一個就是首項關係.函數
那麼就能夠利用疊加的方法來計算了:
假設這裏首項爲1,也就是a(1)=1,而這個常數就爲2
那麼
a(n)=a(n-1)+2
a(n-1)=a(n-2)+2
a(n-2)=a(n-3)+2
a(n-3)=a(n-4)+2
...
a(3)=a(2)+2
a(2)=a(1)+2
將等號左邊的依次相加,右邊的也依次相加
這樣很容易發現,左邊有一部分從a(2)一直加到a(n-1),右邊也有一部分從a(2)一直加到a(n-1),那麼消去,就左邊剩下a(n),右邊就剩下a(1)和n-1個2相加了,數學公式就成了
a(n)=a(1)+2(n-1)
即
a(n)=1+2(n-1)
LaTeX代碼爲
\[a_n=1+2(n-1)\]spa
就是等差數列的通向公式啦,那麼這個遞推關係中能夠看到a(n)=a(n-1)+x即爲連續項間的關係,而a(1)=1就是首項啦. 那這個和遞歸間的關係就很明顯啦
a(n)=a(n-1)+x表示,調用a(n)即執行a(n-1)+x,那麼同時調用a(n-1),...這樣一直下去,最後當a(1)時不在調用函數,直接返回1,再一路加回去,結束遞歸.blog
下面看看用代碼來實現,仍是假定首項爲1,公差爲2遞歸
#include <stdio.h>ci
int main()
{
int AddFun(int);
int num;
printf("Please input num:");
scanf("%d",&num);
printf("The result is %d",AddFun(num));
return 0;
}input
int AddFun(int n)
{
if(n == 1)
{
return 1;
}
return AddFun(n-1) + 2;
}
這裏呢AddFun(n)就是以前看到的a(n),在調用的過程當中先判斷n是否爲1,若是爲1固然就表示如今是首項了,不在繼續調用,直接返回結果1
這裏使用的不是數學中的公式計算,純粹的事利用遞推關係獲得的結果.數學
再看看等比數列. 相信已經很清楚了,等比數列就是後一項是前一項的定常數被,表示爲
a(n)=a(n-1)*q
LaTeX代碼爲
\[a_n=a_{n-1}\cdot q\]
將函數改一改就能使用了,假設首項爲1,公比爲2:
int ProductFun(int n)
{
if(n == 1)io
{
return 1;
}
return AddFun(n-1)*2;
}
那麼留個練習,這但是一道高中數學的典型例題(LaTeX代碼):
已知數列~$\{a_n\}$~知足~$a_n = 2a_{n-1}+1$,且首項~$a_1 = 1$,用遞歸調用寫出代碼過程.
(遞推關係爲a(n)=2a(n-1)+1,a(1)=1)
參考代碼:
#include <stdio.h>
#include <stdlib.h>
int main()
{
int SumCon(int n);
printf("%d",SumCon(10));
return 0;
}
int SumCon(int n)
{
if(n<=0)
{
return 1;
}
return 2 * SumCon( n - 1 ) + 1;
}
固然遞歸不必定要調用函數開完成,使用for循環同樣能夠作到
好比仍是公比爲2,首項爲1的等比數列求第10項的值
能夠寫成
#include <iostream>
using namespace std;
int main()
{
int res = 1;
for(int i=1; i <10; i++)
{
res = res * 2;
}
cout<<res<<endl;
return 0;
}
這個同樣能夠粗略的當作一種遞歸,res = res * 2表示將前一項的2倍賦值給後一項,也就是說後一項與前一項的2倍相等,不就是遞推關係了嗎...
第二部分、遞歸調用注意(直接遞歸)
遞歸調用在編程裏面就是對一個函數,或方法進行本身掉本身,那麼在編寫遞歸代碼時有三點要注意,首先要注意一個問題,就是遞推關係,這裏須要抽象出一個遞推的關係,不必定只有兩層,或許是三層甚至更多
例如:樓梯有n階臺階,上樓能夠一步上1階,也能夠一步上2階,編一程序計算共有多少種不一樣的走法.
先來分析一下,若是隻有一個臺階,那麼顯然只有1種,因此a(1)=1
若是有兩個臺階,呢麼就有每次上一層,上兩次,和一次上兩層,即1+1和2共2種,即a(2)=2
當n=3時,那麼就有1+1+一、1+二、2+1共3種了,彷佛還看不出規律,那麼分析一下遞推關係,即先後項間的關係
到達第三層有兩個方法,一是從第二層走一步到第三層,那麼有a(2)種方法,又能夠從第一層走兩步到第三層,即a(1)種方法,拍腦殼了,顯然到達第三層有a(1)+a(2)種方法,那麼是否是呢?驗證一下
a(1)+a(2)=1+2=3,咦,正好,看來有點像了,再來看看第四層是否是
當n=4時,按照上邊的分析確定爲a(2)+a(3)=2+3=5,下面咱們枚舉一下:1+1+1+一、1+1+二、1+2+一、2+1+一、2+2,剛恰好,看來就是她了!
那麼遞推關係就有了,即a(n)=a(n-1)+a(n-2),同時還有了首項的值:a(1)=1,a(2)=2,因爲遞推關係有三項,很顯然必須有兩個初始值
好啦,上面討論了遞歸的一個注意,下面是第二個注意,臨界條件
既然遞歸就是本身調本身,那麼怎麼中止呢,顯然不作邏輯判斷,計算機會一直運行下去,知道程序崩潰(棧溢出),因此就須要加上一個if判斷來跳出遞歸,其實就是前面提到的數列首項,就是這裏的a(1)和a(2)
那麼代碼就能夠這麼寫了:
int Count(int n)
{
if(2 == n)
{
return 2;
}
else if(1 == n)
{
return 1;
}
return Count( n - 1 ) + Count( n - 2 );
}
實際上這就是一個典型的Fibonacci數列,只是初始值不一樣罷了,下面留兩個練習(著名的數列):
一、反Fibonacci數列,知足的遞推關係爲:a(n+2)=a(n)-a(n+1)
二、巴都萬數列,知足遞推關係:a(n)=a(n-2)+a(n-3)
如今剩下第三個要注意的事項了,就是遞歸體.
先看兩個函數代碼(C++代碼):
一、
void Test_1(int n)
{
cout<<n<<endl;
if(n < 6)
{
Test_1( n + 1 );
}
}
二、
void Test_2(int n)
{
if(n < 6)
{
Test_2( n + 1 );
}
cout<<n<<endl;
}
若是在main()函數中分別調用這兩個函數,會有什麼執行結果呢?
如今咱們假定n爲0
第一個函數打印出
0
1
2
3
4
5
6
第二個函數打印出
6
5
4
3
2
1
0
這個就很奇怪了,這是爲何呢?
實際上前面咱們考慮的遞歸關係是單一的遞歸,可是這兩個遞歸體中,除了完成遞歸外,還有其餘執行代碼.
其實以前也有,只是沒有顯示出來,就沒有被察覺而已
那麼怎麼去分析呢?
其實很簡單,遞歸體自己可分爲三個部分,一個是頭代碼,一個是遞歸表達式,一個是尾代碼,區分頭尾的天然就是遞歸表達式了
看這段代碼:
#include <iostream>
using namespace std;
int main()
{
void Test(int);
Test(1);
return 0;
}
void Test(int n)
{
cout<<n<<endl;
if(n < 3)
{
Test( n + 1 );
cout<<n<<endl;
}
}
輸出結果爲
1
2
3
2
1
那麼在這段代碼中if()前面的輸出流爲頭代碼,中間的Test( n + 1 )爲遞歸表達式,這裏的尾代碼依然是一個輸出流
那麼分析此程序怎麼作呢?很簡單,八個字
「頭尾分開,等待歸來」
頭尾分開,就是將兩個cout<<n<<endl分開,第一個cout<<n<<endl執行,而後轉入第一次遞歸,等待遞歸結束後回到第二個cout<<n<<endl語句執行
這樣說很抽象,咱們一步步分析一下
第一次調用,n=1
執行頭cout<<n<<endl,輸出1,而且尾cout<<n<<endl等待,其值也是1,但未輸出;
進入第一次遞歸,n=2
執行頭cout<<n<<endl,輸出2,而且尾cout<<n<<endl等待,其值是2,但未輸出;
進入第二次遞歸,n=3
執行頭cout<<n<<endl,輸出3,但判斷n < 3 不成立,即再也不執行尾cout<<n<<endl語句,函數體結束
回到第一次遞歸體,執行等待的尾cout<<n<<endl,輸出2,第一次遞歸體結束,會帶最外層
執行等待的cout<<n<<endl,輸出1,整個函數調用結束,回到main()函數體繼續執行其餘命令.
形象的說就像是一架手風琴同樣,函數調用開始就拉開手風琴,而後進入遞歸,等待內層遞歸結束才執行後面的代碼
也就八個字「頭尾分開,等待歸來」
其實這就是實現的棧的結構
到這裏就基本結束了我所理解的遞歸含義,總結一下:
遞歸調用即遞推關係
遞歸三個注意:注意遞推表達、注意臨界判斷、注意遞歸體
摘自http://www.cnblogs.com/zzdxpq/