Khi một bài toán được mô hình hóa thành các đỉnh và các liên kết giữa chúng, câu hỏi đầu tiên thường là: làm thế nào đi qua các đỉnh một cách có hệ thống mà không bỏ sót hoặc lặp vô hạn? Từ nhu cầu đó hình thành hai chiến lược nền tảng của khoa học máy tính: duyệt theo chiều sâu (Depth-First Search - DFS) và duyệt theo chiều rộng (Breadth-First Search - BFS).
Hai thuật toán không chỉ dùng cho đồ thị. Chúng còn xuất hiện trong cây, mê cung, tìm đường, kiểm tra liên thông, phát hiện chu trình, phân tích phụ thuộc, trò chơi và nhiều bài toán tìm kiếm trạng thái.
1. Bài toán ban đầu: làm thế nào khám phá một cấu trúc liên kết?
Trong nhiều bài toán, dữ liệu không nằm theo một dãy tuyến tính. Một thành phố nối với nhiều thành phố khác, một trang web liên kết tới nhiều trang, một người trong mạng xã hội có nhiều quan hệ, hoặc một trạng thái trò chơi có thể sinh ra nhiều trạng thái tiếp theo.
Cấu trúc tự nhiên để mô hình hóa những hệ thống này là đồ thị (graph). Một đồ thị gồm tập đỉnh V và tập cạnh E:
Khi đã có đồ thị, vấn đề cơ bản xuất hiện: bắt đầu từ một đỉnh, cần khám phá các đỉnh có thể đi tới như thế nào?
Một thuật toán duyệt tốt phải đáp ứng ba yêu cầu: biết đỉnh nào đã được thăm, quyết định đỉnh nào sẽ xử lý tiếp theo và không bị mắc kẹt trong chu trình.
Hai lựa chọn tự nhiên xuất hiện. Một là đi sâu nhất có thể rồi quay lại. Hai là khám phá mọi lựa chọn gần nhất trước rồi mới tiến xa hơn. Đây chính là hai tư tưởng của DFS và BFS.
2. DFS: đi sâu trước rồi quay lui
DFS bắt đầu tại một đỉnh, chọn một đỉnh kề chưa được thăm và tiếp tục đi sâu. Khi đến một đỉnh không còn đường mới, thuật toán quay về vị trí trước đó và thử nhánh khác.
Giả sử đồ thị có cấu trúc:
B → D, E
C → F, G
Nếu luôn chọn đỉnh bên trái trước, DFS có thể duyệt:
DFS phù hợp tự nhiên với ngăn xếp (stack). Đỉnh được đưa vào sau cùng sẽ được xử lý trước. Khi viết đệ quy, ngăn xếp lời gọi của chương trình tự đảm nhiệm vai trò này.
Kết quả với thứ tự danh sách kề trên là:
Điểm quan trọng là thứ tự cụ thể có thể thay đổi nếu thứ tự các đỉnh kề thay đổi. Bản chất của DFS không phải một chuỗi cố định mà là chiến lược ưu tiên đi sâu.
3. BFS: khám phá đồ thị theo từng lớp
BFS bắt đầu tại đỉnh nguồn, trước hết thăm tất cả các đỉnh cách nguồn một cạnh. Sau đó thuật toán thăm các đỉnh cách nguồn hai cạnh, rồi ba cạnh và tiếp tục như vậy.
Với cùng đồ thị ở trên, BFS từ A cho thứ tự:
BFS cần một hàng đợi (queue). Đỉnh được thêm vào trước sẽ được xử lý trước. Đây là cơ chế bảo đảm các đỉnh ở mức gần nguồn được duyệt trước các đỉnh xa hơn.
Ở BFS, nên đánh dấu một đỉnh là đã thăm ngay khi đưa nó vào queue. Nếu chờ đến lúc lấy đỉnh ra mới đánh dấu, cùng một đỉnh có thể bị thêm vào queue nhiều lần khi có nhiều đường cùng dẫn tới nó.
4. Vì sao BFS tìm được đường đi ngắn nhất còn DFS thì không?
Đây là khác biệt quan trọng nhất khi lựa chọn thuật toán. Trên đồ thị không trọng số, BFS khám phá các đỉnh theo khoảng cách tăng dần tính bằng số cạnh.
Nếu một đỉnh t được BFS gặp lần đầu ở mức 3, điều đó có nghĩa tồn tại đường từ nguồn đến t gồm 3 cạnh và không có đường nào chỉ gồm 1 hoặc 2 cạnh. Nếu tồn tại, BFS đã phải phát hiện t ở một mức trước đó.
Vì vậy BFS có thể tính khoảng cách ngắn nhất:
DFS không có tính chất này. Nó có thể đi rất sâu vào một nhánh dài trước khi phát hiện một đường ngắn hơn nằm ở nhánh khác.
5. Độ phức tạp và sự khác biệt thực tế
Nếu đồ thị được lưu bằng danh sách kề, cả DFS và BFS đều có thể thăm mỗi đỉnh một lần và kiểm tra mỗi cạnh một số lần hữu hạn. Vì vậy:
Trong đó V là số đỉnh và E là số cạnh.
Khác biệt lớn thường nằm ở bộ nhớ. DFS cần lưu đường đi hiện tại và các nhánh chờ xử lý. BFS có thể phải giữ toàn bộ một lớp lớn của đồ thị trong queue.
| Tiêu chí | DFS | BFS |
|---|---|---|
| Chiến lược | Đi sâu trước | Đi theo từng mức |
| Cấu trúc dữ liệu | Stack hoặc đệ quy | Queue |
| Đường ngắn nhất không trọng số | Không bảo đảm | Bảo đảm |
| Tìm kiếm theo nhánh sâu | Phù hợp | Không phải ưu thế chính |
| Rủi ro bộ nhớ | Độ sâu quá lớn | Một mức có quá nhiều đỉnh |
| Độ phức tạp | O(V + E) | O(V + E) |
6. Từ duyệt đồ thị đến giải quyết bài toán thực tế
DFS đặc biệt phù hợp khi cần khám phá cấu trúc sâu hoặc cần quay lui. Một số ứng dụng điển hình gồm tìm các thành phần liên thông, kiểm tra chu trình, duyệt cây thư mục, sắp xếp topo trong đồ thị có hướng không chu trình và giải các bài toán backtracking.
Trong mê cung, DFS có thể chọn một hướng và tiếp tục cho đến ngõ cụt rồi quay lại. Nếu mục tiêu chỉ là tìm một đường đi, chiến lược này thường đơn giản và hiệu quả.
BFS lại phù hợp khi khoảng cách là yếu tố quan trọng. Nó được dùng để tìm đường ngắn nhất trong mê cung không trọng số, xác định số bước tối thiểu để chuyển từ trạng thái này sang trạng thái khác, tìm người cách một người dùng k bậc trong mạng xã hội hoặc lan truyền tín hiệu theo từng lớp.
Một cách nhìn rộng hơn là coi mỗi trạng thái như một đỉnh và mỗi hành động hợp lệ như một cạnh. Khi đó DFS và BFS trở thành công cụ tìm kiếm trên không gian trạng thái.
Nếu yêu cầu là “có đường tới đích hay không?”, cả DFS và BFS đều có thể trả lời. Nếu yêu cầu là “đường nào có ít bước nhất?”, BFS là lựa chọn tự nhiên hơn trên mê cung mà mỗi bước có cùng chi phí.
7. Chọn DFS hay BFS?
Không có thuật toán nào luôn tốt hơn. Việc lựa chọn phụ thuộc vào mục tiêu của bài toán và hình dạng của không gian tìm kiếm.
Nên ưu tiên DFS khi cần duyệt toàn bộ cấu trúc, phát hiện chu trình, thực hiện quay lui, phân tích quan hệ phụ thuộc hoặc khi lời giải có khả năng nằm sâu và không cần bảo đảm đường ngắn nhất.
Nên ưu tiên BFS khi cần khoảng cách tối thiểu theo số cạnh, tìm trạng thái gần nhất, duyệt dữ liệu theo từng mức hoặc cần biết một đỉnh cách nguồn bao nhiêu bước.
Trong cả hai trường hợp, tập visited là thành phần bắt buộc với đồ thị có chu trình. Nếu không đánh dấu đỉnh đã thăm, thuật toán có thể quay lại các đỉnh cũ vô hạn.
Kết luận
DFS và BFS xuất phát từ cùng một vấn đề cơ bản: làm thế nào khám phá có hệ thống một cấu trúc gồm nhiều trạng thái liên kết với nhau. Sự khác biệt chủ yếu nằm ở quyết định đỉnh nào được xử lý tiếp theo: DFS ưu tiên đỉnh sâu nhất đang chờ, còn BFS ưu tiên đỉnh được phát hiện sớm nhất.
Hiểu rõ stack, queue, tập visited và cách hai thuật toán mở rộng không gian tìm kiếm quan trọng hơn việc ghi nhớ code. Khi nắm được tư tưởng này, DFS và BFS trở thành nền tảng để tiếp cận các bài toán phức tạp hơn như tìm đường ngắn nhất, sắp xếp topo, phân tích liên thông, tìm kiếm trạng thái và nhiều thuật toán đồ thị nâng cao.
Nội dung gốc thuộc tác giả. Vui lòng không đăng lại toàn văn khi chưa được phép, trừ trường hợp pháp luật hoặc giấy phép áp dụng cho phép. Nội dung của bên thứ ba tuân theo quyền và giấy phép tương ứng.
