遞迴
遞迴(recursion)
在數學跟電腦科學上
函數定義中使用自身的方法
也就是一直呼叫自己
舉個例子
等差級數聽過吧
今天如果有個等差數列:
遞迴(recursion)
出來的結果應該長這樣
1,2,3,4,5.....
那如果用遞迴式表示呢?
我們可以發現第n項是他n-1項再+1
遞迴(recursion)
所以是不是可以表示成
這就是數學上遞迴式的樣子
那要怎麼寫成程式語言呢
它包含兩個組成要素
1.初始值(終止條件)
2.處理問題的方法
遞迴(recursion)
在程式語言中
遞迴的意思是函式內呼叫自己
int f(int x){
if(x==1)return 1;
return f(x-1)+1;
}
這個例子有點太過複雜化這個問題
其實可以直接cout<<x
不過畢竟是遞迴 先用簡單的例子慢慢理解
遞迴(recursion)
再用一個例子
int f(int x){
if(x==1)return 1;
return f(x-1)+x;
}
可以看出這個是用來解決什麼問題的嗎
當x=1回傳1
否則
遞迴(recursion)
int f(int x){
if(x==1)return 1;
return f(x-1)+x;
}
.
.
.
遞迴(recursion)
int f(int x){
if(x==1)return 1;
return f(x-1)+x;
}
會發現最後的結果是
1+2+......+(x-2)+(x-1)+x
也就是1加到x
也就是這個式子
遞迴(recursion)
接下來講遞迴的思考脈絡和組成
1.我們會發現大問題拆分成小問題
2.小問題可以拆分成更小的問題
3.每當小問題解決後 大問題也可以用小問題的結果解決
4.除了最小的問題以外 其餘都可以由相同方式解決
5.所以可以透過套用相同函式自己幫自己解決問題
以上就是遞迴的基本核心精神
遞迴(recursion)
會用在哪裡?
枚舉、分治、dfs、線段樹
以前的APCS第三題有蠻多的
之後也能為dp有些啟發性的想法
遞迴(recursion)
接著來講例題
- 費事數列
- 快速冪
- 河內塔
費事數列
費事數列
很經典的遞迴例子
費波那契數列:每一項是由前兩項相加而得
該如何用遞迴解決呢
首先我們會發現第n項的值
是由n-1和n-2項加起來
第1項的值是0
第2項的值是1
這是數列的前兩項
費事數列
遞迴式出來了
接著把它轉成程式語言
費事數列
int f(int n){
if(n==1)return 0;
if(n==2)return 1;
return f(n-1)+f(n-2);
}第1,2項是0,1
其他情況都是由前兩項加起來
遞迴下去會長怎樣呢
費事數列


費事數列
今天測資大一點 比如45
他跑的時間花太久了 會TLE
那要怎麼改善呢

費事數列

我們可以發現甚麼?
一直做下去的話只會有更多重複的
那如果今天已經知道f(n-2)的值還需要分割成小問題繼續遞迴嗎?
費事數列
今天如果我們把重複的東西用一個陣列記下來
每次要用到的時候就可以直接取值
int tmp[1000005];
int f(int n){
if(n==1)return 0;
if(n==2)return 1;
if(tmp[n]!=0)return tmp[n];
return tmp[n]=f(n-1)+f(n-2);
}如果tmp[n]不是0
代表已經有值了
則直接回傳 複雜度O(1)
費事數列
今天如果我們把重複的東西用一個陣列記下來
每次要用到的時候就可以直接取值
int tmp[1000005];
int f(int n){
if(n==1)return 0;
if(n==2)return 1;
if(tmp[n]!=0)return tmp[n];
return tmp[n]=f(n-1)+f(n-2);
}如果tmp[n]不是0
代表已經有值了
則直接回傳 複雜度O(1)
題目
k402
#include<bits/stdc++.h>
#define int long long
using namespace std;
int tmp[1000005];
int f(int n){
if(n==1)return 0;
if(n==2)return 1;
if(tmp[n]!=0)return tmp[n];
return tmp[n]=f(n-1)+f(n-2);
}
signed main(){
int a;
cin>>a;
cout<<f(a);
}要注意數字範圍(雖然他沒寫)
快速冪
快速冪
要怎麼計算出
int f(int a,int b){
int ans=1;
for(int i=1;i<=b;i++){
ans*=a;
}
return ans;
}簡單啊
a*a*a*a....總共*b次
時間複雜度:
快速冪
太慢了
用快速冪
用一點數學理解
如果今天b是偶數
如果今天b是奇數
快速冪
用程式碼來寫的話
int f(int a,int b){
if(b==0)return 1;
int ans=0;
if(b%2==1)ans=a*f(a,b/2)*f(a,b/2);
else ans=f(a,b/2)*f(a,b/2);
return ans;
}b=0
b為奇數
b為偶數
快速冪
f(a,b)
f(a,b/2) f(a,b/2)
f(a,(b/2)/2) f(a,(b/2)/2)
f(a,(b/2)/2) f(a,(b/2)/2)
.
.
.
.
這樣似乎沒有簡化和加速
反而還讓他更慢
我們可以怎麼優化呢?
快速冪
f(a,b)
f(a,b/2) f(a,b/2)
f(a,(b/2)/2) f(a,(b/2)/2)
f(a,(b/2)/2) f(a,(b/2)/2)
.
.
.
.
左邊和右邊好像長一模一樣
那我們就做一次遞迴然後把答案記下來就行
快速冪
int f(int a,int b){
if(b==0)return 1;
int ans=0;
int half=f(a,b/2);
if(b%2==1)ans=a*half*half;
else ans=half*half;
return ans;
}每次做一次f(a,b/2)
這樣做出來會長怎樣
快速冪
f(a,b)
f(a,b/2) f(a,b/2)
f(a,(b/2)/2) f(a,(b/2)/2)
f(a,(b/2)/2) f(a,(b/2)/2)
.
.
.
.
最後只剩下最左邊這條分支
複雜度分析
有點像二分搜
只有一條分支
所以看他做幾層 就會有幾個操作
每次二分 所以層數=可以二分幾次
複雜度:
題目
#include<bits/stdc++.h>
#define int unsigned long long
using namespace std;
const int mod=1e9+7;
int f(int a,int b){
if(b==0)return 1;
int ans=0;
int half=f(a,b/2)%mod;
if(b%2==1)ans=(a*half)%mod*half;
else ans=half*half;
return ans%mod;
}
signed main(){
int n;cin>>n;
for(int i=0;i<n;i++){
int a,b;cin>>a>>b;
cout<<f(a,b)<<'\n';
}
}
河內塔
河內塔
資工人一生必定會搬的一座塔

小時候玩具常有
以前APCS觀念題會考
河內塔(Hanoi Tower)
今天有ABC三根柱子
一開始A柱有n個盤子
下到上 由 大排到小
求最小步驟數
一次移動一個盤子
且小的不能在大的下面
河內塔(Hanoi Tower)
要怎麼用程式語言計算呢?
想法:
如果今天要從A移動第n個盤子到C
盤子由上到下 編號1~n
是不是要把1~n-1先移到B
n
1~n-1
1~n-1
n
1~n-1
A B C
河內塔(Hanoi Tower)
遞迴式出來了
int f(int x){
if(x==1)return 1;
return f(x-1)*2+1;
}步驟數出來了
但題目通常都會問你怎麼搬
河內塔(Hanoi Tower)
我們要相信第回下去後做的是對的
將n號盤移到C之後就變成n-1個盤子的問題了
那我們假設把x盤子從A移到C 藉由B
f(x,A,C,B)
河內塔(Hanoi Tower)
void f(int x,char from,char to,char mid){
if(x==1){
cout<<"Move disc "<<x<<" from "<<from<<" to "<<to<<"\n";
return;
}
f(x-1,from,mid,to);
cout<<"Move disc "<<x<<" from "<<from<<" to "<<to<<"\n";
f(x-1,mid,to,from);
}只有一個盤子 直接移
先把x-1移到B
把x移到C
再把x-1移到C
我們要相信遞迴下去
程式會做好一切
題目
#include<bits/stdc++.h>
using namespace std;
int h(int x){
if(x==1)return 1;
return h(x-1)*2+1;
}
void f(int x,char from,char to,char mid){
if(x==1){
cout<<from<<' '<<to<<'\n';
return;
}
f(x-1,from,mid,to);
cout<<from<<' '<<to<<'\n';
f(x-1,mid,to,from);
}
int main(){
int n;cin>>n;
cout<<h(n)<<'\n';
f(n,'1','3','2');
}
題目
#include<bits/stdc++.h>
using namespace std;
int f(int x){
if(x>=101)return x-10;
//cout<<x<<' ';
return f(f(x+11));
}
int main(){
int n;
while(cin>>n && n!=0){
cout<<"f91("<<n<<") = "<<f(n)<<'\n';
}
}#include<bits/stdc++.h>
using namespace std;
#define int long long
int p[100000005];
int cut(int l,int r){
if(r==l+1)return 0;
int k=-1;
double mid=(p[l]+p[r])/2.0,mn=1e11;
for(int i=l+1;i<r;i++){
if(abs(p[i]-mid)<mn){
k=i;
mn=abs(p[i]-mid);
}
}
return cut(l,k)+cut(k,r)+p[r]-p[l];
}
signed main(){
int n,l;
cin>>n>>l;
for(int i=1;i<=n;i++){
cin>>p[i];
}
p[0]=0;
p[n+1]=l;
cout<<cut(0,n+1)<<'\n';
}d188
#include<bits/stdc++.h>
using namespace std;
int ans[3];
bool solve(int remain,int id){
int l=0,u=sqrt(remain);
if(id==2){
int t=sqrt(remain);
if(t-sqrt(remain)==0){
ans[id]=t;
return true;
}
return false;
}
else if(id==1)l=ans[id-1],u=sqrt(remain);
for(int n=l;n<=u;n++){
ans[id]=n;
if(solve(remain-n*n,id+1))return true;
}
return false;
}
int main(){
ios::sync_with_stdio(0),cin.tie(0);
int n;
cin>>n;
while(n--){
int a;
cin>>a;
if(solve(a,0))cout<<ans[0]<<' '<<ans[1]<<' '<<ans[2]<<'\n';
else cout<<-1<<'\n';
}
}其實這題比較枚舉
dfs的感覺

遞迴
By wuchanghualeo
遞迴
- 100