Чому DFS, а не BFS для пошуку циклу в графіках


85

Переважно DFS використовується для пошуку циклу в графіках, а не BFS. Будь-які причини? Обидва можуть визначити, чи вузол вже був відвіданий під час обходу дерева / графіка.


5
У спрямованих графіках для визначення циклу можна використовувати лише DFS; але в неорієнтованих графіках можна використовувати обидва.
Hengameh

Відповіді:


73

Пошук по глибині ефективніший у пам’яті, ніж пошук по ширині, оскільки ви можете швидше повернутися назад. Це також простіше реалізувати, якщо ви використовуєте стек викликів, але це залежить від найдовшого шляху, що не переповнює стек.

Крім того, якщо ваш графік спрямований, ви повинні не просто згадати, відвідували ви вузол чи ні, але і те, як ви там потрапили. В іншому випадку ви можете подумати, що знайшли цикл, але насправді все, що у вас є, це два окремі шляхи A-> B, але це не означає, що існує шлях B-> A. Наприклад,

Якщо ви зробите BFS, починаючи з 0, він виявить, що цикл присутній, але насправді циклу немає.

За допомогою глибокого першого пошуку ви можете позначити вузли як відвідані під час спуску та зняти позначку під час повернення назад. Див. Коментарі щодо покращення продуктивності цього алгоритму.

Для кращого алгоритму виявлення циклів у спрямованому графіку ви можете розглянути алгоритм Тарджана .


3
(Ефективна пам’ять, тому що ви повертаєтеся раніше, і її легше реалізувати, оскільки ви можете просто дозволити стеку дбати про зберігання відкритого списку, а не чітко його підтримувати.)
Бурштин,

3
ІМО, це простіше лише якщо ти можеш покластися на рекурсію хвоста.
Хенк Гей,

2
"зняти позначку з них під час відмови" - на власний ризик! Це може легко призвести до поведінки O (n ^ 2), зокрема такий DFS неправильно сприймає поперечні ребра як "дерева" (краї "дерева" також можуть бути неправильно названими, оскільки вони насправді більше не будуть утворювати дерево)
Dimitris Andreou

1
@Dimitris Andreo: Ви можете використовувати три відвідані штати замість двох, щоб поліпшити ефективність. У спрямованих графіках існує різниця між "Я вже бачив цей вузол" і "Цей вузол є частиною циклу". За неорієнтованих графіків вони еквівалентні.
Марк Байєрс

Точно, вам точно потрібен третій стан (щоб зробити алгоритм лінійним), тому вам слід переглянути цю частину.
Dimitris Andreou

28
  1. DFS легше впровадити
  2. Як тільки DFS знаходить цикл, стек буде містити вузли, що утворюють цикл. Те саме не стосується BFS, тому вам потрібно виконати додаткову роботу, якщо ви хочете також надрукувати знайдений цикл. Це робить DFS набагато зручнішим.

10

BFS може бути розумним, якщо графік не спрямований (будьте моїм гостем, щоб показати ефективний алгоритм, що використовує BFS, який повідомляє про цикли в спрямованому графіку!), Де кожен "поперечний край" визначає цикл. Якщо поперечний край є {v1, v2}, а корінь (у дереві BFS), що містить ці вузли, є r, то цикл r ~ v1 - v2 ~ r( ~це шлях, -єдиний край), про який можна повідомити майже так само легко, як у DFS.

Єдиною причиною використання BFS було б, якщо ви знаєте, що у вашому (неорієнтованому) графіку будуть довгі шляхи та невеликий покрив траси (іншими словами, глибокий та вузький). У цьому випадку BFS вимагає пропорційно менше пам'яті для своєї черги, ніж стек DFS (обидва, як і раніше, лінійні).

У всіх інших випадках DFS явно перемагає. Він працює як на спрямованих, так і на ненаправлених графіках, і тривіально повідомляти про цикли - просто зчепіть будь-який задній край до шляху від предка до нащадка, і ви отримаєте цикл. Загалом, набагато краще та практичніше, ніж BFS для вирішення цієї проблеми.


4

BFS не працює для спрямованого графіка при пошуку циклів. Розглянемо A-> B та A-> C-> B як шляхи від A до B на графіку. BFS скаже, що після проходження однієї зі стежок, яку відвідують B. Продовжуючи подорож наступним шляхом, він скаже, що позначений вузол B знову знайдений, отже, існує цикл. Очевидно, що тут немає циклу.


Чи можете ви пояснити, як DFS чітко визначить, що у вашому прикладі не існує циклу. Я згоден, що циклу не існує у наведеному прикладі. Але якщо перейти від A-> B, а потім A-> C-> B, ми знайдемо що B вже відвідали, а його батьком є ​​A, а не C .. і я прочитав, що DFS виявить цикл, порівнюючи батьківський елемент вже відвіданого елемента з поточним вузлом, з якого напрямку ми перевіряємо в даний момент. am я отримую DFS неправильним або що?
розбивач

Все, що ви показали тут, - це те, що конкретна реалізація не працює, не те, що це неможливо з BFS. Насправді, це можливо, хоча це займає більше роботи і простір.
Чорнослив

@Prune: Усі потоки (я думаю) тут намагаються довести, що bfs не працює для виявлення циклів. Якщо ви знаєте, як протиставити доказ, ви повинні надати доказ. Просто сказати, що зусиль більше, звичайно недостатньо
Адітя Раман,

Оскільки алгоритм поданий у зв’язаних публікаціях, я не вважаю за доцільне повторювати контур тут.
Чорнослив

Я не міг знайти жодних пов’язаних публікацій, отже, попросив того самого. Я погоджуюсь з Вашим твердженням щодо можливості bfs і щойно замислювався над реалізацією. Дякую за підказку :)
Адітя Раман,

3

Не знаю, чому таке старе запитання з’явилось у моїй стрічці, але всі попередні відповіді погані, тому ...

DFS використовується для пошуку циклів у спрямованих графіках, оскільки він працює .

У DFS кожна вершина "відвідується", де відвідування вершини означає:

  1. Вершина запущена
  2. Відвідується підграф, доступний із цієї вершини. Сюди входить відстеження всіх непростежених ребер, до яких доступна ця вершина, і відвідування всіх недосяжних невідвіданих вершин.

  3. Вершина закінчена.

Найважливішою особливістю є те, що всі ребра, до яких можна дійти з вершини, простежуються до закінчення вершини. Це особливість DFS, але не BFS. Насправді це визначення DFS.

Через цю особливість ми знаємо, що коли запускається перша вершина в циклі:

  1. Жоден з ребер у циклі не простежений. Ми це знаємо, оскільки дістатися до них можна лише з іншої вершини циклу, і ми говоримо про першу вершину, яка починається.
  2. Усі непростежені ребра, до яких можна дійти з цієї вершини, будуть простежені до її закінчення, і це включає всі ребра в циклі, оскільки жоден з них ще не простежений. Отже, якщо існує цикл, ми знайдемо ребро назад до першої вершини після його запуску, але до його закінчення; і
  3. Оскільки всі ребра, які простежуються, доступні з кожної запущеної, але незакінченої вершини, знаходження ребра до такої вершини завжди вказує на цикл.

Отже, якщо існує цикл, то ми гарантовано знайдемо ребро до запущеної, але незакінченої вершини (2), а якщо ми знайдемо таке ребро, то ми гарантовано маємо цикл (3).

Ось чому DFS використовується для пошуку циклів у спрямованих графіках.

BFS не надає таких гарантій, тому просто не працює. (незважаючи на цілком хороші алгоритми пошуку циклів, які включають BFS або подібні як підпроцедуру)

З іншого боку, ненаправлений графік має цикл, коли між будь-якою парою вершин є два шляхи, тобто коли це не дерево. Це легко виявити під час BFS або DFS - ребра, що простежуються до нових вершин, утворюють дерево, а будь-яке інше ребро вказує на цикл.


Дійсно, це найбільш (може, і єдина) відповідь, пов’язана тут, детально описуючи фактичні причини.
plasmacel

2

Якщо розмістити цикл у випадковому місці на дереві, 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 швидше". Це повністю залежить від вхідних даних, і жоден ввід не є більш поширеним, ніж інший у цьому випадку.
IVlad

@Vlad - Цифри не є магічними. Вони є засобами, заявляються як такі, і їх майже нетривіально розрахувати, враховуючи припущення, які я виклав. Якщо наближення середнім значенням є поганим наближенням, це буде вагомою критикою. (І я прямо зазначив, що якщо ви можете зробити припущення щодо структури, відповідь може змінитися.)
Рекс Керр,

цифри магічні, бо вони нічого не означають. Ви взяли випадок, коли DFS працює краще, і екстраполювали ці результати на загальний випадок. Ваші твердження необгрунтовані: "DFS, як правило, потрапляє в цикл, коли він охоплює близько половини дерева": доведіть це. Не кажучи вже про те, що ви не можете говорити про цикли в дереві. Дерево не має циклу за визначенням. Я просто не розумію, у чому ваша суть. DFS буде йти в один бік, поки не потрапить у глухий кут, тому ви не можете знати, яку частину ГРАФУ (НЕ дерева) він буде досліджувати в середньому. Ви щойно вибрали випадковий випадок, який нічого не доводить.
IVlad

@Vlad - Усі нециклічні повністю зв’язані неорієнтовані графіки є деревами (некоренованими неорієнтованими). Я мав на увазі "графік, який був би деревом, збереженим для одного фальшивого посилання". Можливо, це не головна програма для алгоритму - можливо, ви хочете знайти цикли в якомусь заплутаному графіку, який має дуже багато посилань, що робить його не деревом. Але якщо воно деревоподібне, усереднене по всіх графіках, будь-який вузол однаково ймовірно буде джерелом згаданого фальшивого посилання, що робить очікуване покриття дерева 50% при натисканні на посилання. Тому я погоджуюсь, що приклад, можливо, не був репрезентативним. Але математика повинна бути дріб’язковою.
Рекс Керр,

1

Щоб довести, що графік циклічний, вам просто потрібно довести, що він має один цикл (край, спрямований на себе прямо чи опосередковано).

У DFS ми беремо по одній вершині за раз і перевіряємо, чи має вона цикл. Як тільки цикл знайдений, ми можемо опустити перевірку інших вершин.

У BFS нам потрібно відстежувати багато ребер вершин одночасно і частіше в кінці ви дізнаєтесь, чи є у нього цикл. У міру збільшення розміру графіка BFS вимагає більше місця, обчислень та часу порівняно з DFS.


0

Це як би залежить, якщо ви говорите про рекурсивні або ітеративні реалізації.

Рекурсивний DFS відвідує кожен вузол двічі. Iterative-BFS відвідує кожен вузол один раз.

Якщо ви хочете виявити цикл, вам слід дослідити вузли як до, так і після додавання їх суміжностей - як коли ви «запускаєте» на вузлі, так і коли «закінчуєте» вузлом.

Це вимагає більшої роботи в Iterative-BFS, тому більшість людей обирають Recursive-DFS.

Зверніть увагу, що проста реалізація Iterative-DFS, скажімо, зі std :: stack, має ту ж проблему, що і Iterative-BFS. У цьому випадку вам потрібно помістити фіктивні елементи в стек для відстеження, коли ви "закінчите" роботу над вузлом.

Дивіться цю відповідь, щоб отримати докладнішу інформацію про те, як Iterative-DFS вимагає додаткової роботи, щоб визначити, коли ви "закінчите" з вузлом (відповідь в контексті TopoSort):

Топологічне сортування за допомогою DFS без рекурсії

Сподіваємось, це пояснює, чому люди віддають перевагу Recursive-DFS для проблем, де вам потрібно визначити, коли ви "закінчите" обробку вузла.


Це абсолютно неправильно, оскільки неважливо, використовуєте ви рекурсію чи усуваєте рекурсію шляхом ітерації. Ви можете реалізувати ітераційний DFS, який відвідує кожен вузол двічі, як і ви можете реалізувати рекурсивний варіант, який відвідує кожен вузол лише один раз.
plasmacel

0

Вам доведеться використовувати, BFSколи ви хочете знайти найкоротший цикл, що містить даний вузол, у спрямованому графіку.

Наприклад:введіть тут опис зображення

Якщо даний вузол дорівнює 2, є три цикли, де він є частиною - [2,3,4], [2,3,4,5,6,7,8,9]&[2,5,6,7,8,9] . Найкоротший є[2,3,4]

Для реалізації цього за допомогою BFS ви повинні явно вести історію відвіданих вузлів, використовуючи належні структури даних.

Але для всіх інших цілей (наприклад: знайти будь-який циклічний шлях або перевірити, чи існує цикл чи ні), DFSце чіткий вибір з причин, згаданих іншими.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.