遞推遞迴演算法
初見安~講深搜前我們先講講遞迴:)
二.遞推
遞推,顧名思義就是根據已有的推出未知的。很簡單,斐波那契數列就是一個很典型的例子:
那我們就以此作為例題看看吧:
要求輸入:一個整數n
輸出:斐波那契數列的第n個數。
斐波那契數列的規律也顯而易見:第n個數的值為第n-1個數和第n-2個數的和。
這道題的遞推操作的實現,我們可以用到for迴圈:
#include<iostream> using namespace std; int main() { int ans[100],n;//ans儲存數列 cin>>n;//讀入 ans[1]=1;//初始化定義,兩個即可 ans[2]=1; for(int i=3;i<=n;i++)//從3開始,避免非法操作。 { ans[i]=ans[i-1]+ans[i-2]; //遞推累加 } cout<<ans[n];//輸出 return 0; }
所以遞推是很好理解的:)
三.遞迴
遞迴其實是一個比較大的思想了。它和遞推的區別就在於:遞推是根據一個公式往前推過去,而遞迴則是層層深入,直到邊界後再返回。相當於多了一個回溯的過程。
實現遞迴,我們需要自己定義函式並自身呼叫,即程式巢狀。
這是什麼意思呢?可能有點難理解。簡單舉個例題吧:
求2的x次方。
要求輸入:一個整數x
要求輸出:2的x次方
很簡單的一道題——相信你第一反應是可以用for迴圈來做。為了理解,我們用遞迴實現試試。
先寫好框架吧:
#include<iostream> using namespace std; int main() { int x,ans=1; cin>>x; cout<<digui(x);//digui為自己定義的函式的函式名。 return 0p; }
接下來寫自己定義的函式:
void是指無返回值的函式。
void digui(int i)//前文主函式裡的x傳送到這裡,作為變數i——剩下的次方數。
{
if(i==0) return;//到達邊界,返回。
ans*=2;
digui(i-1);
}
如果就這樣了的話,編譯器會告訴你各種沒有定義的錯誤。
首先是void digui(int i)沒有定義。C++在編譯過程中,會有個從上到下的順序,但也會先訪問主函式int main();但在主函式中遇到了一個自定義的函式,就會去查詢——在主函式之前去找。所以我們要把digui定義在主函式前面。
其次就是ans未定義。這裡我們主函式裡定義了,為什麼在自定義函式裡又說沒有定義了?因為就如同上方自定義函式裡的int i一樣,在哪裡定義,就只能在哪裡用。所以int main裡的ans只能在主函式裡呼叫,在digui裡不曾定義。若為了方便,可以定義全域性變數——在主函式外定義。標城如下:
#include<iostream>
using namespace std;
int x,ans=1;//全域性變數
void digui(int i)
{
if(i==0) return;
ans*=2;
digui(i-1);
}
int main()
{
cin>>x;
digui(x);
cout<<ans;
return 0;
}
這樣我們得到的就是2的x次方了:)
如果有興趣的話,求x的y次方有一個專門的函式:pow(int x ,int y)。有一道題叫做快速冪,是這一方法的一個優化,也可以瞭解一下。畢竟這樣算資料量一旦大了就很容易超時(很久才出結果)。
這一應用上,我們可以發現:遞迴實現的自我呼叫其實可以用for迴圈實現。簡單的遞迴也都的確如此,類似於逆序輸出也可以用for迴圈逆序下標輸出。
我們再來看一個遞迴的應用:
已知:
計算x=4.2,n=10以及x=2.5,n=15時的f的值。
不要覺得你可以手算:)根號這種無理數只能用夾逼法算是很痛苦的。
這道題也很明顯的用遞迴,向自己定義的函式傳資料兩次就可以了。
由這一公式我們也可以化簡為我們的遞推式:
所以我們可以開始敲程式碼了:
P.S:sqrt(int x)為#include中的標準函式,求x的平方根
#include<iostream>
#include<cmath>
using namespace std;
double digui(int x,int n)
{
if(n==1) return sqrt(x+1);//到達邊界,開始回溯
return sqrt(digui(x,n-1));//沒有到達邊界,則繼續深入
}
int main()
{
cout<<digui(4.2,10)<<endl;
cout<<digui(2.5,15);
return 0;
}
有沒有突然覺得遞迴很方便呢~這個思想是很簡單的,而難在運用。比如我們下文將講述的——深度優先搜尋(DFS)。
迎評:)
——End——