Menu
CoddyTech

Tìm kiếm theo chiều rộng (BFS)

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

Tìm kiếm theo chiều rộng (breadth-first search) duyệt đồ thị theo từng tầng. Bắt đầu từ một đỉnh nguồn, thuật toán thăm tất cả các đỉnh kề trực tiếp trước, rồi đến tất cả các đỉnh kề chưa thăm của chúng, cứ thế lan rộng ra theo từng vòng có khoảng cách tăng dần. Nhấn Phát ở trên để xem thuật toán tỏa ra từ đỉnh bắt đầu, mỗi lần một tầng.

BFS dùng hàng đợi vào trước ra trước (FIFO), chính điều này bảo đảm thứ tự duyệt theo từng tầng. Vì đến các đỉnh theo thứ tự số bước nhảy, BFS tìm được đường đi ngắn nhất (ít cạnh nhất) trên đồ thị không trọng số. Thuật toán thăm mỗi đỉnh và mỗi cạnh một lần, nên chạy trong thời gian O(V + E).

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

Tiêu chíĐộ phức tạpGhi chú
Thời gianO(V + E)Mỗi đỉnh và mỗi cạnh được thăm một lần
Không gianO(V)Hàng đợi + tập đã thăm, xấu nhất chứa mọi đỉnh
Cách duyệtTheo từng tầngĐỉnh gần nhất trước, theo từng vòng
Đường đi ngắn nhấtCó (không trọng số)Đến mỗi đỉnh bằng ít cạnh nhất

Từng bước thực hiện

BướcĐiều gì xảy ra
1Đưa đỉnh nguồn vào hàng đợi.
2Lấy đỉnh ở đầu hàng đợi ra và đánh dấu đã thăm.
3Xem từng đỉnh kề của nó.
4Đưa vào hàng đợi mọi đỉnh kề chưa được thăm hoặc chưa nằm trong hàng đợi.
5Lặp lại cho đến khi hàng đợi rỗng.

Ví dụ minh họa

Duyệt đồ thị này từ đỉnh 0, với các cạnh 0-1, 0-2, 1-3, 2-3, 2-4:

BướcĐã thămHàng đợi (biên)
Bắt đầu{}[0]
Lấy ra 0{0}[1, 2]
Lấy ra 1{0, 1}[2, 3]
Lấy ra 2{0, 1, 2}[3, 4]
Lấy ra 3{0, 1, 2, 3}[4]
Lấy ra 4{0, 1, 2, 3, 4}[] (xong)

Khi nào nên dùng BFS

Nên dùng khiTránh dùng khi
Bạn cần đường đi ngắn nhất trên đồ thị không trọng sốCác cạnh có trọng số: hãy dùng Dijkstra
Đích nhiều khả năng ở gần đỉnh nguồnĐồ thị rất rộng: hàng đợi có thể phải chứa một biên khổng lồ
Bạn muốn duyệt đồ thị theo thứ tự từng tầngBạn chỉ cần đến được một đỉnh bất kỳ, khi đó DFS tốn ít bộ nhớ hơn
Bạn đang tìm các thành phần liên thông hoặc tuyến đường ít bước nhấtBạn cần sắp xếp topo hoặc phát hiện chu trình: DFS phù hợp hơn

So sánh BFS và DFS

Cả hai đều duyệt mọi đỉnh trong O(V + E), nhưng thứ tự và cấu trúc dữ liệu khác nhau. Xem mô phỏng tìm kiếm theo chiều sâu để so sánh hai thuật toán cạnh nhau.

Khía cạnhBFSDFS
Cấu trúc dữ liệuHàng đợi (FIFO)Ngăn xếp / đệ quy
Thứ tựTheo từng tầng (gần nhất trước)Đi sâu theo một nhánh, rồi quay lui
Đường đi ngắn nhất (không trọng số)Có, ít cạnh nhấtKhông, không được bảo đảm
Bộ nhớ trên đồ thị rộngCao, biên có thể rất lớnThấp, mỗi lần một đường đi
Phù hợp nhất choÍt bước nhảy nhất, thành phần liên thôngPhát hiện chu trình, sắp xếp topo, quay lui

Một cách cài đặt Breadth-First Search 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 Breadth-First Search bằng Python

Python
1from collections import deque2
3
4def bfs(graph, start):5    visited = {start}6    queue = deque([start])7    order = []8    while queue:9        node = queue.popleft()10        order.append(node)11        for neighbor in graph[node]:12            if neighbor not in visited:13                visited.add(neighbor)  # mark on enqueue, not dequeue14                queue.append(neighbor)15    return order16
17
18graph = {19    "A": ["B", "C"],20    "B": ["D", "E"],21    "C": ["F"],22    "D": [],23    "E": ["F"],24    "F": [],25}26
27print("BFS order:", " -> ".join(bfs(graph, "A")))
Chạy code này trong Playground Python

Câu hỏi thường gặp về tìm kiếm theo chiều rộng

Độ phức tạp thời gian của BFS là bao nhiêu?
BFS chạy trong thời gian O(V + E), với V là số đỉnh và E là số cạnh, vì thuật toán thăm mỗi đỉnh một lần và xét mỗi cạnh một lần. BFS dùng O(V) bộ nhớ cho hàng đợi và tập đã thăm.
BFS có tìm được đường đi ngắn nhất không?
Có, trên đồ thị không trọng số. Vì BFS đến các đỉnh theo thứ tự số bước nhảy từ đỉnh nguồn, lần đầu tiên nó đến một đỉnh chính là theo đường đi có ít cạnh nhất. Với đồ thị có trọng số, bạn cần dùng thuật toán Dijkstra.
BFS và DFS khác nhau như thế nào?
BFS dùng hàng đợi và duyệt theo từng tầng (gần nhất trước), còn DFS dùng ngăn xếp và đi sâu theo một nhánh trước khi quay lui. BFS tìm đường đi ngắn nhất trên đồ thị không trọng số; DFS tốn ít bộ nhớ hơn trên đồ thị rộng và phù hợp để phát hiện chu trình và sắp xếp topo.
Khi nào nên dùng BFS thay cho thuật toán Dijkstra?
Dùng BFS khi mọi cạnh có cùng chi phí, vì BFS tìm đường đi ít cạnh nhất trong thời gian O(V + E) mà không cần hàng đợi ưu tiên. Thuật toán Dijkstra cần thiết khi các cạnh có trọng số khác nhau; chạy BFS thông thường trên đồ thị có trọng số chỉ cho đường đi ít bước nhất, không phải đường đi có chi phí thấp nhất.
Vì sao BFS cần tập đã thăm?

Đồ thị có thể chứa chu trình, nên nếu không có tập đã thăm, BFS sẽ đưa cùng một đỉnh vào hàng đợi nhiều lần và lặp vô hạn. Đánh dấu một đỉnh ngay khi đưa nó vào hàng đợi lần đầu (không phải khi lấy ra) còn giúp tránh việc cùng một đỉnh bị các đỉnh kề khác nhau thêm vào hàng đợi hai lần.

Nên đánh dấu đã thăm khi đưa đỉnh vào hay khi lấy đỉnh ra khỏi hàng đợi?

Đánh dấu khi đưa vào hàng đợi. Nếu đợi đến lúc lấy ra, một đỉnh có thể bị các đỉnh kề khác nhau thêm vào hàng đợi nhiều lần trước khi được xử lý, gây lãng phí bộ nhớ và thời gian. Đánh dấu khi đưa vào bảo đảm mỗi đỉnh vào hàng đợi đúng một lần.

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