# APCS實作題2019年6月第2題:機器人的路徑 > 第1版:2023年9月9日 > 第2版:2025年1月6日,新增 BFS 的寫法 > 作者:王一哲 > 題目來源:108年6月實作題第2題 > [ZeroJudge 題目連結](https://zerojudge.tw/ShowProblem?problemid=e287) <br /> ## 題目 ### 問題描述 你有一台機器人,它會在地上爬。經過了8756天的觀察過後,你發現了它移動的規律。它會從地圖中數值最低的那格出發,然後不斷走向周圍的格子中數值最低且沒被走過的格子。直到它沒有路可以走。(周圍的定義是上下左右,共4格) <br /> ### 輸入格式 單筆輸入 第一行有 兩個數字 n, m 代表地圖的大小 接著有 n 行,每行有 m 個數字,用空白隔開 每個數字都非負且小於 1000000 且都不相等 <br /> ### 輸出格式 輸出路徑上的數字總和 <br /> ### 範例:輸入 ``` 1 7 1 2 3 4 5 6 7 ``` ### 範例:正確輸出 ``` 28 ``` <br /> ## Python 程式碼 ### 寫法1,簡單直接的作法 於 ZeroJudge 測試結果,最長運算時間約為 24 ms,使用記憶體最多約為 3.9 MB,通過測試。 ```python= n, m = map(int, input().split()) # 讀取地圖y方向長度n、x方向寬度m visited = [[False]*m for _ in range(n)] # 格子被走訪過的狀態 data = [] # 格子數值資料 now, x, y = 1000000, 0, 0 # 目前格字數值,x座標,y座標 for i in range(n): # 讀取 n 行資料 tmp = list(map(int, input().split())) # 用空格分隔資料,暫存為 tmp data.append(tmp) # 將 tmp 加入 data mintmp = min(tmp) # 找出 tmp 中的最小值 if mintmp < now: # 如果 mintmp 小於 now y = i # y座標更新為i x = tmp.index(mintmp) # x座標更新為 mintmp 於 tmp 的索引值 now = mintmp # now 更新為 mintmp visited[y][x] = True # (y, x) 格子被走訪過 ans = data[y][x] # 答案 ans 設定為 data[y][x] flag = True # 繼續執行程式的狀態 flag 設定為 True while flag: val = [1000000]*4 # 週圍4格的值,預設為測資格子最大值 1000000 s0, s1, s2, s3 = False, False, False, False # 週圍4格是否可以被走訪的狀態 # 左側格子 if x-1 >= 0 and not visited[y][x-1]: val[0] = data[y][x-1] s0 = True # 右側格子 if x+1 < m and not visited[y][x+1]: val[1] = data[y][x+1] s1 = True # 上方格子 if y-1 >= 0 and not visited[y-1][x]: val[2] = data[y-1][x] s2 = True # 下方格子 if y+1 < n and not visited[y+1][x]: val[3] = data[y+1][x] s3 = True # 如果週圍4格任一格可被走訪,找出 val 最小值,更新 ans、x、y、visited if s0 or s1 or s2 or s3: minval = min(val) ans += minval idx = val.index(minval) if idx == 0: visited[y][x-1] = True x = x-1 elif idx == 1: visited[y][x+1] = True x = x+1 elif idx == 2: visited[y-1][x] = True y = y-1 elif idx == 3: visited[y+1][x] = True y = y+1 else: # 如果週圍4格任一格皆不可走訪,flag 設定為 False,終止 while 迴圈 flag = False print(ans) # 印出答案 ``` <br /> ### 寫法2,BFS,使用 list 儲存待走訪節點 於 ZeroJudge 測試結果,最長運算時間約為 24 ms,使用記憶體最多約為 3.9 MB,通過測試。 ```python= n, m = map(int, input().split()) # 地圖維度 n*m graph = [] # 地圖 now = 10000000 # 目前的最小值,預設為大於題目範圍的值 sr, sc = 0, 0 # 起點,預設為 (0, 0) for i in range(n): # 讀取 n 行資料 row = list(map(int, input().split())) # 讀取一行資料 graph.append(row) # 加到 graph imin = min(row) # 此行中的最小值 if imin < now: # 如果 imin < now now = imin # 更新 now sr, sc = i, row.index(imin) # 更新起點座標 # end of for loop que = [[sr, sc]] # 待走訪節點 head = 0 # 從 que 讀取資料的索引值 total = 0 # 加總 visit = [[False]*m for _ in range(n)] # 走訪狀態 # BFS while head < len(que): # 如果 head 還沒有出界繼續執行 r, c = que[head]; head += 1 # 從 que 讀取座標 (r, c),head 加 1 visit[r][c] = True # 已走訪 (r, c) total += graph[r][c] # 更新 total imin = 10000000 # 暫存最小值的變數 for dr, dc in ((0, 1), (-1, 0), (0, -1), (1, 0)): # 四方向檢測 nr, nc = r+dr, c+dc # 要測試的座標 (nr, nc) if nr < 0 or nr >= n or nc < 0 or nc >= m: continue # 如果 (nr, nc) 已出界,檢測下一個點 if visit[nr][nc]: continue # 如果 (nr, nc) 已走訪,檢測下一個點 if graph[nr][nc] < imin: # 如果 (nr, nc) 的值小於 imin if imin == 10000000: que.append([nr, nc]) # 如果 imin 等於 10000000,找到四方向中第一個值,[nr, nc] 加入 que else: que[-1] = [nr, nc] # 否則取代 que 最後一項 imin = graph[nr][nc] # 更新 imin # 結束四方向檢測 # end of while loop print(total) # 印出答案 ``` <br /> ### 寫法3,BFS,使用 deque 儲存待走訪節點 於 ZeroJudge 測試結果,最長運算時間約為 28 ms,使用記憶體最多約為 4.2 MB,通過測試。 ```python= from collections import deque n, m = map(int, input().split()) # 地圖維度 n*m graph = [] # 地圖 now = 10000000 # 目前的最小值,預設為大於題目範圍的值 sr, sc = 0, 0 # 起點,預設為 (0, 0) for i in range(n): # 讀取 n 行資料 row = list(map(int, input().split())) # 讀取一行資料 graph.append(row) # 加到 graph imin = min(row) # 此行中的最小值 if imin < now: # 如果 imin < now now = imin # 更新 now sr, sc = i, row.index(imin) # 更新起點座標 # end of for loop que = deque(); que.append([sr, sc]) # 待走訪節點 total = 0 # 加總 visit = [[False]*m for _ in range(n)] # 走訪狀態 # BFS while que: # 如果 que 還有資料繼續執行 r, c = que.popleft() # 從 que 最前面讀取並移除資料 visit[r][c] = True # 已走訪 (r, c) total += graph[r][c] # 更新 total imin = 10000000 # 暫存最小值的變數 for dr, dc in ((0, 1), (-1, 0), (0, -1), (1, 0)): # 四方向檢測 nr, nc = r+dr, c+dc # 要測試的座標 (nr, nc) if nr < 0 or nr >= n or nc < 0 or nc >= m: continue # 如果 (nr, nc) 已出界,檢測下一個點 if visit[nr][nc]: continue # 如果 (nr, nc) 已走訪,檢測下一個點 if graph[nr][nc] < imin: # 如果 (nr, nc) 的值小於 imin if imin == 10000000: que.append([nr, nc]) # 如果 imin 等於 10000000,找到四方向中第一個值,[nr, nc] 加入 que else: que[-1] = [nr, nc] # 否則取代 que 最後一項 imin = graph[nr][nc] # 更新 imin # 結束四方向檢測 # end of while loop print(total) # 印出答案 ``` <br /><br /> ## C++ 程式碼 ### 寫法1,簡單直接的作法 於 ZeroJudge 測試結果,最長運算時間約為 12 ms,使用記憶體最多約為 368 kB,通過測試。 ```cpp= #include <iostream> #include <algorithm> using namespace std; int main() { int n, m; // 讀取地圖y方向長度n、x方向寬度m cin >> n >> m; bool visited[n][m]; // 格子被走訪過的狀態,預設為 false for(int i=0; i<n; i++) { for(int j=0; j<m; j++) { visited[i][j] = false; } } unsigned int data[n][m]; // 格子數值資料 unsigned int now = 1000000; // 目前格字數值 int x = 0, y = 0; // x座標,y座標 for(int i=0; i<n; i++) { // 讀取 n 行資料 unsigned int tmp[m]; for(int j=0; j<m; j++) { // 用空格分隔資料,暫存為 tmp cin >> tmp[j]; data[i][j] = tmp[j]; // 將 tmp[j] 加入 data } unsigned int mintmp = *min_element(tmp, tmp+m); // 找出 tmp 中的最小值 if (mintmp < now) { // 如果 mintmp 小於 now y = i; // y座標更新為i x = find(tmp, tmp+m, mintmp) - tmp; // x座標更新為 mintmp 於 tmp 的索引值 now = mintmp; // now 更新為 mintmp } } visited[y][x] = true; // (y, x) 格子被走訪過 unsigned int ans = data[y][x]; // 答案 ans 設定為 data[y][x] bool flag = true; // 繼續執行程式的狀態 flag 設定為 True while(flag) { unsigned int val[4]; // 週圍4格的值,預設為測資格子最大值 1000000 for(size_t i=0; i<4; i++) { val[i] = 1000000; } // 週圍4格是否可以被走訪的狀態 bool s0 = false, s1 = false, s2 = false, s3 = false; // 左側格子 if (x-1 >= 0 && !visited[y][x-1]) { val[0] = data[y][x-1]; s0 = true; } // 右側格子 if (x+1 < m && !visited[y][x+1]) { val[1] = data[y][x+1]; s1 = true; } // 上方格子 if (y-1 >= 0 && !visited[y-1][x]) { val[2] = data[y-1][x]; s2 = true; } // 下方格子 if (y+1 < n && !visited[y+1][x]) { val[3] = data[y+1][x]; s3 = true; } // 如果週圍4格任一格可被走訪,找出 val 最小值,更新 ans、x、y、visited if (s0 || s1 || s2 || s3) { unsigned int minval = *min_element(val, val+4); ans += minval; unsigned int idx = find(val, val+4, minval) - val; if (idx == 0) { visited[y][x-1] = true; x = x-1; } else if (idx == 1) { visited[y][x+1] = true; x = x+1; } else if (idx == 2) { visited[y-1][x] = true; y = y-1; } else if (idx == 3) { visited[y+1][x] = true; y = y+1; } } else { // 如果週圍4格任一格皆不可走訪,flag 設定為 False,終止 while 迴圈 flag = false; } } cout << ans << endl; // 印出答案 return 0; } ``` <br /> ### 寫法2,BFS,使用 vector 儲存待走訪節點 於 ZeroJudge 測試結果,最長運算時間約為 4 ms,使用記憶體最多約為 408 kB,通過測試。 ```cpp= #include <iostream> #include <vector> #include <utility> #include <algorithm> using namespace std; int main() { ios::sync_with_stdio(0); cin.tie(0); int n, m; cin >> n >> m; // 地圖維度 n*m vector<vector<int>> graph (n, vector<int> (m, 0)); // 地圖 int now = 10000000, sr = 0, sc = 0; // 目前的最小值,預設為大於題目範圍的值;起點,預設為 (0, 0) for(int i=0; i<n; i++) { // 讀取 n 行資料 for(int j=0, d; j<m; j++) { // 讀取 m 個點 cin >> d; graph[i][j] = d; if (d < now) { // 如果 d < now now = d; // 更新 now sr = i; sc = j; // 更新起點座標 } } } vector<pair<int, int>> que = {make_pair(sr, sc)}; // 待走訪節點 int head = 0, total = 0; // 從 que 讀取資料的索引值,加總 vector<vector<bool>> visit (n, vector<bool> (m, false)); // 走訪狀態 /* BFS */ int dr[4] = {0, -1, 0, 1}, dc[4] = {1, 0, -1, 0}; // 四方向檢測用的陣列 while(head < (int)que.size()) { // 如果 head 還沒有出界繼續執行 int r = que[head].first, c = que[head].second; head++; // 從 que 讀取座標 (r, c),head 加 1 visit[r][c] = true; // 已走訪 (r, c) total += graph[r][c]; // 更新 total int imin = 10000000; // 暫存最小值的變數 for(int i=0; i<4; i++) { // 四方向檢測 int nr = r + dr[i], nc = c + dc[i]; // 要測試的座標 (nr, nc) if (nr < 0 || nr >= n || nc < 0 || nc >= m) continue; // 如果 (nr, nc) 已出界,檢測下一個點 if (visit[nr][nc]) continue; // 如果 (nr, nc) 已走訪,檢測下一個點 if (graph[nr][nc] < imin) { // 如果 (nr, nc) 的值小於 imin if (imin == 10000000) que.push_back(make_pair(nr, nc)); // 如果 imin 等於 10000000,找到四方向中第一個值,[nr, nc] 加入 que else que.back() = make_pair(nr, nc); // 否則取代 que 最後一項 imin = graph[nr][nc]; // 更新 imin } } } cout << total << "\n"; // 印出答案 return 0; } ``` <br /> ### 寫法3,BFS,使用 qeque 儲存待走訪節點 於 ZeroJudge 測試結果,最長運算時間約為 13 ms,使用記憶體最多約為 338 kB,通過測試。 ```cpp= #include <iostream> #include <vector> #include <queue> #include <utility> #include <algorithm> using namespace std; int main() { ios::sync_with_stdio(0); cin.tie(0); int n, m; cin >> n >> m; // 地圖維度 n*m vector<vector<int>> graph (n, vector<int> (m, 0)); // 地圖 int now = 10000000, sr = 0, sc = 0; // 目前的最小值,預設為大於題目範圍的值;起點,預設為 (0, 0) for(int i=0; i<n; i++) { // 讀取 n 行資料 for(int j=0, d; j<m; j++) { // 讀取 m 個點 cin >> d; graph[i][j] = d; if (d < now) { // 如果 d < now now = d; // 更新 now sr = i; sc = j; // 更新起點座標 } } } queue<pair<int, int>> que; que.push(make_pair(sr, sc)); // 待走訪節點 int total = 0; // 加總 vector<vector<bool>> visit (n, vector<bool> (m, false)); // 走訪狀態 /* BFS */ int dr[4] = {0, -1, 0, 1}, dc[4] = {1, 0, -1, 0}; // 四方向檢測用的陣列 while(!que.empty()) { // 如果 que 還有資料繼續執行 int r = que.front().first, c = que.front().second; que.pop(); // 從 que 最前面讀取並移除資料 visit[r][c] = true; // 已走訪 (r, c) total += graph[r][c]; // 更新 total int imin = 10000000; // 暫存最小值的變數 for(int i=0; i<4; i++) { // 四方向檢測 int nr = r + dr[i], nc = c + dc[i]; // 要測試的座標 (nr, nc) if (nr < 0 || nr >= n || nc < 0 || nc >= m) continue; // 如果 (nr, nc) 已出界,檢測下一個點 if (visit[nr][nc]) continue; // 如果 (nr, nc) 已走訪,檢測下一個點 if (graph[nr][nc] < imin) { // 如果 (nr, nc) 的值小於 imin if (imin == 10000000) que.push(make_pair(nr, nc)); // 如果 imin 等於 10000000,找到四方向中第一個值,[nr, nc] 加入 que else que.back() = make_pair(nr, nc); // 否則取代 que 最後一項 imin = graph[nr][nc]; // 更新 imin } } } cout << total << "\n"; // 印出答案 return 0; } ``` <br /><br /> --- ###### tags:`APCS`、`Python`、`C++`