遞迴二

Ruby @ Sprout 2022
Ruby@Sprout

內容大綱

遞迴二

Ruby@Sprout

背包問題

Knapsack Problem

記憶化

Memoization

淹水問題

Flood Fill Problem

記憶化

Ruby@Sprout

遞迴二

Memoization

遞迴二 > 記憶化

二項式定理 Binomial Theorem
\binom{n}{m} = \binom{n-1}{m} + \binom{n-1}{m-1}
Ruby@Sprout
二項式定理 Binomial Theorem
\binom{n}{m}= \left\{ \begin{matrix} 1 & \text{ if } & n=m\text{ or }m=0 \\ \binom{n-1}{m} + \binom{n-1}{m-1} & \text{ if } & n\ne m\text{ and }m\ne0 \end{matrix} \right.

遞迴二 > 記憶化

Ruby@Sprout
二項式定理 Binomial Theorem
\binom{n}{m}= \left\{ \begin{matrix} 1 & \text{ if } & n=m\text{ or }m=0 \\ \binom{n-1}{m} + \binom{n-1}{m-1} & \text{ if } & n\ne m\text{ and }m\ne0 \end{matrix} \right.
int binomial(int n, int m) {
    if (n == m || m == 0) return 1;
    return binomial(n-1, m) + binomial(n-1, m-1);
}

遞迴二 > 記憶化

Ruby@Sprout
樹 Tree

遞迴二 > 記憶化

葉子

根部

根節點

Root Node

葉節點

Leaf Nodes

生活中的樹

Real Life Tree

樹資料結構

Tree Data Structure
Ruby@Sprout
二項式定理 Binomial Theorem

遞迴二 > 記憶化

\binom{5}{3}
\binom{4}{3}
\binom{4}{2}
\binom{3}{3}
\binom{3}{2}
\binom{2}{2}
\binom{2}{1}
\binom{3}{2}
\binom{3}{1}
\binom{2}{2}
\binom{2}{1}

回顧:當呼叫                              時,會發生什麼?

binomial(5, 3)
1
\binom{1}{1}
\binom{1}{0}
\binom{1}{1}
\binom{1}{0}
\binom{2}{1}
\binom{2}{0}
\binom{1}{1}
\binom{1}{0}
1
1
1
1
1
1
1
1
1
Ruby@Sprout
發現問題:重複計算

遞迴二 > 記憶化

\binom{5}{3}
\binom{4}{3}
\binom{4}{2}
\binom{3}{3}
\binom{3}{2}
\binom{2}{2}
\binom{2}{1}
\binom{3}{2}
\binom{3}{1}
\binom{2}{2}
\binom{2}{1}

回顧:當呼叫                              時,會發生什麼?

binomial(5, 3)
1
\binom{1}{1}
\binom{1}{0}
\binom{1}{1}
\binom{1}{0}
\binom{2}{1}
\binom{2}{0}
\binom{1}{1}
\binom{1}{0}
1
1
1
1
1
1
1
1
1
耗費大量資源計算重複結果
bad
Ruby@Sprout
解決方案 Solution

遞迴二 > 記憶化

記憶化 (Memoization)

記憶化是電腦科學常用的最佳化技術,主要用來加速方法或函式之間的呼叫,將呼叫過的函式回傳結果暫存,即可避免重複計算。 

長話短說

把算過的東西記起來。
Ruby@Sprout
記憶化實作 Implementation of Memoization

遞迴二 > 記憶化

int binom[101][101] = { 0 };

int memoizedBinomial(int n, int m) {
    if (binom[n][m] != 0) return binom[n][m];
    if (n == m || m == 0) return binom[n][m] = 1;
    return (
        binom[n][m] 
        = memoizedBinomial(n-1, m) 
        + memoizedBinomial(n-1, m-1)
    );
}
Code
Ruby@Sprout
記憶化二項式函式 Memoized Binomial Function

遞迴二 > 記憶化

\binom{5}{3}
\binom{4}{3}
\binom{4}{2}
\binom{3}{3}
\binom{3}{2}
\binom{2}{2}
\binom{2}{1}
\binom{3}{2}
\binom{3}{1}

當呼叫                                            時

memoizedBinomial(5, 3)
1
\binom{1}{1}
\binom{1}{0}
\binom{2}{1}
\binom{2}{0}
1
1
1
1
3
4
2
3
2
3
6
10
Ruby@Sprout
記憶化二項式函式 Memoized Binomial Function

遞迴二 > 記憶化

\binom{5}{3}
\binom{4}{3}
\binom{4}{2}
\binom{3}{3}
\binom{3}{2}
\binom{2}{2}
\binom{2}{1}
\binom{3}{2}
\binom{3}{1}
1
\binom{1}{1}
\binom{1}{0}
\binom{2}{1}
\binom{2}{0}
1
1
1
1
3
4
2
3
2
3
6
10

當呼叫                                            時

memoizedBinomial(5, 3)
Ruby@Sprout
記憶化實作 Implementation of Memoization

遞迴二 > 記憶化

int binom[101][101] = { 0 };

int memoizedBinomial(int n, int m) {
    if (binom[n][m] != 0) return binom[n][m];
    if (n == m || m == 0) return binom[n][m] = 1;
    int res = memoizedBinomial(n-1, m) 
            + memoizedBinomial(n-1, m-1);
    return binom[n][m] = binom[n][n-m] = res;
}
Even Better Code
Ruby@Sprout
記憶化二項式函式 Memoized Binomial Function

遞迴二 > 記憶化

\binom{5}{3}
\binom{4}{3}
\binom{4}{2}
\binom{3}{3}
\binom{3}{2}
\binom{2}{2}
\binom{2}{1}
\binom{3}{2}
\binom{3}{1}
1
\binom{1}{1}
\binom{1}{0}
1
1
1
3
4
2
3
6
10
3

當呼叫                                            時

memoizedBinomial(5, 3)
nice
Ruby@Sprout

記憶化

Ruby@Sprout

遞迴二

Memoization

內容大綱

遞迴二

Ruby@Sprout

背包問題

Knapsack Problem

記憶化

Memoization

淹水問題

Flood Fill Problem

背包問題

Ruby@Sprout

遞迴二

Knapsack Problem

遞迴二 > 背包問題

什麼是背包問題? What is the Knapsack Problem?

情境:你是一個醫生,某天...

(當作放鬆三分鐘吧!)

Ruby@Sprout
什麼是背包問題? What is the Knapsack Problem?

經過簡單搜索,你清點了所有在那位自殺軍官身上的隨身物品。

願他安息,但現在最重要的是要活下去。所以你需要這些物品,

以最提升自己生存的機會。不過你發現,你無法帶走所有物品

遞迴二 > 背包問題

Ruby@Sprout
什麼是背包問題? What is the Knapsack Problem?

因此,你測量了所有物品的重量並評估能夠帶來的生存價值。試圖在背包負重限制內,帶走使生存機會提高最多的物品組合

遞迴二 > 背包問題

你,該怎麼選?

[重量] 4 KG
[價值] 6

[重量] 1 KG
[價值] 1

[重量] 1 KG
[價值] 2

[重量] 5 KG
[價值] 6

[重量] 4 KG
[價值] 3

[重量] 5 KG
[價值] 5

負重 10 KG

Ruby@Sprout
選擇策略 Choosing Strategy

遞迴二 > 背包問題

[重量] 4 KG
[價值] 6

[重量] 1 KG
[價值] 1

[重量] 1 KG
[價值] 2

[重量] 5 KG
[價值] 6

[重量] 4 KG
[價值] 3

[重量] 5 KG
[價值] 5

負重 10 KG

一、從輕的開始選,選到不能選

[總價值] 12

Ruby@Sprout
選擇策略 Choosing Strategy

遞迴二 > 背包問題

[重量] 4 KG
[價值] 6

[重量] 1 KG
[價值] 1

[重量] 1 KG
[價值] 2

[重量] 5 KG
[價值] 6

[重量] 4 KG
[價值] 3

[重量] 5 KG
[價值] 5

負重 10 KG

二、從價值大的開始選,選到不能選

[總價值] 14

Ruby@Sprout
選擇策略 Choosing Strategy

遞迴二 > 背包問題

最佳解

[總價值] 14

從輕的選

[總價值] 12

從價值大的選

[總價值] 14

Ruby@Sprout
選擇策略 Choosing Strategy

遞迴二 > 背包問題

以下三選擇策略
1. 從輕的開始選
2. 從價值大的開始選
​3. 從價值與重量比值大的開始選
都可以被稱作
貪婪演算法 (Greedy Algorithm)
不保證每次選擇的物品組合都是最佳解!
Ruby@Sprout
貪婪演算法失效 Underperformance of Greedy

遞迴二 > 背包問題

WEIGHT_LIMIT := 100
TestCase #1:
(W, V)
(100, 100), (99, 99), (1, 99)
從價值大的選:[]    
真正的最佳解:[] 

TestCase #2:
(W, V, V/W)
(1, 50, 50), (100, 100, 1), (2, 4, 2)
從比值大的選:[] 
真正的最佳解:[]    
Ruby@Sprout
貪婪演算法失效 Underperformance of Greedy

遞迴二 > 背包問題

WEIGHT_LIMIT := 100
TestCase #1:
(W, V)
(100, 100), (99, 99), (1, 99)
從價值大的選:[1]    
真正的最佳解:[] 

TestCase #2:
(W, V, V/W)
(1, 50, 50), (100, 100, 1), (2, 4, 2)
從比值大的選:[] 
真正的最佳解:[]    
Ruby@Sprout
貪婪演算法失效 Underperformance of Greedy

遞迴二 > 背包問題

WEIGHT_LIMIT := 100
TestCase #1:
(W, V)
(100, 100), (99, 99), (1, 99)
從價值大的選:[1]    
真正的最佳解:[] 

TestCase #2:
(W, V, V/W)
(1, 50, 50), (100, 100, 1), (2, 4, 2)
從比值大的選:[] 
真正的最佳解:[]    
Ruby@Sprout
貪婪演算法失效 Underperformance of Greedy

遞迴二 > 背包問題

WEIGHT_LIMIT := 100
TestCase #1:
(W, V)
(100, 100), (99, 99), (1, 99)
從價值大的選:[1]    
真正的最佳解:[2, 3] 

TestCase #2:
(W, V, V/W)
(1, 50, 50), (100, 100, 1), (2, 4, 2)
從比值大的選:[] 
真正的最佳解:[]    
Ruby@Sprout
貪婪演算法失效 Underperformance of Greedy

遞迴二 > 背包問題

WEIGHT_LIMIT := 100
TestCase #1:
(W, V)
(100, 100), (99, 99), (1, 99)
從價值大的選:[1]    
真正的最佳解:[2, 3] 

TestCase #2:
(W, V, V/W)
(1, 50, 50), (100, 100, 1), (2, 4, 2)
從比值大的選:[] 
真正的最佳解:[]    
Ruby@Sprout
totalV = 100
totalV = 198
貪婪演算法失效 Underperformance of Greedy

遞迴二 > 背包問題

WEIGHT_LIMIT := 100
TestCase #1:
(W, V)
(100, 100), (99, 99), (1, 99)
從價值大的選:[1]    
真正的最佳解:[2, 3] 

TestCase #2:
(W, V, V/W)
(1, 50, 50), (100, 100, 1), (2, 4, 2)
從比值大的選:[1] 
真正的最佳解:[]    
Ruby@Sprout
totalV = 100
totalV = 198
貪婪演算法失效 Underperformance of Greedy

遞迴二 > 背包問題

WEIGHT_LIMIT := 100
TestCase #1:
(W, V)
(100, 100), (99, 99), (1, 99)
從價值大的選:[1]    
真正的最佳解:[2, 3] 

TestCase #2:
(W, V, V/W)
(1, 50, 50), (100, 100, 1), (2, 4, 2)
從比值大的選:[1, 3] 
真正的最佳解:[]    
Ruby@Sprout
totalV = 100
totalV = 198
貪婪演算法失效 Underperformance of Greedy

遞迴二 > 背包問題

WEIGHT_LIMIT := 100
TestCase #1:
(W, V)
(100, 100), (99, 99), (1, 99)
從價值大的選:[1]    
真正的最佳解:[2, 3] 

TestCase #2:
(W, V, V/W)
(1, 50, 50), (100, 100, 1), (2, 4, 2)
從比值大的選:[1, 3] 
真正的最佳解:[2]    
Ruby@Sprout
totalV = 100
totalV = 198
貪婪演算法失效 Underperformance of Greedy

遞迴二 > 背包問題

WEIGHT_LIMIT := 100
TestCase #1:
(W, V)
(100, 100), (99, 99), (1, 99)
從價值大的選:[1]    
真正的最佳解:[2, 3] 

TestCase #2:
(W, V, V/W)
(1, 50, 50), (100, 100, 1), (2, 4, 2)
從比值大的選:[1, 3] 
真正的最佳解:[2]    
Ruby@Sprout
totalV = 100
totalV = 198
totalV = 54
totalV = 100
選擇策略 Choosing Strategy

遞迴二 > 背包問題

如何保證每次選擇的物品組合都是最佳解?
枚舉 (Enumeration)
枚舉(列舉、窮舉)所有可以選擇的物品組合,並選擇所有組合中價值最大的。
但,我們該如何枚舉?
Ruby@Sprout
枚舉 Enumeration

遞迴二 > 背包問題

一、枚舉每個物品的「選與不選
將物品編號,並以遞迴枚舉所有選法,再檢查有沒有超重
想法
1
2
2
3
3
3
3

不選

123
12
13
1
23
2
3

Ruby@Sprout
枚舉 Enumeration

遞迴二 > 背包問題

一、枚舉每個物品的「選與不選
int V[N] = { 12, 23, ... };
int W[N] = { 11, 25, ... };
int maxValue = -1;

void choose(int i, int v, int w) {
    if (i == N-1) {
        if (w <= WEIGHT_LIMIT)
            maxValue = max(maxValue, v);
        return;
    }
    choose(i+1, v+V[i], w+W[i]); /* 選 */ 
    choose(i+1, v, w);           /* 不選 */
}
choose(0, 0, 0);
cout << maxValue << endl;
實作
Ruby@Sprout
枚舉 Enumeration

遞迴二 > 背包問題

一、枚舉每個物品的「選與不選
簡單剪枝
將物品編號,並以遞迴枚舉所有選法,枚舉過程中每一步都檢查有沒有超重。
將物品編號,並以遞迴枚舉所有選法,再檢查有沒有超重
剪枝:在枚舉過程中,提前終止不滿組條件(不合法)的狀態。
Ruby@Sprout
枚舉 Enumeration

遞迴二 > 背包問題

一、枚舉每個物品的「選與不選
簡單剪枝
剪枝:在枚舉過程中,提前終止不滿組條件(不合法)的狀態。
1
2
2
3
3
3
3

不選

123
12
13
1
23
2
3

例如:物品1本身已超出負重

超重

超重

超重

超重

Ruby@Sprout
枚舉 Enumeration

遞迴二 > 背包問題

一、枚舉每個物品的「選與不選
剪枝:在枚舉過程中,提前終止不滿組條件(不合法)的狀態。
簡單剪枝
1
2
3
3

不選

23
2
3

超重

Ruby@Sprout
枚舉 Enumeration

遞迴二 > 背包問題

一、枚舉每個物品的「選與不選
int V[N] = { 12, 23, ... };
int W[N] = { 11, 25, ... };
int maxValue = -1;

void choose(int i, int v, int w) {
    if (i == N-1) {
        if (w <= WEIGHT_LIMIT)
            maxValue = max(maxValue, v);
        return;
    }
    choose(i+1, v+V[i], w+W[i]); /* 選 */ 
    choose(i+1, v, w);           /* 不選 */
}
簡單剪枝
Before
Ruby@Sprout
枚舉 Enumeration

遞迴二 > 背包問題

一、枚舉每個物品的「選與不選
int V[N] = { 12, 23, ... };
int W[N] = { 11, 25, ... };
int maxValue = -1;

void choose(int i, int v, int w) {
    if (w > WEIGHT_LIMIT) return;
    if (i == N-1) {
        maxValue = max(maxValue, v);
        return;
    }
    choose(i+1, v+V[i], w+W[i]); /* 選 */ 
    choose(i+1, v, w);           /* 不選 */
}
簡單剪枝
After
Ruby@Sprout
枚舉 Enumeration

遞迴二 > 背包問題

一、枚舉每個物品的「選與不選
1
2
2
3
3
3
3

不選

123
12
13
1
23
2
3

複雜度分析
每個物品有兩個狀態 [選|不選]
若有 n 個物品,則共有 2^n 種狀態 
\text O(2^n)
bad
Ruby@Sprout
枚舉 Enumeration

遞迴二 > 背包問題

二、嘗試記憶化
重新設計
Ruby@Sprout
但第一種枚舉方式難以實現記憶化,所以我們需要重新設計函式。
設計一函式 maxValue(i, w) ,回傳:
可以拿 0~i 號物品的情況下,限重 w 可裝下的最大價值

有回傳值,就可記憶參數所對應的回傳結果

例如: 
0~2號重量5以內物品組合中最大價值=maxValue(2, 5)
0~7號重量9以內物品組合中最大價值=maxValue(7, 9)
枚舉 Enumeration

遞迴二 > 背包問題

二、嘗試記憶化
重新設計
Ruby@Sprout
我們該如何以遞迴實作出 maxValue 函式?
maxValue(i, w)
將問題切割為數個子問題
動態規劃 (Dynamic Programming)
maxValue(i-1, w)
maxValue(i-1, w-W[i])+V[i]

不選

取最大值

枚舉 Enumeration

遞迴二 > 背包問題

二、嘗試記憶化
重新設計
Ruby@Sprout
終止條件:
i < 0
maxValue(i, w)
maxValue(i-1, w)
maxValue(i-1, w-W[i])+V[i]

不選

取最大值

枚舉 Enumeration

遞迴二 > 背包問題

二、嘗試記憶化
重新設計
Ruby@Sprout
int W[N] = { 3, 4, 5, 8,  ... };
int V[N] = { 1, 6, 6, 11, ... };

int maxValue(int i, int w) {
    if (i < 0) return 0;
    int res = maxValue(i-1, w);
    if (w >= W[i])
        res = max(res, maxValue(i-1, w-W[i])+V[i]);
    return res;  
}
Original
枚舉 Enumeration

遞迴二 > 背包問題

二、嘗試記憶化
重新設計
Ruby@Sprout
int W[N] = { 3, 4, 5, 8,  ... };
int V[N] = { 1, 6, 6, 11, ... };

int mv[N][N] = { -1, ..., -1 };
int maxValue(int i, int w) {
    if (i < 0) return 0;
    if (mv[i][w] != -1) return mv[i][w];
    int res = maxValue(i-1, w);
    if (w >= W[i])
        res = max(res, maxValue(i-1, w-W[i])+V[i]);
    return mv[i][w] = res;  
}
Memoized
枚舉 Enumeration

遞迴二 > 背包問題

二、嘗試記憶化
Ruby@Sprout
每個狀態只會計算一次
\text O(nW)
W 為重量限制
複雜度分析
小節 Conclusion

遞迴二 > 背包問題

Ruby@Sprout
閱讀並理解題目
有公式解嗎?
直接套用公式
嘗試枚舉

通過時間限制了嗎?

恭喜!

加上剪枝及記憶化

一、
小節 Conclusion

遞迴二 > 背包問題

Ruby@Sprout
二、
將問題切割為數個子問題
動態規劃 (Dynamic Programming)
活用
寫遞迴並不難,難的是將問題切割為子問題,並找到其之間關係
多練習、多觀察。
小試身手 Exercise

遞迴二 > 背包問題

Ruby@Sprout
[不算分] 課堂練習:
10 min

背包問題

Ruby@Sprout

遞迴二

Knapsack Problem

內容大綱

遞迴二

Ruby@Sprout

背包問題

Knapsack Problem

記憶化

Memoization

淹水問題

Flood Fill Problem

淹水問題

Ruby@Sprout

遞迴二

Flood Fill Problem

遞迴二 > 深度優先搜尋

淹水問題 Flood Fill Problem
Ruby@Sprout

遞迴二 > 深度優先搜尋

淹水問題 Flood Fill Problem
Ruby@Sprout
1
2
3
4

遞迴二 > 深度優先搜尋

淹水問題 Flood Fill Problem
Ruby@Sprout
Depth-First Search (DFS)
深度優先搜尋
Breadth-First Search (BFS)
廣度優先搜尋
How to flood fill?
該如何淹水?
今日關注的方法

遞迴二 > 深度優先搜尋

深度優先搜尋 Depth-First Search
Ruby@Sprout
深度優先搜尋,顧名思義:
以深度為優先進行「淹水擴散」

遞迴二 > 深度優先搜尋

深度優先搜尋 Depth-First Search
Ruby@Sprout
以 3x3 庭院示範:

遞迴二 > 深度優先搜尋

深度優先搜尋 Depth-First Search
Ruby@Sprout
以 3x3 庭院示範:
2
3
4
1
1

遞迴二 > 深度優先搜尋

深度優先搜尋 Depth-First Search
Ruby@Sprout
以 3x3 庭院示範:
2
4
2
4
3
1
2

遞迴二 > 深度優先搜尋

深度優先搜尋 Depth-First Search
Ruby@Sprout
以 3x3 庭院示範:
4
2
4
3
3
1
2
3

遞迴二 > 深度優先搜尋

深度優先搜尋 Depth-First Search
Ruby@Sprout
以 3x3 庭院示範:
4
2
4
3
3
1
2
3
4

遞迴二 > 深度優先搜尋

深度優先搜尋 Depth-First Search
Ruby@Sprout
以 3x3 庭院示範:
4
2
4
3
4
1
2
3
4
5

遞迴二 > 深度優先搜尋

深度優先搜尋 Depth-First Search
Ruby@Sprout
以 3x3 庭院示範:
4
2
4
3
4
1
2
3
4
5
6

遞迴二 > 深度優先搜尋

深度優先搜尋 Depth-First Search
Ruby@Sprout
以 3x3 庭院示範:
4
2
4
3
1
2
3
4
5
6
1
7

遞迴二 > 深度優先搜尋

深度優先搜尋 Depth-First Search
Ruby@Sprout
以 3x3 庭院示範:
4
2
4
3
1
2
3
4
5
6
7
8
1

遞迴二 > 深度優先搜尋

深度優先搜尋 Depth-First Search
Ruby@Sprout
以 3x3 庭院示範:
4
2
4
3
1
2
3
4
5
6
7
8
9

遞迴二 > 深度優先搜尋

深度優先搜尋 Depth-First Search
Ruby@Sprout
以 3x3 庭院示範:
4
2
4
3
1
2
3
4
5
6
7
8
9

遞迴二 > 深度優先搜尋

深度優先搜尋 Depth-First Search
Ruby@Sprout
以 3x3 庭院示範:
4
2
4
3
1
2
3
4
5
6
7
8
9

遞迴二 > 深度優先搜尋

深度優先搜尋 Depth-First Search
Ruby@Sprout
以 3x3 庭院示範:
4
2
4
3
1
2
3
4
5
6
7
8
9

遞迴二 > 深度優先搜尋

深度優先搜尋 Depth-First Search
Ruby@Sprout
以 3x3 庭院示範:
4
2
4
3
1
2
3
4
5
6
7
8
9

遞迴二 > 深度優先搜尋

深度優先搜尋 Depth-First Search
Ruby@Sprout
以 3x3 庭院示範:
4
2
4
3
1
2
3
4
5
6
7
8
9

遞迴二 > 深度優先搜尋

深度優先搜尋 Depth-First Search
Ruby@Sprout
以 3x3 庭院示範:
4
2
4
3
1
2
3
4
5
6
7
8
9

遞迴二 > 深度優先搜尋

深度優先搜尋 Depth-First Search
Ruby@Sprout
以 3x3 庭院示範:
4
2
4
3
1
2
3
4
5
6
7
8
9

遞迴二 > 深度優先搜尋

深度優先搜尋 Depth-First Search
Ruby@Sprout
以 3x3 庭院示範:
2
4
3
1
2
3
4
5
6
7
8
9

遞迴二 > 深度優先搜尋

深度優先搜尋 Depth-First Search
Ruby@Sprout
以 3x3 庭院示範:
4
3
1
2
3
4
5
6
7
8
9

遞迴二 > 深度優先搜尋

深度優先搜尋 Depth-First Search
Ruby@Sprout
以 3x3 庭院示範:
4
1
2
3
4
5
6
7
8
9

遞迴二 > 深度優先搜尋

深度優先搜尋 Depth-First Search
Ruby@Sprout
以 3x3 庭院示範:
1
2
3
4
5
6
7
8
9

遞迴二 > 深度優先搜尋

深度優先搜尋 Depth-First Search
Ruby@Sprout
以 3x3 庭院示範:
1
2
3
4
5
6
7
8
9
Done!

遞迴二 > 深度優先搜尋

深度優先搜尋 Depth-First Search
Ruby@Sprout
實作
int grid[N][N] = { EMPTY, ..., EMPTY };
/* grid[y][x] ∈ { EMPTY, WATER, WALL } */
void dfs(int x, int y) {
    grid[y][x] = WATER;
    if (y-1 >= 0 && grid[y-1][x] == EMPTY) 
        dfs(x, y-1);  /* 向上擴散 */
    if (x+1 < N  && grid[y][x+1] == EMPTY) 
        dfs(x+1, y);  /* 向右擴散 */
    if (y+1 < N  && grid[y+1][x] == EMPTY) 
        dfs(x, y+1);  /* 向下擴散 */
    if (x-1 >= 0 && grid[y][x-1] == EMPTY) 
        dfs(x-1, y);  /* 向左擴散 */
}

遞迴二 > 深度優先搜尋

小試身手 Exercise
Ruby@Sprout
[算分] 課堂練習:
10 min

遞迴二 > 深度優先搜尋

小試身手 Exercise
Ruby@Sprout
參考解答:
[想法]

遍歷整個 grid ,每見到一格水,就把答案 +1 ,並且在該點做 dfs 把每個連續的水方塊做標記,這樣才不會重複計算水池數量。

[程式碼]

淹水問題

Ruby@Sprout

遞迴二

Flood Fill Problem

內容大綱

遞迴二

Ruby@Sprout

背包問題

Knapsack Problem

記憶化

Memoization

淹水問題

Flood Fill Problem
謝謝大家

Brought to you by Ruby
Ruby@Sprout

資訊之芽 Sprout 2022 - 遞迴二

By Ruby Ku

資訊之芽 Sprout 2022 - 遞迴二

  • 505