Menu
CoddyTech

Sắp xếp topo

Cập nhật lần cuối

Sắp xếp topo (topological sort) của một đồ thị có hướng không chu trình (DAG) là một cách xếp các đỉnh thành hàng sao cho với mọi cạnh u → v, u luôn đứng trước v. Nó trả lời những câu hỏi như "nên chạy các tác vụ này theo thứ tự nào để mọi điều kiện tiên quyết đều xong trước?" Nhấn Phát ở trên để xem thuật toán Kahn lần lượt tách các đỉnh ra theo một thứ tự hợp lệ.

Thuật toán Kahn liên tục lấy một đỉnh không còn cạnh đi vào (bậc vào 0), thêm nó vào thứ tự, rồi xóa các cạnh đi ra của nó, việc này có thể tạo thêm những đỉnh mới có bậc vào 0. Thuật toán chỉ chạy được trên DAG: nếu có chu trình, một số đỉnh sẽ không bao giờ đạt bậc vào 0 và không tồn tại thứ tự hợp lệ. Thuật toán chạy trong thời gian O(V + E).

Độ phức tạp thời gian và không gian

Đại lượngĐộ phức tạpGhi chú
Thời gianO(V + E)Mỗi đỉnh được đưa ra một lần, mỗi cạnh bị xóa một lần
Không gianO(V)Bậc vào của các đỉnh, tập sẵn sàng và thứ tự kết quả
Yêu cầuMột DAGĐồ thị có chu trình không có thứ tự topo
Kết quảKhông duy nhấtCó thể có nhiều thứ tự hợp lệ

Từng bước (thuật toán Kahn)

BướcĐiều gì xảy ra
1Tính bậc vào (số cạnh đi vào) của mọi đỉnh.
2Gom mọi đỉnh có bậc vào 0 vào một tập sẵn sàng.
3Lấy một đỉnh sẵn sàng, thêm nó vào thứ tự kết quả.
4Giảm bậc vào của từng đỉnh kế tiếp của nó.
5Đỉnh kế tiếp nào giảm về bậc vào 0 sẽ được thêm vào tập sẵn sàng.
6Lặp lại cho đến khi tập sẵn sàng rỗng.

Ví dụ minh họa

Sắp xếp DAG có các cạnh A→C, B→C, C→D, C→E, D→F, E→F (bậc vào ban đầu A:0 B:0 C:2 D:1 E:1 F:2):

BướcTập sẵn sàngThứ tựThao tác
0{A, B}[]A và B có bậc vào 0 ngay từ đầu, nên cả hai đều sẵn sàng.
1{B}[A]Đưa ra A; cạnh A→C của nó giảm bậc vào của C từ 2 xuống 1.
2{C}[A, B]Đưa ra B; cạnh B→C của nó giảm C từ 1 xuống 0, nên C trở nên sẵn sàng.
3{D, E}[A, B, C]Đưa ra C; các cạnh C→D và C→E giảm D và E về 0, cả hai trở nên sẵn sàng.
4{E}[A, B, C, D]Đưa ra D; cạnh D→F của nó giảm bậc vào của F từ 2 xuống 1.
5{F}[A, B, C, D, E]Đưa ra E; cạnh E→F của nó giảm F từ 1 xuống 0, nên F trở nên sẵn sàng.
6{}[A, B, C, D, E, F]Đưa ra F; tập sẵn sàng rỗng và cả 6 đỉnh đã có thứ tự: xong.

Khi nào nên dùng sắp xếp topo

Nên dùng khiTránh dùng khi
Bạn cần một thứ tự tôn trọng các phụ thuộc (các bước build, cài đặt package, môn học tiên quyết).Đồ thị vô hướng: thứ tự topo chỉ được định nghĩa cho đồ thị có hướng.
Đồ thị là một DAG và bạn muốn một thứ tự tuyến tính hợp lệ bất kỳ.Đồ thị có thể chứa chu trình mà bạn vẫn cần một thứ tự toàn phần (điều đó không tồn tại).
Bạn muốn phát hiện chu trình với chi phí thấp: sắp xếp topo thất bại chứng tỏ có chu trình.Bạn cần thứ tự ngắn nhất hoặc tối ưu theo một trọng số nào đó; sắp xếp topo thông thường bỏ qua trọng số.
Bạn sẽ xử lý thứ tự này một lần trong O(V + E).Các cạnh thay đổi liên tục và bạn phải sắp xếp lại sau mỗi lần cập nhật, khi đó một cấu trúc cập nhật tăng dần phù hợp hơn.

Code Topological Sort

Một cách cài đặt Topological Sort gọn gàng, chạy được ngay bằng Python, JavaScript, Java, C++, C. Chọn một ngôn ngữ, sao chép code hoặc mở sẵn trong Coddy Playground.

Code Topological Sort bằng Python

Python
1from collections import deque2
3
4def topological_sort(graph):5    # Kahn's algorithm: repeatedly remove nodes with no incoming edges6    in_degree = {node: 0 for node in graph}7    for node in graph:8        for neighbor in graph[node]:9            in_degree[neighbor] += 110    queue = deque(node for node in graph if in_degree[node] == 0)11    order = []12    while queue:13        node = queue.popleft()14        order.append(node)15        for neighbor in graph[node]:16            in_degree[neighbor] -= 117            if in_degree[neighbor] == 0:18                queue.append(neighbor)19    if len(order) != len(graph):20        raise ValueError("Graph has a cycle, no topological order")21    return order22
23
24graph = {25    "shirt": ["tie", "jacket"],26    "tie": ["jacket"],27    "pants": ["shoes", "jacket"],28    "socks": ["shoes"],29    "shoes": [],30    "jacket": [],31}32
33print(" -> ".join(topological_sort(graph)))
Chạy code này trong Playground Python

Câu hỏi thường gặp về sắp xếp topo

Sắp xếp topo dùng để làm gì?

Nó sắp xếp các tác vụ sao cho mọi phụ thuộc luôn đứng trước thứ cần đến nó. Các ứng dụng thực tế gồm hệ thống build và trình quản lý package (biên dịch phần phụ thuộc trước), xếp lịch môn học có môn tiên quyết, và thứ tự tính công thức trong bảng tính.

Độ phức tạp thời gian của sắp xếp topo là bao nhiêu?
Cả thuật toán Kahn và cách dựa trên DFS đều chạy trong thời gian O(V + E), vì mỗi đỉnh được xử lý một lần và mỗi cạnh được xét một lần. Chúng dùng thêm O(V) bộ nhớ.
Tại sao sắp xếp topo cần một DAG?
Một chu trình có hướng tạo ra mâu thuẫn: nếu a phải đứng trước b và b phải đứng trước a, không có thứ tự tuyến tính nào thỏa mãn cả hai. Vì vậy thứ tự topo tồn tại khi và chỉ khi đồ thị là đồ thị có hướng không chu trình. Thuật toán Kahn phát hiện chu trình khi nó kết thúc mà chưa đưa ra hết mọi đỉnh.
Thuật toán Kahn và sắp xếp topo bằng DFS khác nhau như thế nào?
Thuật toán Kahn dùng vòng lặp và giống BFS: nó liên tục xóa các đỉnh có bậc vào 0, khiến việc phát hiện chu trình và xác định thứ tự hoàn toàn tường minh. Cách dùng DFS thăm các đỉnh bằng đệ quy và đưa mỗi đỉnh vào đầu thứ tự khi lời gọi đệ quy của nó kết thúc, tạo ra thứ tự ngược với thời điểm kết thúc. Cả hai đều là O(V + E); Kahn tránh được đệ quy sâu và tự nhiên cho biết tập sẵn sàng, còn DFS thường viết ngắn hơn.
Khi nào nên dùng sắp xếp topo thay vì sắp xếp thông thường?
Hãy dùng sắp xếp topo khi thứ tự được xác định bởi các phụ thuộc giữa các phần tử chứ không phải bởi một khóa so sánh được. Một thuật toán sắp xếp so sánh thông thường như mergesort O(n log n) sắp xếp theo giá trị; sắp xếp topo sắp xếp theo các cạnh "phải đứng trước", và khác với sắp xếp so sánh, nó có thể cho ra nhiều đáp án hợp lệ với cùng một đầu vào.
Kết quả của sắp xếp topo có duy nhất không?

Thường là không. Mỗi khi có hai đỉnh trở lên cùng sẵn sàng (bậc vào 0), bạn có thể đưa chúng ra theo thứ tự bất kỳ, nên phần lớn DAG có nhiều thứ tự topo hợp lệ. Thứ tự chỉ duy nhất khi ở mỗi bước có đúng một đỉnh sẵn sàng, điều xảy ra khi DAG tạo thành một chuỗi duy nhất (một đường đi Hamilton).

Hình minh họa các ngôn ngữ lập trình của Coddy

Thành thạo thuật toán cùng Coddy

Bắt đầu