Về cơ bản, DFS được sử dụng để tìm một chu trình trong đồ thị chứ không phải BFS. Bất kỳ lý do? Cả hai đều có thể tìm thấy nếu một nút đã được truy cập trong khi duyệt qua cây / đồ thị.
Về cơ bản, DFS được sử dụng để tìm một chu trình trong đồ thị chứ không phải BFS. Bất kỳ lý do? Cả hai đều có thể tìm thấy nếu một nút đã được truy cập trong khi duyệt qua cây / đồ thị.
Câu trả lời:
Tìm kiếm đầu tiên theo chiều sâu hiệu quả hơn về bộ nhớ so với tìm kiếm đầu tiên theo chiều rộng vì bạn có thể quay lại sớm hơn. Nó cũng dễ thực hiện hơn nếu bạn sử dụng ngăn xếp cuộc gọi nhưng điều này dựa vào đường dẫn dài nhất không làm tràn ngăn xếp.
Ngoài ra, nếu đồ thị của bạn có hướng thì bạn không chỉ phải nhớ xem bạn đã ghé thăm một nút hay chưa mà còn phải nhớ cách bạn đến đó. Nếu không, bạn có thể nghĩ rằng bạn đã tìm thấy một chu trình nhưng thực tế tất cả những gì bạn có là hai con đường riêng biệt A-> B nhưng điều đó không có nghĩa là có một con đường B-> A. Ví dụ,
Nếu bạn thực hiện BFS bắt đầu từ 0
đó, nó sẽ phát hiện có chu kỳ nhưng thực tế không có chu kỳ.
Với tìm kiếm đầu tiên theo chiều sâu, bạn có thể đánh dấu các nút là đã truy cập khi bạn đi xuống và bỏ đánh dấu chúng khi bạn quay lại. Xem nhận xét để cải thiện hiệu suất trên thuật toán này.
Đối với thuật toán tốt nhất để phát hiện các chu kỳ trong một đồ thị có hướng, bạn có thể xem xét thuật toán của Tarjan .
Một BFS có thể hợp lý nếu đồ thị không được định hướng (hãy làm khách mời của tôi khi hiển thị một thuật toán hiệu quả bằng cách sử dụng BFS sẽ báo cáo các chu kỳ trong một đồ thị có hướng!), Trong đó mỗi "cạnh chéo" xác định một chu trình. Nếu cạnh chéo là {v1, v2}
và gốc (trong cây BFS) chứa các nút r
đó, thì chu trình là r ~ v1 - v2 ~ r
( ~
là một đường dẫn, -
một cạnh duy nhất), có thể được báo cáo gần như dễ dàng như trong DFS.
Lý do duy nhất để sử dụng BFS là nếu bạn biết đồ thị (vô hướng) của bạn sẽ có đường dẫn dài và đường bao phủ nhỏ (nói cách khác, sâu và hẹp). Trong trường hợp đó, BFS sẽ yêu cầu ít bộ nhớ hơn cho hàng đợi của nó so với ngăn xếp của DFS (tất nhiên là cả hai vẫn tuyến tính).
Trong tất cả các trường hợp khác, DFS rõ ràng là người chiến thắng. Nó hoạt động trên cả đồ thị có hướng và vô hướng, và việc báo cáo các chu trình là rất đơn giản - chỉ cần nối bất kỳ cạnh sau nào với đường dẫn từ tổ tiên đến hậu duệ và bạn sẽ có được chu trình. Nói chung, tốt hơn và thiết thực hơn nhiều so với BFS cho vấn đề này.
BFS sẽ không hoạt động cho một đồ thị có hướng trong việc tìm kiếm các chu trình. Coi A-> B và A-> C-> B là các đường đi từ A đến B trong đồ thị. BFS sẽ nói rằng sau khi đi dọc theo một trong những con đường mà B được truy cập. Khi tiếp tục đi theo con đường tiếp theo, nó sẽ thông báo rằng nút B được đánh dấu đã được tìm thấy một lần nữa, do đó, một chu kỳ ở đó. Rõ ràng là không có chu kỳ ở đây.
Tôi không biết tại sao một câu hỏi cũ lại xuất hiện trong nguồn cấp dữ liệu của tôi, nhưng tất cả các câu trả lời trước đó đều tệ, vì vậy ...
DFS được sử dụng để tìm các chu trình trong đồ thị có hướng, vì nó hoạt động .
Trong DFS, mọi đỉnh đều được "thăm", trong đó việc thăm một đỉnh có nghĩa là:
Đồ thị con có thể đến được từ đỉnh đó được truy cập. Điều này bao gồm việc theo dõi tất cả các cạnh chưa được đánh dấu có thể tới được từ đỉnh đó và truy cập tất cả các đỉnh chưa được đánh dấu có thể truy cập.
Đỉnh đã hoàn thành.
Đặc điểm quan trọng là tất cả các cạnh có thể tới được từ một đỉnh đều được truy tìm trước khi kết thúc đỉnh. Đây là một tính năng của DFS, nhưng không phải BFS. Trên thực tế, đây là định nghĩa của DFS.
Do tính năng này, chúng ta biết rằng khi đỉnh đầu tiên trong một chu trình được bắt đầu:
Vì vậy, nếu có một chu trình, thì chúng ta được đảm bảo sẽ tìm thấy một cạnh của đỉnh bắt đầu nhưng chưa hoàn thành (2), và nếu chúng ta tìm thấy một cạnh như vậy, thì chúng ta được đảm bảo rằng có một chu trình (3).
Đó là lý do tại sao DFS được sử dụng để tìm các chu trình trong đồ thị có hướng.
BFS không cung cấp những đảm bảo như vậy, vì vậy nó không hoạt động. (mặc dù các thuật toán tìm kiếm chu kỳ hoàn toàn tốt bao gồm BFS hoặc tương tự như một thủ tục phụ)
Mặt khác, đồ thị vô hướng có chu trình bất cứ khi nào có hai đường đi giữa bất kỳ cặp đỉnh nào, tức là khi nó không phải là một cây. Điều này rất dễ phát hiện trong cả BFS hoặc DFS - Các cạnh được truy tìm đến các đỉnh mới tạo thành một cây và bất kỳ cạnh nào khác chỉ ra một chu trình.
Nếu bạn đặt một chu kỳ tại một vị trí ngẫu nhiên trên cây, DFS sẽ có xu hướng đạt được chu kỳ khi nó được bao phủ khoảng một nửa cây và một nửa thời gian nó đã đi qua nơi mà chu kỳ đi, và một nửa thời gian nó sẽ không ( và sẽ tìm thấy nó trung bình trong một nửa phần còn lại của cây), vì vậy nó sẽ đánh giá trung bình khoảng 0,5 * 0,5 + 0,5 * 0,75 = 0,625 của cây.
Nếu bạn đặt một chu kỳ tại một điểm ngẫu nhiên trong cây, BFS sẽ có xu hướng chỉ đạt được chu kỳ khi nó được đánh giá lớp của cây ở độ sâu đó. Do đó, bạn thường phải đánh giá các lá của cây nhị phân cân bằng, điều này thường dẫn đến việc đánh giá nhiều cây hơn. Đặc biệt, 3/4 trường hợp có ít nhất một trong hai mắt xích xuất hiện trên lá của cây, và trong những trường hợp đó bạn phải đánh giá trung bình 3/4 cây (nếu có một mắt xích) hoặc 7 / 8 trong số cây (nếu có hai), vì vậy bạn đã đạt được kỳ vọng tìm kiếm 1/2 * 3/4 + 1/4 * 7/8 = (7 + 12) / 32 = 21/32 = 0,656 ... của cây mà không cần thêm chi phí tìm kiếm cây có chu kỳ được thêm vào từ các nút lá.
Ngoài ra, DFS dễ thực hiện hơn BFS. Vì vậy, nó là một trong những để sử dụng trừ khi bạn biết điều gì đó về các chu kỳ của mình (ví dụ: các chu trình có thể nằm gần gốc mà bạn tìm kiếm, tại thời điểm đó BFS mang lại cho bạn lợi thế).
Để chứng minh rằng một đồ thị là tuần hoàn, bạn chỉ cần chứng minh nó có một chu trình (cạnh hướng về chính nó trực tiếp hoặc gián tiếp).
Trong DFS, chúng ta lấy một đỉnh tại một thời điểm và kiểm tra xem nó có chu kỳ hay không. Ngay sau khi một chu trình được tìm thấy, chúng ta có thể bỏ qua việc kiểm tra các đỉnh khác.
Trong BFS, chúng ta cần theo dõi nhiều cạnh đỉnh đồng thời và thường xuyên hơn là không tìm hiểu xem nó có chu trình hay không. Khi kích thước của đồ thị ngày càng lớn, BFS đòi hỏi nhiều không gian, tính toán và thời gian hơn so với DFS.
Nó phụ thuộc vào việc bạn đang nói về triển khai đệ quy hoặc lặp lại.
Đệ quy-DFS truy cập mỗi nút hai lần. Lặp lại-BFS truy cập mỗi nút một lần.
Nếu bạn muốn phát hiện một chu kỳ, bạn cần phải điều tra các nút cả trước và sau khi bạn thêm các phụ cận của chúng - cả khi bạn "bắt đầu" trên một nút và khi bạn "kết thúc" với một nút.
Điều này đòi hỏi nhiều công việc hơn trong Iterative-BFS vì vậy hầu hết mọi người chọn Recursive-DFS.
Lưu ý rằng một cách triển khai đơn giản của Iterative-DFS với std :: stack có cùng vấn đề với Iterative-BFS. Trong trường hợp đó, bạn cần đặt các phần tử giả vào ngăn xếp để theo dõi khi bạn "hoàn thành" công việc trên một nút.
Xem câu trả lời này để biết thêm chi tiết về cách Iterative-DFS yêu cầu công việc bổ sung để xác định thời điểm bạn "kết thúc" với một nút (được trả lời trong ngữ cảnh của TopoSort):
Sắp xếp tôpô bằng cách sử dụng DFS mà không cần đệ quy
Hy vọng rằng điều đó giải thích tại sao mọi người ủng hộ Recursive-DFS cho các vấn đề mà bạn cần xác định khi nào bạn xử lý "xong" một nút.
Bạn sẽ phải sử dụng BFS
khi bạn muốn tìm chu kỳ ngắn nhất có chứa một nút nhất định trong một đồ thị có hướng.
Nếu nút đã cho là 2, có ba chu kỳ mà nó là một phần của - [2,3,4]
, [2,3,4,5,6,7,8,9]
& [2,5,6,7,8,9]
. Ngắn nhất là[2,3,4]
Để thực hiện điều này bằng BFS, bạn phải duy trì rõ ràng lịch sử của các nút đã truy cập bằng cách sử dụng cấu trúc dữ liệu thích hợp.
Nhưng đối với tất cả các mục đích khác (ví dụ: để tìm bất kỳ con đường tuần hoàn nào hoặc để kiểm tra xem một chu trình có tồn tại hay không), DFS
là sự lựa chọn rõ ràng vì những lý do được người khác đề cập.