BFS 與 DFS:圖形搜尋演算法

圖形搜尋是路徑規劃的基礎。在學習 A* 之前,我們必須先掌握兩種最基本的搜尋演算法:

  • BFS (廣度優先搜尋):一層一層往外探索,保證找到最短路徑(未加權圖)
  • DFS (深度優先搜尋):一條路走到黑,再回頭探索其他分支

BFS:廣度優先搜尋

BFS 的概念就像「在水面上丟一顆石頭,漣漪一層一層往外擴散」。

BFS 的運作流程

1. 從起點開始,標記為「已訪問」
2. 將起點加入佇列 (Queue)
3. 當佇列不為空時:
   a. 取出佇列前端元素
   b. 訪問該元素的所有未訪問鄰居
   c. 將鄰居標記為已訪問並加入佇列
4. 重複直到找到目標或佇列為空

實作 BFS

from collections import deque

def bfs(graph, start, goal=None):
    """
    廣度優先搜尋
    graph: dict — 鄰接表表示的圖形
    start: 起點
    goal: 目標(如果為 None,則訪問所有節點)
    """
    visited = set()
    queue = deque([start])
    visited.add(start)
    parent = {start: None}  # 記錄路徑
    
    while queue:
        node = queue.popleft()
        print(f"訪問: {node}", end=" → " if node != goal else "\n")
        
        if node == goal:
            print(f"找到目標 {goal}!")
            # 重建路徑
            path = []
            while node is not None:
                path.append(node)
                node = parent[node]
            return list(reversed(path))
        
        for neighbor in graph.get(node, []):
            if neighbor not in visited:
                visited.add(neighbor)
                parent[neighbor] = node
                queue.append(neighbor)
    
    return None  # 找不到目標

# 測試:迷宮地圖
graph = {
    'A': ['B', 'C'],
    'B': ['A', 'D', 'E'],
    'C': ['A', 'F'],
    'D': ['B'],
    'E': ['B', 'F'],
    'F': ['C', 'E', 'G'],
    'G': ['F']
}

path = bfs(graph, 'A', 'G')
print(f"BFS 最短路徑: {' → '.join(path)}")

BFS 的應用

  1. 迷宮求解:保證找到最短出口
  2. 社群網路「幾度分隔」:LinkedIn 的「一度/二度人脈」
  3. 網路廣播:P2P 網路的資料擴散
  4. 垃圾回收 (GC):Java 與 Python 的標記清除演算法

DFS:深度優先搜尋

DFS 的概念就像「在迷宮中沿著右手邊的牆一直走」。

DFS 的運作流程

1. 從起點開始,標記為「已訪問」
2. 將起點加入堆疊 (Stack)
3. 當堆疊不為空時:
   a. 取出堆疊頂端元素
   b. 如果該元素有未訪問的鄰居,選擇一個並加入堆疊
   c. 如果沒有未訪問的鄰居,回溯(彈出)
4. 重複直到找到目標或堆疊為空

實作 DFS

def dfs(graph, start, goal=None):
    """深度優先搜尋(使用堆疊)"""
    visited = set()
    stack = [start]
    parent = {start: None}
    
    while stack:
        node = stack.pop()
        
        if node not in visited:
            visited.add(node)
            print(f"訪問: {node}", end=" → ")
            
            if node == goal:
                print(f"\n找到目標 {goal}!")
                path = []
                while node is not None:
                    path.append(node)
                    node = parent[node]
                return list(reversed(path))
            
            for neighbor in reversed(graph.get(node, [])):
                if neighbor not in visited:
                    parent[neighbor] = node
                    stack.append(neighbor)
    
    print()
    return None

path = dfs(graph, 'A', 'G')
print(f"DFS 找到的路徑: {' → '.join(path)}")

DFS 的應用

  1. 拓撲排序:編譯器決定程式碼編譯順序
  2. 連通分量檢測:圖片分割、社群偵測
  3. 迷宮生成:隨機 DFS 是經典的迷宮生成演算法
  4. 語法分析樹:編譯器的 AST 遍歷

BFS vs DFS 比較

| 特性 | BFS | DFS | |------|-----|-----| | 資料結構 | 佇列 (Queue) | 堆疊 (Stack) | | 最短路徑 | ✅ 保證(未加權圖) | ❌ 不保證 | | 記憶體使用 | O(寬度),可能很大 | O(深度),通常較小 | | 完整度 | ✅ 一定找到解 | ✅ 一定找到解(有限圖) | | 最佳適用 | 找到最短解 | 記憶體受限、需要遍歷全部 |

實戰:迷宮生成與求解

import random

# === 使用隨機 DFS 生成迷宮 ===
def generate_maze(width, height):
    """使用 Recursive Backtracker 生成迷宮"""
    # 初始化迷宮(全部牆壁)
    maze = [[1] * (2 * width + 1) for _ in range(2 * height + 1)]
    
    # 所有格子
    grid = [(x, y) for y in range(height) for x in range(width)]
    
    # DFS 生成
    start = (0, 0)
    visited = set()
    stack = [start]
    visited.add(start)
    
    # 將起點設為通道
    maze[1][1] = 0
    
    while stack:
        x, y = stack[-1]
        neighbors = []
        for dx, dy in [(1, 0), (-1, 0), (0, 1), (0, -1)]:
            nx, ny = x + dx, y + dy
            if 0 <= nx < width and 0 <= ny < height and (nx, ny) not in visited:
                neighbors.append((nx, ny, dx, dy))
        
        if neighbors:
            nx, ny, dx, dy = random.choice(neighbors)
            visited.add((nx, ny))
            # 打通牆壁
            maze[2 * y + 1 + dy][2 * x + 1 + dx] = 0
            maze[2 * ny + 1][2 * nx + 1] = 0
            stack.append((nx, ny))
        else:
            stack.pop()
    
    return maze

def print_maze(maze):
    for row in maze:
        print(''.join('██' if c else '  ' for c in row))

maze = generate_maze(10, 10)
print("=== 隨機生成迷宮(10x10)===")
print_maze(maze)

# === 使用 BFS 求解迷宮 ===
def solve_maze_bfs(maze):
    """用 BFS 求解迷宮"""
    height = len(maze)
    width = len(maze[0])
    
    start = (1, 1)
    goal = (width - 2, height - 2)
    
    queue = deque([start])
    visited = {start}
    parent = {start: None}
    
    while queue:
        x, y = queue.popleft()
        
        if (x, y) == goal:
            path = []
            while (x, y) is not None:
                path.append((x, y))
                (x, y) = parent[(x, y)]
            return list(reversed(path))
        
        for dx, dy in [(1, 0), (-1, 0), (0, 1), (0, -1)]:
            nx, ny = x + dx, y + dy
            if (0 <= nx < width and 0 <= ny < height 
                and maze[ny][nx] == 0 and (nx, ny) not in visited):
                visited.add((nx, ny))
                parent[(nx, ny)] = (x, y)
                queue.append((nx, ny))
    
    return None

path = solve_maze_bfs(maze)
print(f"\nBFS 找到最短路徑: {len(path)} 步" if path else "找不到出口!")

使用 Vibe Coding 實作 BFS/DFS

🔥 【BFS/DFS 詠唱範例】 「請幫我實作台灣高鐵路線查詢系統: 1. 使用所有高鐵站點建立圖形。 2. 實作 BFS 找出「最少站點數」的路線。 3. 實作 DFS 找出所有可能路線。 4. 比較 BFS 與 DFS 找到的路徑差異。 5. 用 Matplotlib 畫出搜尋過程(動畫展示)。」

本日總結

在本章中,你學到了:

  1. BFS (廣度優先搜尋):使用佇列,保證最短路徑
  2. DFS (深度優先搜尋):使用堆疊,遍歷所有可能
  3. BFS vs DFS 比較:何時該用哪一種
  4. 迷宮生成:使用隨機 DFS 生成迷宮
  5. 迷宮求解:使用 BFS 找到最短路徑

下一章,我們將學習 Dijkstra 演算法——BFS 的加權圖版本!

會員專屬免費教學

本章節為註冊會員專屬的免費開放內容!請先登入或註冊會員,即可立即解鎖閱讀。

立即登入 / 註冊