Переважно DFS використовується для пошуку циклу в графіках, а не BFS. Будь-які причини? Обидва можуть визначити, чи вузол вже був відвіданий під час обходу дерева / графіка.
Переважно DFS використовується для пошуку циклу в графіках, а не BFS. Будь-які причини? Обидва можуть визначити, чи вузол вже був відвіданий під час обходу дерева / графіка.
Відповіді:
Пошук по глибині ефективніший у пам’яті, ніж пошук по ширині, оскільки ви можете швидше повернутися назад. Це також простіше реалізувати, якщо ви використовуєте стек викликів, але це залежить від найдовшого шляху, що не переповнює стек.
Крім того, якщо ваш графік спрямований, ви повинні не просто згадати, відвідували ви вузол чи ні, але і те, як ви там потрапили. В іншому випадку ви можете подумати, що знайшли цикл, але насправді все, що у вас є, це два окремі шляхи A-> B, але це не означає, що існує шлях B-> A. Наприклад,
Якщо ви зробите BFS, починаючи з 0
, він виявить, що цикл присутній, але насправді циклу немає.
За допомогою глибокого першого пошуку ви можете позначити вузли як відвідані під час спуску та зняти позначку під час повернення назад. Див. Коментарі щодо покращення продуктивності цього алгоритму.
Для кращого алгоритму виявлення циклів у спрямованому графіку ви можете розглянути алгоритм Тарджана .
BFS може бути розумним, якщо графік не спрямований (будьте моїм гостем, щоб показати ефективний алгоритм, що використовує BFS, який повідомляє про цикли в спрямованому графіку!), Де кожен "поперечний край" визначає цикл. Якщо поперечний край є {v1, v2}
, а корінь (у дереві BFS), що містить ці вузли, є r
, то цикл r ~ v1 - v2 ~ r
( ~
це шлях, -
єдиний край), про який можна повідомити майже так само легко, як у DFS.
Єдиною причиною використання BFS було б, якщо ви знаєте, що у вашому (неорієнтованому) графіку будуть довгі шляхи та невеликий покрив траси (іншими словами, глибокий та вузький). У цьому випадку BFS вимагає пропорційно менше пам'яті для своєї черги, ніж стек DFS (обидва, як і раніше, лінійні).
У всіх інших випадках DFS явно перемагає. Він працює як на спрямованих, так і на ненаправлених графіках, і тривіально повідомляти про цикли - просто зчепіть будь-який задній край до шляху від предка до нащадка, і ви отримаєте цикл. Загалом, набагато краще та практичніше, ніж BFS для вирішення цієї проблеми.
BFS не працює для спрямованого графіка при пошуку циклів. Розглянемо A-> B та A-> C-> B як шляхи від A до B на графіку. BFS скаже, що після проходження однієї зі стежок, яку відвідують B. Продовжуючи подорож наступним шляхом, він скаже, що позначений вузол B знову знайдений, отже, існує цикл. Очевидно, що тут немає циклу.
Не знаю, чому таке старе запитання з’явилось у моїй стрічці, але всі попередні відповіді погані, тому ...
DFS використовується для пошуку циклів у спрямованих графіках, оскільки він працює .
У DFS кожна вершина "відвідується", де відвідування вершини означає:
Відвідується підграф, доступний із цієї вершини. Сюди входить відстеження всіх непростежених ребер, до яких доступна ця вершина, і відвідування всіх недосяжних невідвіданих вершин.
Вершина закінчена.
Найважливішою особливістю є те, що всі ребра, до яких можна дійти з вершини, простежуються до закінчення вершини. Це особливість DFS, але не BFS. Насправді це визначення DFS.
Через цю особливість ми знаємо, що коли запускається перша вершина в циклі:
Отже, якщо існує цикл, то ми гарантовано знайдемо ребро до запущеної, але незакінченої вершини (2), а якщо ми знайдемо таке ребро, то ми гарантовано маємо цикл (3).
Ось чому DFS використовується для пошуку циклів у спрямованих графіках.
BFS не надає таких гарантій, тому просто не працює. (незважаючи на цілком хороші алгоритми пошуку циклів, які включають BFS або подібні як підпроцедуру)
З іншого боку, ненаправлений графік має цикл, коли між будь-якою парою вершин є два шляхи, тобто коли це не дерево. Це легко виявити під час BFS або DFS - ребра, що простежуються до нових вершин, утворюють дерево, а будь-яке інше ребро вказує на цикл.
Якщо розмістити цикл у випадковому місці на дереві, DFS схильний потрапляти в цикл, коли він охоплює близько половини дерева, і половину часу, який він вже пройде, куди йде цикл, і половину часу цього не буде ( і знайде його в середньому в половині решти дерева), тому він оцінить в середньому приблизно 0,5 * 0,5 + 0,5 * 0,75 = 0,625 дерева.
Якщо ви розміщуєте цикл у випадковому місці на дереві, BFS схильний потрапляти в цикл лише тоді, коли він оцінює шар дерева на такій глибині. Таким чином, зазвичай вам доводиться оцінювати листя двійкового дерева балансу, що, як правило, призводить до оцінки більшої частини дерева. Зокрема, 3/4 часу принаймні одне з двох посилань з'являється в листі дерева, і в цих випадках вам доведеться оцінити в середньому 3/4 дерева (якщо є одне посилання) або 7 / 8 дерева (якщо їх два), тож ви вже очікуєте на пошук 1/2 * 3/4 + 1/4 * 7/8 = (7 + 12) / 32 = 21/32 = 0,656 ... дерева, навіть не додаючи витрат на пошук дерева з циклом, доданим від вузлів листа.
Крім того, DFS легше реалізувати, ніж BFS. Отже, його слід використовувати, якщо ви не знаєте чогось про свої цикли (наприклад, цикли, ймовірно, будуть біля кореня, з якого ви шукаєте, і в цей момент BFS дає вам перевагу).
Щоб довести, що графік циклічний, вам просто потрібно довести, що він має один цикл (край, спрямований на себе прямо чи опосередковано).
У DFS ми беремо по одній вершині за раз і перевіряємо, чи має вона цикл. Як тільки цикл знайдений, ми можемо опустити перевірку інших вершин.
У BFS нам потрібно відстежувати багато ребер вершин одночасно і частіше в кінці ви дізнаєтесь, чи є у нього цикл. У міру збільшення розміру графіка BFS вимагає більше місця, обчислень та часу порівняно з DFS.
Це як би залежить, якщо ви говорите про рекурсивні або ітеративні реалізації.
Рекурсивний DFS відвідує кожен вузол двічі. Iterative-BFS відвідує кожен вузол один раз.
Якщо ви хочете виявити цикл, вам слід дослідити вузли як до, так і після додавання їх суміжностей - як коли ви «запускаєте» на вузлі, так і коли «закінчуєте» вузлом.
Це вимагає більшої роботи в Iterative-BFS, тому більшість людей обирають Recursive-DFS.
Зверніть увагу, що проста реалізація Iterative-DFS, скажімо, зі std :: stack, має ту ж проблему, що і Iterative-BFS. У цьому випадку вам потрібно помістити фіктивні елементи в стек для відстеження, коли ви "закінчите" роботу над вузлом.
Дивіться цю відповідь, щоб отримати докладнішу інформацію про те, як Iterative-DFS вимагає додаткової роботи, щоб визначити, коли ви "закінчите" з вузлом (відповідь в контексті TopoSort):
Топологічне сортування за допомогою DFS без рекурсії
Сподіваємось, це пояснює, чому люди віддають перевагу Recursive-DFS для проблем, де вам потрібно визначити, коли ви "закінчите" обробку вузла.
Вам доведеться використовувати, BFS
коли ви хочете знайти найкоротший цикл, що містить даний вузол, у спрямованому графіку.
Якщо даний вузол дорівнює 2, є три цикли, де він є частиною - [2,3,4]
, [2,3,4,5,6,7,8,9]
&[2,5,6,7,8,9]
. Найкоротший є[2,3,4]
Для реалізації цього за допомогою BFS ви повинні явно вести історію відвіданих вузлів, використовуючи належні структури даних.
Але для всіх інших цілей (наприклад: знайти будь-який циклічний шлях або перевірити, чи існує цикл чи ні), DFS
це чіткий вибір з причин, згаданих іншими.