遞迴(上)
Arvin Liu @ Sprout
Question Box
什麼是遞迴?
就是一個函數呼叫自己!
直接看看例子吧!
簡單的遞迴code
the sum of 1 -> n
int f(int x){
if (x == 1) return 1;
return x + f(x-1);
}
f(5)= 5 + f(4)
= 5 + [4 + f(3)]
= 5 + [4 + [3 + f(2)]]
= 5 + [4 + [3 + [2 + f(1)]]]
= 5 + [4 + [3 + [2 + [1]]]]
the sum of 1 -> n
int f(int x){
if (x == 1) return 1;
return x + f(x-1);
}
f(5) = 5 + 4 + 3 + 3 \text{ (return 1)}
= 5 + 4 + 6 \text{ (return 3)}
= 5 + 10 \text{ (return 6)}
= 15 \text{ (return 10)}
\text{ (return 15)}
斐波那契數列
(Fibonacci number)
Fibonacci - Definition
如果 n = 1 or 2,
f(n) = 1
如果不是,
f(n) = f(n-1)+f(n-2)
Fibonacci - Definition
f(n)=
\begin{cases}
1, \text{if \,n=1\,or \,2}\\
f(n-1)+f(n-2), \text{otherwise}
\end{cases}
f(5)= f(4) + f(3)
= f(3) + f(2) + f(2) + f(1)
= f(2) + f(1) + 1 + 1 + 1
= 5
Fibonacci - Trace Code
f(n)=
\begin{cases}
1, \text{if \,n=1\,or \,2}\\
f(n-1)+f(n-2), \text{otherwise}
\end{cases}
int fib(int x){
if ( x==1 || x==2 ) return 1;
return fib(x-1) + fib(x-2);
}
Return就是這個函數=什麼。
Fibonacci - Trace Code
int fib(int x){
if ( x==1 || x==2 ) return 1;
return fib(x-1) + fib(x-2);}
fib(5)
fib(4)
fib(3)
fib(3)
fib(2)
fib(2)
fib(1)
fib(2)
fib(1)
1
1
2
1
1
1
2
3
5
遞迴ㄉ小提醒
記得要寫中止條件
例如fib就是n=1或2
思考遞迴條件
fib(n)=fib(n-1)+fib(n-2)
思考要怎麼將大的問題拆成小的問題。
Try try see !
f(n)=
\begin{cases}
1, \text{if \,n=1\,or \,2\,or \, 3}\\
f(n-1)+f(n-2), \text{if n \% 3 = 0 }\\
f(n-1)+f(n-3), \text{otherwise}
\end{cases}
f(20) = 1352,你可以自己驗證看看。
Try try see !
f(20) = 1352,你可以自己驗證看看。
int f(int n){
if(n<=3) return 1;
if(n%3==0) return f(n-1)+f(n-2);
return f(n-1)+f(n-3);
}
求出f(100)!
是不是求不出來呢?
記憶化(Memorization)
Memorization
F(3)一樣卻會重算!
Fibonacci - Memorization
int mem[10000]={0}; // 初始化成0。
int fib(int x){
if(n == 1 || n == 2) return 0;
if(mem[x]) // mem[x] = 0 表示沒存過。
return mem[x];
else
return mem[x] = fib(x-1) + fib(x-2);
}
如果mem沒存過,就直接算,並且存入mem。
如果算過,就直接回傳。
Memorization - Example
int mem[10000]={0}; // 初始化成0。
int f(int n){
if(n<=3) return 1;
if(mem[n]) return mem[n];
if(n%3==0) return mem[n] = f(n-1)+f(n-2);
return mem[n] = f(n-1)+f(n-3);
}
事實上這就是個簡單DP!
DP : Dynamic Programming 動態規劃
DP的其中一個實作方法就是遞迴 + 記憶化
Practice!
Problem Description
Solution - 中止條件
m=1
m=n
Solution - 遞迴規則
C(n,m) =
C(n-1,m-1)+
C(n-1,m)
Solution
#include <iostream>
using namespace std;
int C(int n,int m){
if(m==1 || n==m) return 1;
else return C(n-1,m-1)+C(n-1,m);
}
int main(){
int T;
cin >> T;
while(T--){
int n,m;
cin >> n >> m;
cout << C(n,m) << endl;
}
}
Solution + Memorization
#include <iostream>
using namespace std;
int mem[100][100]={0};
int C(int n,int m){
if(m==1 || n==m) return 1;
else if(mem[n][m]) return mem[n][m];
else return mem[n][m] = C(n-1,m-1)+C(n-1,m);
}
int main(){
int T;
cin >> T;
while(T--){
int n,m;
cin >> n >> m;
cout << C(n,m) << endl;
}
}
最大公因數 - GCD
輾轉相除法
如果 a>b
則 a 會變成 a%b,
並算GCD(a%b,b)
輾轉相除法
如果 a>b
則 a 會變成 a%b,並算GCD(a%b,b)
換句話說:
如果 a>b
GCD(a,b) = GCD(a%b , b)
輾轉相除法
如果 a>b,
GCD(a,b) = GCD(a%b , b)
= GCD(b, a%b)
為什麼a%b,b要換位子?
因為要讓左邊的數字比較大。
因為 b > a%b
GCD
int GCD(int a,int b){
if (b==0) return a;
return GCD(b,a%b);
}
想想看,
如果呼叫GCD(x,y),其中x<y會怎麼樣?
遞迴還可以幹嘛?
Everything You Want!
爆破別人的密碼?
a
zzzzzzzz
aa
aaa
...
b
ba
...
...
...
密碼的所有可能
爆搜最佳解!
a
zzzzzzzz
aa
aaa
...
b
ba
...
...
...
密碼的所有可能
遞迴
By Arvin Liu
遞迴
Teaching slide - Recurssion (I)
- 1,466