ในยุคนี้ที่อะไร ๆ ก็ต้องเร็ว ต้องฉลาดและต้องหาให้เจอภายในพริบตา อัลกอริธึมการค้นหา (Search Algorithms) ก็กลายเป็นพระเอกที่อยู่เบื้องหลังหลายสิ่งรอบตัวเราโดยไม่รู้ตัว ไม่ว่าจะเป็นแอปนำทางที่พาเราเลี่ยงรถติดเช่น Google Maps เกมแข่งรถที่บอทรู้ทางลัดเช่น Forza Horizon

บทความนี้จะพาคุณไปรู้จักกับสองตัวท็อปของวงการค้นหาอย่าง Breadth-First Search (BFS) และ Depth-First Search (DFS) ผ่านตัวอย่างในภาษา Python ที่อ่านง่าย ใช้งานได้จริงกัน

Breadth-First Search (BFS) คืออะไร?

BFS เป็นอัลกอริธึมที่ค้นหาจากจุดเริ่มต้นไปยังจุดหมายโดยไล่ระดับความลึกทีละชั้น เหมือนกับการเดินทางไปทีละจุดใกล้ๆ ก่อน แล้วค่อยขยายวงกว้างออกไป

ลองนึกถึงการค้นหาหมายเลขโทรศัพท์ของเพื่อนในสมุดโทรศัพท์ คุณอาจไล่ดูรายชื่อจากตัวอักษร A ไป B แล้วไป C ซึ่งคล้ายกับแนวคิดของ BFS หรือหากคุณอยู่ในห้องสมุดแล้วต้องการหาหนังสือที่ต้องการ คุณอาจเริ่มจากการดูหมวดหมู่หลักก่อน แล้วค่อยลงลึกไปยังชั้นที่เฉพาะเจาะจงมากขึ้น

ถ้างั้นเราลองมาดูตัวอย่างโค้ดกัน

ขั้นตอนที่1 การกำหนดกราฟในรูปแบบ Dictionary

graph = {
    'A': ['B', 'C'],
    'B': ['D', 'E'],
    'C': ['F'],
    'D': [],
    'E': ['F'],
    'F': []
}

ขั้นตอนที่2 ลองใช้งาน BFS

โค้ดนี้เป็นจะการทำงานของอัลกอริธึมการค้นหาความกว้างโดยเริ่มจากโหนดต้นทางแล้วทำการเยี่ยมชมทุกโหนดในกราฟที่เชื่อมต่อกับโหนดต้นทางทีละขั้นตอน โดยใช้คิว (queue) ในการจัดลำดับการเยี่ยมชม

visited = []
queue = []

def bfs(visited, graph, node):
  visited.append(node)
  queue.append(node)

  while queue:
    m = queue.pop(0) 
    print (m, end = " ") 

    for neighbour in graph[m]:
      if neighbour not in visited:
        visited.append(neighbour)
        queue.append(neighbour)

# Driver Code
print("BFS")
bfs(visited, graph, 'A')

โดยโค้ดแต่ละส่วนจะมีการทำงานดังนี้ :

visited → ใช้เก็บว่าเคยไปเยี่ยมชมโหนดไหนแล้วบ้าง
queue → คิวที่ใช้เก็บโหนดที่ต้องไปต่อ
pop(0) → เป็นการดึงโหนดแรกเข้ามาทำงาน

ทีนี้ลองมาดูผลลัพธ์กันจากโค้ดเบื้องต้นกัน :

Image description

ผลลัพธ์จากการรันฟังก์ชัน BFS จะเป็นการแสดงโหนดที่ถูกเยี่ยมชมในลำดับที่กว้างที่สุดเป็นชั้น ๆ ชั้นหนึ่งแล้วไปยังระดับชั้นถัดไป โดยเริ่มจากโหนด ('A') และเยี่ยมชมโหนดที่เชื่อมต่อกับโหนดนั้นๆ ทีละระดับโดยใช้คิวในการจัดลำดับการเยี่ยมชมหรือก็คือมาก่อนได้ก่อนนั่นเอง
การทำงานของ BFS คือ:
เริ่มจาก 'A' และไปเยี่ยมชม 'B' และ 'C' (ที่อยู่ในระดับเดียวกันหรือชั้นเดียวกัน)
จากนั้นไปเยี่ยมชม 'D', 'E', และ 'F' ตามลำดับ

Depth-First Search (DFS) คืออะไร?

DFS เป็นอัลกอริธึมที่ค้นหาข้อมูลโดย เจาะลึก ลงไปตามเส้นทางหนึ่งให้สุดก่อน แล้วจึงย้อนกลับมาและลองเส้นทางถัดไป เหมือนกับการเดินเข้าไปในเขาวงกตโดยเลือกทางใดทางหนึ่ง แล้วเดินให้สุดทางก่อน หากไปต่อไม่ได้จึงย้อนกลับและลองทางอื่น

ลองนึกถึงคุณกำลังหาเพื่อนในอาคารที่มีหลายชั้นและแต่ละชั้นมีหลายห้อง
แทนที่จะเดินดูทีละห้องในแต่ละชั้นคุณอาจเลือกเดินเข้าไปในห้องแรก แล้วดูให้ลึกว่ามีห้องในห้องย่อยมั้ย ถ้ามีก็เข้าไปต่อเรื่อย ๆ จนสุดทางก่อน ค่อยย้อนกลับมาเริ่มใหม่กับห้องถัดไป นี่คือแนวคิดของ DFS

หรืออีกตัวอย่างง่าย ๆ ถ้าหากคุณกำลังหาไฟล์ในโฟลเดอร์คอมพิวเตอร์ DFS จะให้คุณเข้าไปในโฟลเดอร์แรกก่อน แล้วเปิดโฟลเดอร์ย่อยในนั้น ไล่ลงไปเรื่อย ๆ จนไม่มีอะไรจะเปิดอีกแล้ว ถึงจะย้อนกลับมาดูโฟลเดอร์อื่นในระดับเดียวกัน

เราลองมาดูตัวอย่างโค้ดกัน

ขั้นตอนที่3 ลองใช้งาน DFS

โค้ดนี้เป็นการทำงานของอัลกอริธึมการค้นหาความลึกโดยเริ่มจากโหนดต้นทางแล้วทำการเยี่ยมชมโหนดที่เชื่อมต่อกับโหนดต้นทางจนกว่าจะถึงโหนดที่ไม่มีโหนดต่อเชื่อม จากนั้นจะย้อนกลับและเยี่ยมชมโหนดที่ยังไม่ถูกเยี่ยมชม โดยใช้การเรียกฟังก์ชันซ้ำในการทำงาน

visited = set()

def dfs(visited, graph, node):
    if node not in visited:
        print (node)
        visited.add(node)
        for neighbour in graph[node]:
            dfs(visited, graph, neighbour)

print("DFS :")
dfs(visited, graph, 'A')

โดยโค้ดแต่ละส่วนจะมีการทำงานดังนี้ :

visited → ใช้เก็บว่าเคยไปเยี่ยมชมโหนดไหนแล้วบ้าง
dfs() → เป็นการค้นหาลึกเข้าไปเรื่อย ๆ จนสุดทางก่อนค่อยย้อนกลับ

ทีนี้ลองมาดูผลลัพธ์กันจากโค้ดเบื้องต้นกัน :

Image description

ผลลัพธ์ที่ได้จากการรันโค้ด DFS (Depth-First Search) คือการแสดงลำดับของโหนดที่ถูกเยี่ยมชม โดย DFS จะเลือก “ดำดิ่ง” ไปยังโหนดที่ลึกที่สุดก่อน แล้วค่อยย้อนกลับมาเก็บโหนดที่ยังตกค้างอยู่ภายหลัง

มาดูขั้นตอนแบบง่าย ๆ:

เริ่มจากโหนด 'A' → DFS จะเยี่ยมชม 'A' และมุ่งหน้าไปยัง 'B' เพราะเป็นโหนดที่เชื่อมต่อกับ 'A' เป็นลำดับแรก

จาก 'B' → ไปต่อที่ 'D' (ลึกสุดก่อนตามแนวทางของ DFS)

พอถึง 'D' → ไม่มีโหนดให้ไปต่อแล้ว → จึง “ย้อนกลับ” มาที่ 'B' แล้วไปยัง 'E'

เยี่ยมชม 'E' เสร็จ → กลับไปที่ 'A' อีกครั้ง

ตอนนี้ยังเหลือ 'C' ที่เชื่อมกับ 'A' → DFS เลยไปต่อที่ 'C'

จาก 'C' → เดินหน้าต่อไปยัง 'F'

สุดท้าย เยี่ยมชม 'F' เสร็จ → ย้อนกลับขึ้นมาเรื่อย ๆ จนครบทุกโหนดที่เชื่อมต่อ

ถ้าพูดง่าย ๆ คือ DFS จะเป็นการ “ลุยลึกก่อนคิดทีหลัง” คือไปให้สุดทางก่อน แล้วค่อยย้อนกลับมาเช็กว่าเรายังตกหล่นอะไรไว้บ้าง

ทีนี้เรามาดูตัวอย่างอื่นอีกกันโดยใช้เกมหนีซอมบี้กันบ้างดีกว่า

โดยก่อนเริ่มให้เรารันโค้ดนี้ลงไปใน Google Colab

import time
from collections import deque
import os

maze = [
    [0, 0, 0, 0, 0],
    [0, 1, 1, 1, 0],
    [0, 0, 0, 1, 0],
    [0, 1, 0, 0, 0],
    [0, 0, 0, 1, 0]
]

player_pos = (0, 0)
zombie_pos = (4, 4)

def bfs(start, goal):
    queue = deque([[start]])
    visited = set()

    while queue:
        path = queue.popleft()
        x, y = path[-1]
        if (x, y) == goal:
            return path

        for dx, dy in [(-1,0),(1,0),(0,-1),(0,1)]:
            nx, ny = x + dx, y + dy
            if 0 <= nx < 5 and 0 <= ny < 5 and maze[nx][ny] == 0 and (nx, ny) not in visited:
                queue.append(path + [(nx, ny)])
                visited.add((nx, ny))
    return []

def print_maze(player, zombie):
    os.system('cls' if os.name == 'nt' else 'clear')
    for i in range(5):
        row = ""
        for j in range(5):
            if (i, j) == player:
                row += "P "
            elif (i, j) == zombie:
                row += "Z "
            elif maze[i][j] == 1:
                row += "# "
            else:
                row += ". "
        print(row)
    print()

while True:
    print_maze(player_pos, zombie_pos)

    if zombie_pos == player_pos:
        print("ผีจับคุณได้แล้ว! Game Over.")
        break

    move = input("WASD เพื่อขยับ: ").upper()
    dx, dy = 0, 0
    if move == "W": dx, dy = -1, 0
    elif move == "S": dx, dy = 1, 0
    elif move == "A": dx, dy = 0, -1
    elif move == "D": dx, dy = 0, 1

    new_x = player_pos[0] + dx
    new_y = player_pos[1] + dy

    if 0 <= new_x < 5 and 0 <= new_y < 5 and maze[new_x][new_y] == 0:
        player_pos = (new_x, new_y)

    path = bfs(zombie_pos, player_pos)
    if len(path) > 1:
        zombie_pos = path[1]

    time.sleep(0.1)

โดยโค้ดแต่ละส่วนจะมีการทำงานดังนี้ :

visited → ใช้เก็บว่าเคยไปเยี่ยมชมโหนดไหนแล้วบ้าง
queue → คิวที่ใช้เก็บโหนดที่ต้องไปต่อ
pop(0) → เป็นการดึงโหนดแรกเข้ามาทำงาน
bfs → ฟังก์ชันที่ใช้ค้นหาเส้นทางจากตำแหน่งผีไปยังตำแหน่งผู้เล่น โดยจะเริ่มต้นจากตำแหน่งของผีและตรวจสอบไปในทิศทางต่าง ๆ
print_maze → ฟังก์ชันที่ใช้แสดงผลแผนที่ทุกครั้งหลังจากที่ผู้เล่นหรือผีทำการขยับ

โดยหลักการเกมนี้มีง่าย ๆ คือ :

  1. ซอมบี้จะเดินตามทางสั้นที่สุดโดยใช้ BFS

  2. ผู้เล่นควบคุมด้วยปุ่ม W A S D

  3. ผีจะเดินทีละ 1 ช่องทุกตา สลับกับเรา

เมื่อเราลองรันแล้วจะได้หน้าตา Output ออกมาเป็นแบบนี้

Image description

โดยให้เราเดินโดยใช้ปุ่มที่ตั้งค่าในโค้ดซึ่งในตัวอย่างนี้ใช้เป็น (W, A, S, D) โดยให้เดินไปทีละตาสลับกับซอมบี้ที่จะเดินมาหาเรา

Image description

จนเมื่อซอมบี้ใช้หลักการ BFS จนหาผู้เล่นเจอก็จะปรากฏดังนี้

Image description

สรุปบทความนี้

จากบทความนี้ เราได้ทำความรู้จักกับอัลกอริธึมการค้นหายอดนิยมทั้งสองแบบคือ Breadth-First Search (BFS) และ Depth-First Search (DFS) พร้อมตัวอย่างโค้ดภาษา Python ที่สามารถนำไปทดลองและประยุกต์ใช้ได้จริง ไม่ว่าจะเป็นการยกตัวอย่างของโหนดต่าง ๆ และการหาเส้นทางในแผนที่เกม แม้อัลกอริธึมเหล่านี้จะดูเรียบง่าย แต่ก็สามารถต่อยอดไปสู่การแก้ปัญหาที่ซับซ้อนมากขึ้นได้มากขึ้นในอนาคต เช่น เกมที่ใช้ AI ในการประมวลผลเส้นทาง, ระบบนำทาง, หรือแม้แต่การวิเคราะห์เครือข่ายสังคมออนไลน์ ลองนำความรู้ที่ได้ไปประยุกต์ใช้ดูนะครับ

ใครลองทำอะไรเจ๋ง ๆ มา แชร์กันได้เลยครับ 😄

Reference :
https://favtutor.com/blogs/breadth-first-search-python
https://favtutor.com/blogs/depth-first-search-python