Tại sao lại là DFS chứ không phải BFS để tìm chu trình trong đồ thị


85

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ị.


5
Trong đồ thị có hướng, chỉ DFS có thể được sử dụng để phát hiện một chu trình; nhưng trong đồ thị vô hướng có thể được sử dụng.
Hengameh

Câu trả lời:


73

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ịhướng, bạn có thể xem xét thuật toán của Tarjan .


3
(Bộ nhớ hiệu quả vì bạn quay trở lại sớm hơn và dễ thực hiện hơn vì bạn có thể chỉ để ngăn xếp lưu trữ danh sách mở thay vì phải duy trì nó một cách rõ ràng.)
Amber

3
IMO, nó chỉ dễ dàng hơn nếu bạn có thể dựa vào đệ quy đuôi.
Hank Gay

2
"bỏ đánh dấu chúng khi bạn quay ngược" - nguy hiểm của riêng bạn! Điều này có thể dễ dàng dẫn đến hành vi O (n ^ 2), cụ thể là một DFS như vậy sẽ hiểu nhầm các cạnh chéo là các cạnh "cây" (các cạnh "cây" cũng sẽ là một từ nhầm lẫn vì chúng sẽ không thực sự tạo thành một cây nữa)
Dimitris Andreou

1
@Dimitris Andreo: Bạn có thể sử dụng ba trạng thái đã truy cập thay vì hai trạng thái để cải thiện hiệu suất. Với đồ thị có hướng, có sự khác biệt giữa "Tôi đã thấy nút này trước đây" và "Nút này là một phần của vòng lặp". Với đồ thị vô hướng chúng tương đương nhau.
Mark Byers

Chính xác, bạn chắc chắn cần một trạng thái thứ ba (để làm cho thuật toán tuyến tính), vì vậy bạn nên xem xét sửa đổi phần đó.
Dimitris Andreou

28
  1. DFS dễ thực hiện hơn
  2. Khi DFS tìm thấy một chu trình, ngăn xếp sẽ chứa các nút tạo thành chu trình. Điều này cũng không đúng với BFS, vì vậy bạn cần phải làm thêm nếu bạn muốn in chu trình đã tìm thấy. Điều này làm cho DFS thuận tiện hơn rất nhiều.

10

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.


4

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.


Bạn có thể giải thích cách DFS xác định rõ ràng rằng chu trình đó không tồn tại trong ví dụ của bạn không. Tôi đồng ý rằng chu trình không tồn tại trong ví dụ được cung cấp. Nhưng nếu chúng ta đi từ A-> B rồi đến A-> C-> B, chúng ta sẽ tìm thấy rằng B đã được truy cập và cha của nó là A không phải C..và tôi đọc rằng DFS sẽ phát hiện chu kỳ bằng cách so sánh cha của phần tử đã được truy cập với nút hiện tại mà chúng tôi đang kiểm tra tại thời điểm này. Tôi có nhận sai DFS không hay gì?
người đập bể

Tất cả những gì bạn đã chỉ ra ở đây là triển khai cụ thể này không hoạt động, không phải là không thể với BFS. Trên thực tế, điều đó là hoàn toàn có thể, mặc dù cần nhiều công việc và không gian hơn.
Tỉa

@Prune: Tất cả các chủ đề (tôi nghĩ) ở đây đang cố gắng chứng minh rằng bfs sẽ không hoạt động để phát hiện các chu kỳ. Nếu bạn biết cách chống lại chứng minh, bạn nên đưa ra bằng chứng. Đơn giản chỉ cần nói rằng những nỗ lực lớn hơn sẽ không đủ
Aditya Raman

Vì thuật toán được đưa ra trong các bài đăng được liên kết, tôi cảm thấy không thích hợp để lặp lại dàn bài ở đây.
Tỉa

Tôi không thể tìm thấy bất kỳ bài đăng được liên kết nào, do đó yêu cầu giống nhau. Tôi đồng ý với quan điểm của bạn về khả năng của bfs và vừa nghĩ về việc triển khai. Cảm ơn vì mẹo :)
Aditya Raman

3

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à:

  1. Đỉnh được bắt đầu
  2. Đồ 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.

  3. Đỉ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:

  1. Không có cạnh nào trong chu trình đã được truy tìm. Chúng tôi biết điều này, bởi vì bạn chỉ có thể đến chúng từ một đỉnh khác trong chu trình, và chúng ta đang nói về đỉnh đầu tiên được bắt đầu.
  2. Tất cả các cạnh chưa được đánh giá có thể đạt được từ đỉnh đó sẽ được truy tìm trước khi nó kết thúc và bao gồm tất cả các cạnh trong chu trình, vì chưa có cạnh nào trong số chúng được truy tìm. Do đó, nếu có một chu trình, chúng ta sẽ tìm thấy một cạnh quay trở lại đỉnh đầu tiên sau khi nó được bắt đầu, nhưng trước khi nó kết thúc; và
  3. Vì tất cả các cạnh được truy tìm đều có thể đạt được từ mọi đỉnh bắt đầu nhưng chưa hoàn thành, việc tìm kiếm một cạnh của một đỉnh như vậy luôn chỉ ra một chu trình.

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.


Thật vậy, đây là câu trả lời liên quan nhất (có thể là duy nhất) ở đây, dựa trên lý do thực tế.
plasmacel

2

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ế).


Rất nhiều con số kỳ diệu ở đó. Tôi không đồng ý với các đối số "DFS là nhanh hơn". Nó phụ thuộc hoàn toàn vào đầu vào và không có đầu vào nào phổ biến hơn đầu vào khác trong trường hợp này.
IVlad

@Vlad - Những con số không phải là ma thuật. Chúng là các phương tiện, được phát biểu như vậy, và hầu như rất nhỏ để tính toán dựa trên các giả định tôi đã nêu. Nếu xấp xỉ trung bình là một xấp xỉ xấu, đó sẽ là một lời chỉ trích xác đáng. (Và tôi tuyên bố một cách rõ ràng rằng nếu bạn có thể làm cho các giả định về cấu trúc, câu trả lời có thể thay đổi.)
Rex Kerr

những con số kỳ diệu bởi vì chúng không có ý nghĩa gì. Bạn đã lấy một trường hợp DFS làm tốt hơn và ngoại suy các kết quả đó cho trường hợp chung. Tuyên bố của bạn là vô căn cứ: "DFS sẽ có xu hướng đạt chu kỳ khi nó được che phủ khoảng một nửa cây": hãy chứng minh điều đó. Chưa kể rằng bạn không thể nói về các chu kỳ trong một cái cây. Một cây không có chu kỳ theo định nghĩa. Tôi chỉ không thấy điểm của bạn là gì. DFS sẽ đi theo một chiều cho đến khi nó đi vào ngõ cụt, vì vậy bạn không có cách nào biết được trung bình nó sẽ khám phá bao nhiêu ĐỒ HỌA (KHÔNG phải cây). Bạn vừa chọn một trường hợp ngẫu nhiên không chứng minh được gì.
IVlad

@Vlad - Tất cả các đồ thị vô hướng được kết nối đầy đủ không theo chu trình đều là cây (vô hướng chưa được lấy gốc). Ý tôi là "một biểu đồ sẽ là một cây tiết kiệm cho một liên kết giả". Có lẽ đây không phải là ứng dụng chính của thuật toán - có thể bạn muốn tìm các chu trình trong một đồ thị rối nào đó có rất nhiều liên kết khiến nó không phải là một cây. Nhưng nếu nó ở dạng cây, được tính trung bình trên tất cả các biểu đồ, thì bất kỳ nút nào cũng có khả năng là nguồn của liên kết giả nói trên, điều này làm cho độ bao phủ cây dự kiến ​​là 50% khi liên kết được truy cập. Vì vậy, tôi chấp nhận rằng ví dụ có thể không mang tính đại diện. Nhưng toán học phải là tầm thường.
Rex Kerr

1

Để 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.


0

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.


Điều này hoàn toàn sai, vì bạn sử dụng đệ quy hay loại bỏ đệ quy bằng cách lặp lại không quan trọng. Bạn có thể triển khai DFS lặp lại truy cập mỗi nút hai lần, giống như bạn có thể triển khai một biến thể đệ quy chỉ truy cập mỗi nút một lần.
plasmacel

0

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.

Ví dụ:nhập mô tả hình ảnh ở đây

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), DFSlà sự lựa chọn rõ ràng vì những lý do được người khác đề cập.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.