聽說這是第七堂大赦剋
聽說今天要上遞迴
[ Lecturer ]
"你這節課不是應該待在隔壁嗎"
"我英文手寫沒寫名字被扣 10 分"
"我有什麼辦法,就真的很可愛啊"
Repkironca,aka 阿蘇
這好像是我第一次出現在大社
你們應該知道我是誰吧
對吧對吧 (っ °Д °;)っ
前情提要
bool IsLying (int first, int second){
if (first != 5 || second != 5){
cout << "My APCS successfully failed.\n";
return false;
}
cout << "stop lying, it's impossible for you\n";
return true;
}
這也是一個 function
而且它有傳入值跟回傳值
void Print (){
cout << "My APCS successfully failed.\n";
}
這是一個 function
這是 sin 的反元素
中文稱為 反正弦
但反弦 !=function
> 那可以在 function 中呼叫另一個 function 嗎
#include <iostream>
using namespace std;
void Study(){
cout << "Studying...\n";
}
void Touch_Ayame (){
cout << "Touching_ayame...\n";
}
void Compare (int study, int touch_ayame){
// 摸魚的英文不是 touch-fish,但摸余肯定是 touch-ayame
bool judge = study >= touch_ayame;
while(study > 0 || touch_ayame > 0){
if (study-- > 0) Study();
if (touch_ayame-- > 0) Touch_Ayame();
}
if (judge) cout << "好耶你真的是出來讀書的\n";
else cout << "你根本從頭到尾都在混==\n";
}
int main (){
int s, t; cin >> s >> t;
Compare(s, t);
}
問就是可以,有何不能的理由
study = 3
t.a. = 5
judge = false
study = 3
study = 2
t.a. = 4
study = 2
study = 1
t.a. = 3
study = 1
study = 0
t.a. = 2
t.a. = 2
t.a. = 1
t.a. = 1
t.a. = 0
遞迴
"若有一函數是由自己定義的,則此為遞迴式"
[ 以數學角度來看 ]
費波那契數列
遞迴
這兩個東西是等價的!
int Fibonacci(int tar, int index = 2, int a = 1, int b = 0){
if (tar == 0) return 0;
// 如果是第 0 項,定義為 0
if (tar == 1) return 1;
// 如果是第 1 項,定義為 1
if (tar == index) return a + b;
// 若已經找到所求項,則 a + b 就是答案
return Fibonacci(tar, ++index, a+b, a);
// 否則更新 a、b,繼續找下一項
}
不要懷疑,我們剛剛怎麼呼喚別人
就怎麼呼喚自己,完全相同
一句話完成定義:在 function 中呼喚自己
[ 以資訊角度來看 ]
遞迴
看起來很抽象ㄇ?那我們一步步抽絲剝繭
int Fibonacci(int tar, int index = 2, int a = 1, int b = 0){
if (tar == 0) return 0;
// 如果是第 0 項,定義為 0
if (tar == 1) return 1;
// 如果是第 1 項,定義為 1
if (tar == index) return a + b;
// 若已經找到所求項,則 a + b 就是答案
return Fibonacci(tar, ++index, a+b, a);
// 否則更新 a、b,繼續找下一項
}
tar = 5
index = 2
a = 1
b = 0
tar = 5
index = 3
a = 1
b = 1
tar = 5
index = 4
a = 2
b = 1
tar = 5
index = 5
a = 3
b = 2
tar = 5
index = 2
a = 1
b = 0
tar = 5
index = 2
a = 1
b = 0
tar = 5
index = 2
a = 1
b = 0
tar = 5
index = 3
a = 1
b = 1
tar = 5
index = 3
a = 1
b = 1
tar = 5
index = 3
a = 1
b = 1
tar = 5
index = 3
a = 1
b = 1
tar = 5
index = 4
a = 2
b = 1
tar = 5
index = 4
a = 2
b = 1
tar = 5
index = 4
a = 2
b = 1
tar = 5
index = 4
a = 2
b = 1
about 終止條件
即第六行(二 & 四是特判)
一定要有個條件可以結束遞迴
且確保此條件是絕對會發生的
否則永遠跑不完
int Fibonacci(int tar, int index = 2, int a = 1, int b = 0){
if (tar == 0) return 0;
// 如果是第 0 項,定義為 0
if (tar == 1) return 1;
// 如果是第 1 項,定義為 1
if (tar == index) return a + b;
// 若已經找到所求項,則 a + b 就是答案
return Fibonacci(tar, ++index, a+b, a);
// 否則更新 a、b,繼續找下一項
}
int NeverStop(int n){
return NeverStop(n-1) + NeverStop(n-2);
}
缺乏終止條件
等同於無限迴圈
不是吃 TLE 就是被 RE
int NeverStop(int n){
if (n == 0) return 0;
return NeverStop(n-1) + NeverStop(n-2);
}
終止條件不合理
n = 1 時,會遞迴到 - 1
未考慮到所有情況
應該是今天最可愛的一題實作
- 如果你看不懂下面兩行,它的意思是 如果 x / 2 的餘數為 0 及 如果 x / 2 的餘數為 1
- 終止條件記得要寫在最上面
- 還是吃 WA 嗎,哥你 484 又忘記換行了
#include <iostream>
#define gura ios_base::sync_with_stdio(false), cin.tie(0), cout.tie(0);
using namespace std;
int F (int x){
if (x == 1) return 1;
if (x%2 == 0) return F(x/2);
return F(x-1) + F(x+1);
}
int main (){
gura // i/o 優化,不過這題不加也不會怎麼樣,與第二行等價
int num; cin >> num;
cout << F(num) << '\n';
}
AC Code
遞迴除了能拿來處理遞迴
還有其他雜七雜八的應用。
- 盒內塔問題(等等會講)
具體來說,遞迴 99.9 % 都不是用在剛剛那邊
- 二分搜、最小公倍數、分治(等等不會講)
- 所以我說真的不考慮來小社ㄇ {{{(>_<)}}}
河內塔問題
CSES_2165 Tower of Hanoi
- 原始問題應該會到 64 個盤子,但那會把你電腦操爆
- 感覺很不遞迴嗎?那我們來觀察一下有沒有規律可言
- 令最上面的盤子編號為 1,編號向下遞增,三個柱子分別為 L、M、R
N = 1,總步數 1 步
N = 2,總步數 3 步
L M R
1
2
1
L M R
2
1
L M R
2
1
L M R
N = 3,總步數 7 步
2
L M R
1
3
2
L M R
1
3
2
L M R
1
3
2
L M R
1
3
2
L M R
1
3
2
L M R
1
3
2
L M R
1
3
- 把上面所有東西移到暫存區,再把自己移到終點 最後把暫存區的東西放到終點
"把上面所有東西移到暫存區,再把自己移到終點,最後把暫存區的東西放到終點"
- 總步數?
int find_total (int num){
if (num == 1) return 1;
return 2 * find_total(num-1) + 1;
}
- 那我要怎麼做出它的歷程
void move (int plate, int from, int mid, int tar){
if (plate == 0) return;
move(plate-1, from, tar, mid); //(上面的)起點 到 暫存
cout << from << ' ' << tar << '\n'; //(自己) 起點 到 終點
move(plate-1, mid, from, tar); //(上面的)暫存 到 終點
}
就,直接照做啊w
#include <bits/stdc++.h>
#define gura ios_base::sync_with_stdio(false), cin.tie(0), cout.tie(0);
using namespace std;
int find_total (int num){
if (num == 1) return 1;
return find_total(num-1) * 2 + 1;
}
void move (int plate, int from, int mid, int tar){
if (plate == 0) return;
move(plate-1, from, tar, mid); //(上面的)起點 到 暫存
cout << from << ' ' << tar << '\n'; //(自己) 起點 到 終點
move(plate-1, mid, from, tar); //(上面的)暫存 到 終點
}
int main (){
gura
int plate_count; cin >> plate_count;
cout << find_total(plate_count) << '\n';
move(plate_count, 1, 2, 3);
}
AC Code
Why Recursion?
- 可讀性高
很多東西寫成迴圈真的很難懂
- 好寫、符合直覺
當然如果你覺得迴圈比較好寫
你要用迴圈我也沒意見
只是後面題目真的很難砸迴圈
- 速度相對較慢
Solution:記憶化、砸演算法 ...
遞迴的其他常見運用
暫時還不用學會,我不打算也沒時間講
不過演算法小社會慢慢教
最大公因數
利用遞迴刻出輾轉相除法
希望你們還記得它┏(゜ロ゜;)┛
int gcd(int a, int b){
if (a < b) return gcd(b, a);
if (b == 0) return a;
return gcd(b, a % b);
}
喔你想多了,這當然不是 AC CODE
二分搜尋法
不過用遞迴做這題會被卡常
只能用迴圈就是
int binary_search (int tar, int l, int r, vector <int> vec){
int mid = (l+r) / 2;
if (vec[mid] == tar) return mid;
if (mid == l || mid == r) return 0;
if (vec[mid] > tar) return binary_search(tar, l, mid, vec);
return binary_search(tar, mid+1, r, vec);
}
//==========================
while (true){
int mid = (l+r) / 2;
if (vec[mid] == query){
cout << mid << '\n';
break;
}
else if (mid == l || mid == r){
cout << 0 << '\n';
break;
}
else if (vec[mid] > query) r = mid;
else l = mid + 1;
}
DFS,深度優先搜尋
事實上用 BFS 也能解出
但我們還沒學資料結構
你們可能找不到方法做這個
否則滿水的其實
void dfs (int node){
visited[node] = true;
for (auto to:graph[node]){
if (!visited[to]) dfs(to);
}
}
這些時機也可能要砸遞迴
- Divide and Conquer,分治演算法
- Dynamic Programming,動態規劃
- 對一個 graph 做各種噁心操作
- APCS 想不到解答要唬爛一個暴力解的時候
- 直覺告訴你要用遞迴的時候
好耶砸題單時間(TCIRC)
其實就,輾轉相除法,一模一樣
很煩,超級煩,強烈建議用 vector 做
如果你做出了上一題,那這就是水題
已經有點實作題的味道ㄌ
好耶砸題單時間(CSES)
八皇后問題,去年有被改編成社賽題目
做為防破台門檻。你們肯定可以的吧:)
前面的例題
好耶砸題單時間(NEOJ)
理論上他希望你用遞迴刻 Merge Sort
但我知道你會砸 std::sort()
有個內建函式可以解決這個
但試試用遞迴寫 la
如果現在想不出來,可以等上完 DP 再回頭
相對來說比較簡單
好耶砸題單時間(ZJ)
前面的例題,且與 TCIRC_b001 完全等價
呈 CSES_2165,不過又多了變化
現在開始與結束狀態都不固定了
先思考一下他的遞迴式為何?
題目本身是不難
但輸出格式很麻煩
學長我寫不出來 QAQ
那就去看解答
沒啦,認真說的話,如果你已經苦思良久,完全沒有半點想法
去觀摩 AC CODE 從中汲取養分
並非什麼可恥之事,或你把任何想到的做法都砸一次
反正吃 WA 就吃 WA,又不會有人笑你
然後也可以在群組直接發問沒關係
只要有時間我們都很樂意幫助
真的不敢也可以來私訊我 XD
A,話說
- 地點鬼轉成 二二八和平公園
有人簡稱為 二二八
可惜他不會打橄欖球
有人簡稱為 二和
可惜不是北一的那間
- 烤肉店鬼轉成 夯肉殿
不是 那真的很好吃 我確定
- 報名費不變,還是 $ 800
現在就可以繳錢了,快來吧
- 我說你家同是交了沒
建中社活組是個破單位咩