0Pricing
DSA Interview Prep · บทเรียน

การแทนกราฟและการตั้งค่าการท่อง

สร้างกราฟมีทิศทางและไม่มีทิศทางด้วยลิสต์เพื่อนบ้าน เริ่มต้น BFS ด้วย deque และ DFS ด้วยสแตกหรือการเรียกซ้ำ พร้อมติดตามโหนดที่เยี่ยมชม

การแทนกราฟและการตั้งค่าการท่อง เป็นบทเรียน DSA Interview Prep ฟรีบน CoddyKit นี่คือบทเรียนที่ 1 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน DSA Interview Prep และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส DSA Interview Prep มีบทเรียนทั้งหมด 4 บทเรียน

กราฟคืออะไร

กราฟ คือกลุ่มของ โหนด (จุดยอด) ที่เชื่อมต่อกันด้วย เส้นเชื่อม ต่างจากต้นไม้ กราฟสามารถมีวงจร มีหลายเส้นทางระหว่างโหนด และมีองค์ประกอบที่ไม่เชื่อมต่อกัน กราฟใช้จำลองระบบในโลกจริง เช่น เครือข่ายสังคม แผนที่ถนน ต้นไม้การพึ่งพา และลิงก์หน้าเว็บ ระบบที่ไม่ใช่เรื่องง่ายแทบทุกระบบ รวมถึงการสัมภาษณ์ด้านการออกแบบระบบและอัลกอริทึม มักเกี่ยวข้องกับกราฟ การทำความเข้าใจการแทนกราฟและการท่องกราฟจึงเป็นสิ่งจำเป็น

# Graph terminology:
# - V: set of vertices (nodes)
# - E: set of edges
# - Directed graph: edges have direction (A -> B but not B -> A)
# - Undirected graph: edges are bidirectional
# - Weighted graph: edges have costs/weights
# - Cyclic: contains at least one cycle
# - Acyclic: no cycles (DAG = Directed Acyclic Graph)
# - Connected: every node reachable from every other
# - Disconnected: multiple isolated components
print('Graph: nodes + edges, directed/undirected, weighted/unweighted')

การแทนกราฟด้วยรายการเพื่อนบ้าน

รายการเพื่อนบ้านจะจัดเก็บรายการโหนดที่อยู่ติดกันของแต่ละโหนด ในไพธอน ให้ใช้ dict ที่จับคู่โหนดแต่ละโหนดกับรายการโหนดที่อยู่ติดกัน วิธีนี้เป็นการแทนกราฟที่ใช้กันมากที่สุดในโจทย์สัมภาษณ์: ใช้หน่วยความจำ O(V + E) (มีประสิทธิภาพสำหรับกราฟกระจัดกระจาย), ใช้เวลา O(ดีกรี) ในการวนดูโหนดที่อยู่ติดกัน และใช้เวลาเฉลี่ย O(1) ในการตรวจสอบว่าโหนดเชื่อมโยงกันหรือไม่เมื่อใช้รูปแบบเซตแฮช โจทย์กราฟส่วนใหญ่บน LeetCode ใช้รูปแบบนี้

from collections import defaultdict

# Build an undirected graph
def build_undirected(edges):
    graph = defaultdict(list)
    for u, v in edges:
        graph[u].append(v)
        graph[v].append(u)  # both directions
    return graph

edges = [(0,1), (0,2), (1,3), (2,3), (3,4)]
graph = build_undirected(edges)
print(dict(graph))
# {0:[1,2], 1:[0,3], 2:[0,3], 3:[1,2,4], 4:[3]}

# Directed graph: only one direction
def build_directed(edges):
    graph = defaultdict(list)
    for u, v in edges:
        graph[u].append(v)  # only u -> v
    return graph

การแทนกราฟด้วยเมทริกซ์เพื่อนบ้าน

เมทริกซ์เพื่อนบ้านคืออาร์เรย์สองมิติขนาด V×V โดย matrix[i][j] = 1 (หรือน้ำหนักของเส้นเชื่อม) หากมีเส้นเชื่อมจาก i ไปยัง j และเป็น 0 ในกรณีอื่น วิธีนี้ตรวจสอบเส้นเชื่อมได้ในเวลา O(1) แต่ใช้หน่วยความจำ O(V²) โดยไม่ขึ้นกับจำนวนเส้นเชื่อม จึงสิ้นเปลืองสำหรับกราฟกระจัดกระจาย วิธีนี้เหมาะกว่าเมื่อกราฟมีความหนาแน่น (มีเส้นเชื่อมจำนวนมาก) หรือเมื่อจำเป็นต้องตรวจสอบการมีอยู่ของเส้นเชื่อมอย่างรวดเร็ว เช่น ในการหาเส้นทางสั้นที่สุดระหว่างทุกคู่ด้วยอัลกอริทึมฟลอยด์–วอร์แชลล์

# Adjacency matrix for 5 nodes
V = 5
matrix = [[0] * V for _ in range(V)]

edges = [(0,1), (0,2), (1,3), (2,3), (3,4)]
for u, v in edges:
    matrix[u][v] = 1
    matrix[v][u] = 1  # undirected

# Print the matrix:
for row in matrix:
    print(row)
# Neighbour check: O(1)
print('Edge 0-2:', bool(matrix[0][2]))  # True
print('Edge 0-4:', bool(matrix[0][4]))  # False

# Space: O(V^2) vs adjacency list O(V+E)
# Dense graph: matrix often better; sparse: list better

การแทนกราฟด้วยรายการเส้นเชื่อม

รายการเส้นเชื่อมเป็นการแทนกราฟที่เรียบง่ายที่สุด โดยเป็นเพียงรายการทูเพิล (ต้นทาง, ปลายทาง) ซึ่งอาจมีน้ำหนักกำกับไว้ด้วย วิธีนี้ใช้หน่วยความจำ O(E) และวนดูเส้นเชื่อมทั้งหมดได้ง่าย อย่างไรก็ตาม การค้นหาโหนดที่อยู่ติดกันต้องตรวจสอบเส้นเชื่อมทั้งหมด จึงใช้เวลา O(E) รายการเส้นเชื่อมใช้ในอัลกอริทึมกราฟที่วนผ่านเส้นเชื่อมทุกเส้นโดยตรง เช่น เบลล์แมน–ฟอร์ด (ผ่อนคลายเส้นเชื่อมทั้งหมด n-1 รอบ) และอัลกอริทึมต้นไม้ทอดคลุมต่ำสุดของครูสคัล

# Weighted edge list: (source, destination, weight)
edge_list = [
    (0, 1, 4),
    (0, 2, 1),
    (1, 3, 1),
    (2, 3, 5),
    (3, 4, 3)
]

# Useful for:
# Bellman-Ford: iterate all edges n-1 times
# Kruskal's MST: sort by weight then union-find

# Sort by weight for Kruskal:
edge_list_sorted = sorted(edge_list, key=lambda e: e[2])
print('Sorted by weight:', edge_list_sorted)

# Finding neighbours: O(E) scan -- inefficient for traversal
node_0_neighbors = [v for u, v, w in edge_list if u == 0]
print('Node 0 neighbors:', node_0_neighbors)

การเตรียม BFS: คิวและเซตที่เยี่ยมชมแล้ว

BFS (การค้นหาแบบกว้างก่อน) สำรวจกราฟทีละระดับโดยใช้ คิว องค์ประกอบสำคัญคือเซตที่เยี่ยมชมแล้วเพื่อป้องกันการเยี่ยมชมโหนดซ้ำในกราฟที่มีวัฏจักร หากไม่มีเซตที่เยี่ยมชมแล้ว BFS บนกราฟที่มีวัฏจักรจะวนซ้ำไปตลอด การเตรียมมาตรฐานคือ เริ่มต้นคิวด้วยโหนดต้นทาง ทำเครื่องหมายว่าเยี่ยมชมแล้ว จากนั้นนำโหนดออกจากคิว ประมวลผล และใส่โหนดเพื่อนบ้านที่ยังไม่เคยเยี่ยมชมลงคิวซ้ำไปเรื่อย ๆ

from collections import deque

def bfs(graph, start):
    visited = {start}        # mark source as visited
    queue = deque([start])   # initialise queue
    order = []
    while queue:
        node = queue.popleft()
        order.append(node)
        for neighbour in graph[node]:
            if neighbour not in visited:
                visited.add(neighbour)     # mark BEFORE enqueue
                queue.append(neighbour)
    return order

from collections import defaultdict
graph = defaultdict(list)
for u, v in [(0,1),(0,2),(1,3),(2,3),(3,4)]:
    graph[u].append(v); graph[v].append(u)

print(bfs(graph, 0))  # [0, 1, 2, 3, 4]

การเตรียม DFS: สแตกหรือการเรียกซ้ำ

DFS (การค้นหาแบบลึกก่อน) สำรวจไปตามแต่ละแขนงให้ไกลที่สุดก่อนย้อนกลับมา ใช้งานได้ทั้งแบบเรียกซ้ำ (โดยใช้สแตกการเรียกใช้) หรือแบบวนซ้ำ (โดยใช้สแตกที่จัดการเอง) ทั้งสองแบบต้องใช้เซตที่เยี่ยมชมแล้วสำหรับกราฟที่มีวัฏจักร แบบวนซ้ำจะใส่โหนดเพื่อนบ้านลงสแตกในลำดับย้อนกลับ เพื่อให้ตรงกับลำดับการท่องกราฟของ DFS แบบเรียกซ้ำ แม้ว่าลำดับการสำรวจอาจแตกต่างกันระหว่างการใช้งานทั้งสองแบบ

def dfs_recursive(graph, node, visited=None, order=None):
    if visited is None: visited = set(); order = []
    visited.add(node)
    order.append(node)
    for neighbour in graph[node]:
        if neighbour not in visited:
            dfs_recursive(graph, neighbour, visited, order)
    return order

def dfs_iterative(graph, start):
    visited = set()
    stack = [start]
    order = []
    while stack:
        node = stack.pop()
        if node in visited: continue
        visited.add(node)
        order.append(node)
        for neighbour in reversed(graph[node]):  # reverse for same order as recursive
            if neighbour not in visited:
                stack.append(neighbour)
    return order

print('Recursive DFS:', dfs_recursive(graph, 0))
print('Iterative DFS:', dfs_iterative(graph, 0))

ควรใช้ BFS หรือ DFS เมื่อใด

เลือกใช้ BFS เมื่อจำเป็นต้องหาเส้นทางสั้นที่สุด (มีเส้นเชื่อมน้อยที่สุด) ในกราฟที่ไม่มีน้ำหนัก หรือต้องการประมวลผลโหนดทีละระดับ เลือกใช้ DFS เมื่อต้องการสำรวจโหนดทั้งหมดที่เข้าถึงได้ ตรวจจับวัฏจักร ค้นหาองค์ประกอบเชื่อมต่อ เรียงลำดับเชิงทอพอโลยี หรือแจกแจงเส้นทางทั้งหมด ในทางปฏิบัติ: ใช้ BFS สำหรับ «สั้นที่สุด/จำนวนก้าวน้อยที่สุด» และใช้ DFS สำหรับ «การมีอยู่/การเข้าถึงได้/การแจกแจง»

# BFS use cases:
# - Shortest path in unweighted graph (fewest edges)
# - Level-order traversal
# - Word ladder (minimum transformations)
# - Clone graph

# DFS use cases:
# - Connected components (flood fill)
# - Cycle detection
# - Topological sort
# - All paths between two nodes
# - Maze solving (any path)
# - N-queens, Sudoku (backtracking)

# Both: O(V + E) time, O(V) space for visited
print('BFS: shortest hops | DFS: existence and enumeration')

กราฟจากรูปแบบข้อมูลเข้าของ LeetCode

โจทย์กราฟบน LeetCode มีรูปแบบข้อมูลเข้าหลากหลายแบบ รายการเส้นเชื่อม: [[0,1],[0,2]] — ให้สร้างรายการเพื่อนบ้าน รายการเพื่อนบ้านแบบอิงดัชนี: graph[i] คือรายการโหนดที่อยู่ติดกันของ i ตาราง/เมทริกซ์: อาร์เรย์สองมิติขนาด m×n โดยเซลล์เป็นโหนด และเซลล์ที่อยู่ติดกัน (ขึ้น/ลง/ซ้าย/ขวา) เป็นโหนดเพื่อนบ้าน โหนดที่มีโหนดลูก: คลาสแบบกำหนดเอง เช่น Node(val, neighbors) ให้จำแนกรูปแบบเหล่านี้และแปลงเป็นรายการเพื่อนบ้านเป็นขั้นตอนแรก

# Format 1: edge list -> adjacency list
def edges_to_adj(n, edges):
    graph = [[] for _ in range(n)]
    for u, v in edges:
        graph[u].append(v)
        graph[v].append(u)
    return graph

# Format 2: 2D grid -> adjacency (implicit)
# Neighbours of (r, c): (r-1,c), (r+1,c), (r,c-1), (r,c+1)
DIRS = [(-1,0),(1,0),(0,-1),(0,1)]
def grid_neighbours(grid, r, c):
    rows, cols = len(grid), len(grid[0])
    return [(r+dr, c+dc) for dr, dc in DIRS
            if 0 <= r+dr < rows and 0 <= c+dc < cols]

grid = [[1,1,0],[0,1,1],[1,0,0]]
print('Neighbours of (0,0):', grid_neighbours(grid, 0, 0))
print('Neighbours of (1,1):', grid_neighbours(grid, 1, 1))

การทำเครื่องหมายเซลล์ที่เยี่ยมชมแล้วในตาราง

สำหรับโจทย์ตาราง มีสองวิธีในการติดตามเซลล์ที่เยี่ยมชมแล้ว วิธี A: ใช้เซต visited แยกต่างหากของทูเพิล (row, col) ซึ่งใช้หน่วยความจำเพิ่มเติม O(m*n) วิธี B: แก้ไขตารางเดิมโดยตรง โดยทำเครื่องหมายเซลล์ที่เยี่ยมชมแล้วด้วยค่าพิเศษบ่งชี้ เช่น '#' หรือ 2 และคืนค่าเดิมภายหลังหากจำเป็น วิธีแก้ไขในตารางเดิมใช้หน่วยความจำเพิ่มเติม O(1) และใช้กันทั่วไปในโจทย์การเติมพื้นที่และการนับจำนวนเกาะ

def num_islands(grid):
    if not grid:
        return 0
    rows, cols = len(grid), len(grid[0])
    count = 0

    def dfs(r, c):
        if r < 0 or r >= rows or c < 0 or c >= cols:
            return
        if grid[r][c] != '1':
            return
        grid[r][c] = '#'  # mark as visited (in-place)
        dfs(r+1, c); dfs(r-1, c)
        dfs(r, c+1); dfs(r, c-1)

    for r in range(rows):
        for c in range(cols):
            if grid[r][c] == '1':
                dfs(r, c)
                count += 1
    return count

grid = [['1','1','0','0'],
        ['1','1','0','0'],
        ['0','0','1','0'],
        ['0','0','0','1']]
print(num_islands(grid))  # 3

การเริ่มต้น BFS จากหลายต้นทาง

BFS หลายต้นทางเริ่มจากหลายโหนดพร้อมกัน โดยเริ่มต้นคิวด้วยโหนดต้นทางทั้งหมดและทำเครื่องหมายว่าเยี่ยมชมแล้ว วิธีนี้ใช้ในโจทย์อย่าง «ระยะทางจาก 0 ที่ใกล้ที่สุด» «ส้มเน่า» และ «กำแพงและประตู» ซึ่งต้องการหาระยะทางสั้นที่สุดจากโหนดต้นทางใดก็ได้ BFS หลายต้นทางใช้เวลา O(V + E) เช่นเดียวกับ BFS จากต้นทางเดียว เพราะโหนดแต่ละโหนดยังคงถูกเยี่ยมชมได้มากที่สุดเพียงครั้งเดียว

from collections import deque

def rotting_oranges(grid):
    rows, cols = len(grid), len(grid[0])
    queue = deque()
    fresh = 0
    # Multi-source: all rotten oranges start at time=0
    for r in range(rows):
        for c in range(cols):
            if grid[r][c] == 2:
                queue.append((r, c, 0))  # (row, col, time)
            elif grid[r][c] == 1:
                fresh += 1
    dirs = [(0,1),(0,-1),(1,0),(-1,0)]
    time = 0
    while queue:
        r, c, t = queue.popleft()
        for dr, dc in dirs:
            nr, nc = r+dr, c+dc
            if 0<=nr<rows and 0<=nc<cols and grid[nr][nc]==1:
                grid[nr][nc] = 2  # mark rotten
                fresh -= 1
                queue.append((nr, nc, t+1))
                time = t + 1
    return time if fresh == 0 else -1

print(rotting_oranges([[2,1,1],[1,1,0],[0,1,1]]))  # 4

ความหนาแน่นของกราฟและการเลือกวิธีแทนกราฟ

การเลือกระหว่างรายการเพื่อนบ้านกับเมทริกซ์ขึ้นอยู่กับความหนาแน่นของกราฟ ซึ่งคืออัตราส่วน E/V² กราฟกระจัดกระจาย (E << V²) ได้ประโยชน์จากรายการเพื่อนบ้าน: ใช้หน่วยความจำ O(V+E) เทียบกับ O(V²) สำหรับเมทริกซ์ ส่วนกราฟหนาแน่น (E ≈ V²) ได้ประโยชน์จากเมทริกซ์เพื่อนบ้าน: ตรวจสอบเส้นเชื่อมได้ในเวลา O(1) เทียบกับ O(ดีกรี) สำหรับรายการเพื่อนบ้าน ในโจทย์สัมภาษณ์ รายการเพื่อนบ้านมักเป็นตัวเลือกที่เหมาะสมที่สุด เนื่องจากโจทย์ส่วนใหญ่เป็นกราฟกระจัดกระจาย

# Graph density comparison:
# Sparse: social network (V=1B users, avg 200 friends)
#   E = 200 * 1B = 200B << V^2 = 10^18 -> adjacency list
# Dense: complete graph (every node connected to every other)
#   E = V*(V-1)/2 ≈ V^2 -> adjacency matrix

# Interview rule of thumb:
# - Default to adjacency list (defaultdict(list))
# - Use matrix only when asked about dense graph or O(1) edge lookup
# - Grid problems: use implicit adjacency (4-directional neighbours)

print('Sparse graph (E << V^2): use adjacency list')
print('Dense graph (E ~ V^2): consider adjacency matrix')

ตรวจสอบความเข้าใจ

ทดสอบความเข้าใจแนวคิดโครงสร้างข้อมูลและอัลกอริทึม — การเตรียมตัวสัมภาษณ์เขียนโปรแกรมจากบทเรียนนี้

สรุปบทเรียน

ในบทเรียนนี้ คุณได้เรียนรู้การแทนกราฟสามรูปแบบ (รายการเพื่อนบ้าน เมทริกซ์ และรายการเส้นเชื่อม) รวมถึงวิธีเลือกใช้แต่ละแบบ การเตรียม BFS และ DFS ด้วยเซตที่เยี่ยมชมแล้วเพื่อป้องกันการวนซ้ำไม่สิ้นสุดในกราฟที่มีวัฏจักร และรูปแบบการใช้งานจริง เช่น การทำเครื่องหมายในตารางเดิมและBFS หลายต้นทาง ต่อไป เราจะประยุกต์ใช้ BFS เพื่อหาเส้นทางสั้นที่สุดและท่องกราฟทีละระดับ

คำถามที่พบบ่อย

บทเรียน “การแทนกราฟและการตั้งค่าการท่อง” ฟรีหรือไม่

ใช่ — ข้อความเต็มของ “การแทนกราฟและการตั้งค่าการท่อง” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส DSA Interview Prep ให้อัปเกรดเป็น CoddyKit PRO คอร์ส DSA Interview Prep มีบทเรียนทั้งหมด 4 บทเรียน

คุณจะเรียนรู้อะไรในบทเรียน “การแทนกราฟและการตั้งค่าการท่อง”

สร้างกราฟมีทิศทางและไม่มีทิศทางด้วยลิสต์เพื่อนบ้าน เริ่มต้น BFS ด้วย deque และ DFS ด้วยสแตกหรือการเรียกซ้ำ พร้อมติดตามโหนดที่เยี่ยมชม คุณปฏิบัติ DSA Interview Prep ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน

คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน DSA Interview Prep หรือไม่

ไม่จำเป็นต้องมีประสบการณ์มาก่อน DSA Interview Prep บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 1 จากทั้งหมด 4 บทเรียน

บทเรียน “การแทนกราฟและการตั้งค่าการท่อง” ใช้เวลานานแค่ไหน

บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย

ฉันเขียนและรันโค้ดในบทเรียน DSA Interview Prep นี้ได้ไหม

ได้ บทเรียน DSA Interview Prep ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

บทเรียนทั้งหมดในหลักสูตรนี้

  1. การแทนกราฟและการตั้งค่าการท่อง
  2. BFS: เส้นทางสั้นที่สุดและการท่องตามระดับ
  3. DFS: องค์ประกอบเชื่อมต่อและการเติมพื้นที่
  4. การตรวจจับวัฏจักรในกราฟมีทิศทางและไม่มีทิศทาง
← กลับไปที่ DSA Interview Prep