聽說這是第七堂大赦剋

聽說今天要上遞迴

[ 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

遞迴

"若有一函數是由自己定義的,則此為遞迴式"

[ 以數學角度來看 ]

a_n = \left\{ \begin{matrix} 0 &if& n = 0\\ 1 &if& n = 1\\ a_{n-1} + a_{n-2} &if& n \geq 2 \end{matrix} \right.

費波那契數列

遞迴

這兩個東西是等價的!

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,繼續找下一項
}
a_n = \left\{ \begin{matrix} 0 &if& n = 0\\ 1 &if& n = 1\\ a_{n-1} + a_{n-2} &if& n \geq 2 \end{matrix} \right.

不要懷疑,我們剛剛怎麼呼喚別人

就怎麼呼喚自己,完全相同

一句話完成定義:在 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

未考慮到所有情況

應該是今天最可愛的一題實作

F(x) = \left\{ \begin{matrix} 1 &if& x = 1\\ F(x/2) &if& x \equiv 0 \pmod{2}\\ F(x-1) + F(x+1) &if& x \equiv 1 \pmod{2} \end{matrix} \right.
  • 如果你看不懂下面兩行,它的意思是 如果 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';
}
F(x) = \left\{ \begin{matrix} 1 &if& x = 1\\ F(x/2) &if& x \equiv 0 \pmod{2}\\ F(x-1) + F(x+1) &if& x \equiv 1 \pmod{2} \end{matrix} \right.

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

  • 把上面所有東西移到暫存區,再把自己移到終點       最後把暫存區的東西放到終點

"把上面所有東西移到暫存區,再把自己移到終點,最後把暫存區的東西放到終點"

  • 總步數?
a_n = \left\{ \begin{matrix} 1 &if& n = 1\\ 2 × a_{n-1} + 1 &if& n \geq 2\\ \end{matrix} \right.
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

現在就可以繳錢了,快來吧

  • 我說你家同是交了沒

建中社活組是個破單位咩