Tổng quan
Cách lưu trữ đồ thị trong bộ nhớ ảnh hưởng trực tiếp đến hiệu năng của mọi thuật toán. Có ba cách biểu diễn phổ biến, mỗi cách phù hợp cho bài toán khác nhau.
| Biểu diễn | Bộ nhớ | Kiểm tra cạnh (u,v) | Liệt kê hàng xóm u | Phù hợp khi |
|---|---|---|---|---|
| Ma trận kề | O(V²) | O(1) | O(V) | V nhỏ, đồ thị dày |
| Danh sách kề | O(V+E) | O(bậc u) | O(bậc u) | Thường dùng nhất |
| Danh sách cạnh | O(E) | O(E) | O(E) | Kruskal’s MST |
Ma trận kề (Adjacency Matrix)
Dùng mảng 2D matrix[u][v] — bằng 1 (hoặc trọng số) nếu có cạnh (u, v), bằng 0 nếu không.
def tao_ma_tran_ke(n, canh, co_huong=False):
"""
n: số đỉnh (đánh số 0 đến n-1)
canh: [(u, v)] hoặc [(u, v, trong_so)]
"""
matrix = [[0] * n for _ in range(n)]
for edge in canh:
if len(edge) == 3:
u, v, w = edge
else:
u, v, w = edge[0], edge[1], 1
matrix[u][v] = w
if not co_huong:
matrix[v][u] = w
return matrix
# Đồ thị vô hướng 4 đỉnh
canh = [(0,1), (0,2), (1,3), (2,3)]
matrix = tao_ma_tran_ke(4, canh)
for row in matrix:
print(row)
# [0, 1, 1, 0]
# [1, 0, 0, 1]
# [1, 0, 0, 1]
# [0, 1, 1, 0]
# Kiểm tra cạnh O(1)
print("Có cạnh (0,1):", matrix[0][1] != 0) # True
print("Có cạnh (0,3):", matrix[0][3] != 0) # False
Danh sách kề (Adjacency List)
Dùng dict hoặc list of list — mỗi đỉnh lưu danh sách hàng xóm. Đây là cách biểu diễn phổ biến nhất trong thi đấu lập trình.
from collections import defaultdict
def tao_ds_ke(n, canh, co_huong=False, co_trong_so=False):
"""Xây dựng danh sách kề từ danh sách cạnh."""
graph = defaultdict(list)
for edge in canh:
if co_trong_so:
u, v, w = edge
graph[u].append((v, w))
if not co_huong:
graph[v].append((u, w))
else:
u, v = edge
graph[u].append(v)
if not co_huong:
graph[v].append(u)
return graph
# Đồ thị vô hướng đơn giản
canh = [(0,1), (0,2), (1,3), (2,3)]
g = tao_ds_ke(4, canh)
print(dict(g))
# {0: [1, 2], 1: [0, 3], 2: [0, 3], 3: [1, 2]}
# Đồ thị có hướng có trọng số
canh_w = [(0,1,4), (0,2,1), (2,1,2), (1,3,1), (2,3,5)]
gw = tao_ds_ke(4, canh_w, co_huong=True, co_trong_so=True)
print(dict(gw))
# {0: [(1,4),(2,1)], 2: [(1,2),(3,5)], 1: [(3,1)]}
# Duyệt hàng xóm
for v, w in gw[0]:
print(f" 0 → {v} (trọng số {w})")
Danh sách cạnh (Edge List)
Đơn giản nhất — chỉ lưu danh sách các cạnh. Dùng khi cần xử lý tất cả cạnh tuần tự (như Kruskal’s MST).
# Đồ thị có trọng số: list of (u, v, weight)
canh = [
(0, 1, 4),
(0, 2, 1),
(2, 1, 2),
(1, 3, 1),
(2, 3, 5)
]
# Sắp xếp theo trọng số — dùng cho Kruskal's
canh_sap_xep = sorted(canh, key=lambda e: e[2])
for u, v, w in canh_sap_xep:
print(f" ({u},{v}) trọng số {w}")
# (0,2) 1
# (1,3) 1
# (2,1) 2
# (0,1) 4
# (2,3) 5
Đọc đầu vào theo định dạng thi đấu
Phần lớn bài thi đưa đồ thị theo dạng: dòng đầu là n m (số đỉnh, số cạnh), rồi m dòng mỗi dòng là một cạnh.
import sys
from collections import defaultdict
def doc_do_thi_vo_huong():
"""
Đọc đồ thị từ stdin theo định dạng:
Dòng 1: n m
Dòng 2..m+1: u v [w]
"""
input_data = """5 6
1 2
1 3
2 4
3 4
3 5
4 5""".strip().split('\n')
it = iter(input_data)
n, m = map(int, next(it).split())
graph = defaultdict(list)
for _ in range(m):
parts = list(map(int, next(it).split()))
u, v = parts[0] - 1, parts[1] - 1 # Chuyển về 0-indexed
w = parts[2] if len(parts) == 3 else 1
graph[u].append((v, w))
graph[v].append((u, w))
return n, graph
n, g = doc_do_thi_vo_huong()
print(f"{n} đỉnh")
for u in sorted(g.keys()):
print(f" {u}: {g[u]}")
Bài tập luyện tập
Dễ — Đếm số cạnh và bậc
from collections import defaultdict
def thong_ke_do_thi(n, canh):
"""In số cạnh, bậc của từng đỉnh, đỉnh bậc cao nhất."""
graph = defaultdict(list)
for u, v in canh:
graph[u].append(v)
graph[v].append(u)
print(f"Số đỉnh: {n}, Số cạnh: {len(canh)}")
bac = {i: len(graph[i]) for i in range(n)}
for i in range(n):
print(f" Đỉnh {i}: bậc {bac[i]}")
dinh_cao_nhat = max(bac, key=bac.get)
print(f"Đỉnh bậc cao nhất: {dinh_cao_nhat} (bậc {bac[dinh_cao_nhat]})")
canh = [(0,1),(0,2),(0,3),(1,2),(2,3)]
thong_ke_do_thi(4, canh)
Trung bình — Chuyển đổi giữa các dạng biểu diễn
def ma_tran_sang_ds_ke(matrix):
"""Chuyển ma trận kề → danh sách kề (có trọng số)."""
n = len(matrix)
graph = {i: [] for i in range(n)}
for u in range(n):
for v in range(n):
if matrix[u][v] != 0:
graph[u].append((v, matrix[u][v]))
return graph
def ds_ke_sang_ma_tran(n, graph):
"""Chuyển danh sách kề (có trọng số) → ma trận kề."""
matrix = [[0] * n for _ in range(n)]
for u in graph:
for v, w in graph[u]:
matrix[u][v] = w
return matrix
# Test
matrix = [
[0, 3, 0, 7],
[3, 0, 2, 0],
[0, 2, 0, 1],
[7, 0, 1, 0]
]
graph = ma_tran_sang_ds_ke(matrix)
print("Danh sách kề:", dict(graph))
matrix2 = ds_ke_sang_ma_tran(4, graph)
print("Ma trận gốc:", matrix == matrix2) # True
Khó — Bậc vào và bậc ra trong đồ thị có hướng
from collections import defaultdict
def phan_tich_co_huong(n, canh):
"""
Tính bậc vào (in-degree) và bậc ra (out-degree) cho đồ thị có hướng.
Tìm đỉnh nguồn (bậc vào = 0) và đỉnh đích (bậc ra = 0).
"""
in_deg = [0] * n
out_deg = [0] * n
for u, v in canh:
out_deg[u] += 1
in_deg[v] += 1
nguon = [i for i in range(n) if in_deg[i] == 0]
dich = [i for i in range(n) if out_deg[i] == 0]
print(f"Bậc vào: {in_deg}")
print(f"Bậc ra: {out_deg}")
print(f"Đỉnh nguồn (in=0): {nguon}")
print(f"Đỉnh đích (out=0): {dich}")
# Pipeline: 0→1, 0→2, 1→3, 2→3, 3→4
canh = [(0,1),(0,2),(1,3),(2,3),(3,4)]
phan_tich_co_huong(5, canh)
Kết luận
Trong hầu hết bài thi, danh sách kề là lựa chọn mặc định. Chỉ dùng ma trận kề khi V ≤ 500 và cần tra cạnh O(1). Dùng danh sách cạnh khi cần sắp xếp cạnh theo trọng số (Kruskal’s). Tiếp theo hãy học Sắp xếp tô pô — ứng dụng quan trọng của DFS trên DAG.
Bình luận