Як працює пошук за шириною першого пошуку при пошуку найкоротшого шляху?


129

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

Наприклад, скажімо, у мене такий графік:

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

І моя мета - дістатися від А до Е (всі ребра не зважені).

Я починаю з А, тому що це моє походження. Я стояв у черзі A, після чого негайно вийшов з черги на A і досліджував його. Це дає B і D, тому що A пов'язаний з B і D. Я таким чином чергую як B, так і D.

Я відмовляюсь від B і досліджую його, і виявляю, що це призводить до A (вже дослідженого), і C, тому я чергую C. Я потім відкидаю D, і виявляю, що це призводить до E, моєї мети. Потім я відмовляюся від C і виявляю, що це також веде до E, моєї мети.

Я логічно знаю, що найшвидший шлях - A-> D-> E, але я не впевнений, як саме допомагає пошук на перших порах - як я повинен записувати контури таким чином, що коли закінчу, я можу проаналізувати результати та побачити що найкоротший шлях - A-> D-> E?

Також зауважте, що я фактично не використовую дерево, тому немає "батьківських" вузлів, лише дітей.


2
"Крім того, зауважте, що я фактично не використовую дерево, тому немає" батьківських "вузлів, лише дітей" - ну вам, очевидно, доведеться десь зберігати батьків. Для DFS ви робите це опосередковано через стек викликів, для BFS - це потрібно робити це явно. Я нічого не можу з цим зробити, я боюся :)
Voo

Відповіді:


85

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

Алгоритм Dijkstra адаптує BFS, щоб він знаходив найкоротші шляхи з одним джерелом.

Для того, щоб отримати найкоротший шлях від початку до вузла, вам потрібно підтримувати два елементи для кожного вузла в графі: його поточну найкоротшу відстань та попередній вузол у найкоротшому шляху. Спочатку всі відстані встановлюються до нескінченності, а всі попередники встановлюються порожніми. У вашому прикладі ви встановлюєте відстань A на нуль, а потім продовжуєте BFS. На кожному кроці ви перевіряєте, чи зможете ви покращити відстань нащадка, тобто відстань від початку до попередника плюс довжина краю, який ви досліджуєте, менше, ніж найкраща поточна відстань для відповідного вузла. Якщо ви можете покращити відстань, встановіть новий найкоротший шлях і запам’ятайте попередника, через який цей шлях був придбаний. Коли черга BFS порожня, виберіть вузол (у вашому прикладі це E) і переведіть своїх попередників назад до початку.

Якщо це звучить трохи заплутано, у Вікіпедії є приємний розділ псевдокоду щодо цієї теми.


Дякую! Я раніше читав псевдокод, але не зміг його зрозуміти, ваше пояснення змусило мене натиснути
Джейк

46
Я хотів би зробити наступну примітку для людей, які в майбутньому дивляться на цю публікацію: Якщо краї не зважені, немає необхідності зберігати "поточну найкоротшу відстань" для кожного вузла. Все, що потрібно зберегти, - це батьківський для кожного виявленого вузла. Таким чином, поки ви вивчаєте вузол і привласнюєте всіх його наступників, просто встановіть батьківський вузол цих вузлів на той вузол, який ви вивчаєте (це подвоїться як маркування їх "виявленим"). Якщо цей батьківський вказівник є NUL / nil / None для будь-якого заданого вузла це означає, що він ще не був виявлений BFS, або він є самим джерелом / кореневим вузлом.
Шашанк

@Shashank Якщо ми не підтримуємо відстань, то як би ми знали найкоротшу відстань, будь ласка, поясніть більше.
Gaurav Sehgal

12
@gauravsehgal Цей коментар стосується графіків з невагомими краями. BFS знайде найкоротшу відстань просто завдяки своїй радіальній схемі пошуку, яка враховує вузли в порядку їх відстані від початкової точки.
Шашанк

13
Порада для читачів коментаря @ Shashank: невагомі та рівномірно зважені (наприклад, всі ребра мають вагу = 5) еквівалентні.
TWiStErRob

54

Як було зазначено вище, BFS може тільки бути використана , щоб знайти найкоротший шлях в графі , якщо:

  1. Немає петель

  2. Всі краї мають однакову чи відсутність ваги.

Щоб знайти найкоротший шлях, все, що вам потрібно зробити, - це почати з джерела та здійснити перший попереду пошук і зупинитись, коли знайдете свій Вузол призначення. Єдине додаткове, що вам потрібно зробити - це масив попереднього [n], який буде зберігати попередній вузол для кожного відвідуваного вузла. Попереднє джерело може бути нульовим.

Щоб надрукувати шлях, простий цикл через попередній масив [] від джерела до досягнення пункту призначення та друкування вузлів. DFS також може бути використаний для пошуку найкоротшого шляху в графіку за аналогічних умов.

Однак, якщо графік є більш складним, містить зважені ребра та петлі, тоді нам потрібна більш досконала версія BFS, тобто алгоритм Дійкстри.


1
Dijkstra if no
-ve vaights

Чи працює BFS для пошуку всіх найкоротших шляхів між двома вузлами?
Марія Інес Парнісарі

35
@javaProgrammer, це неправильно. BFS також може бути використаний для пошуку найкоротшого шляху в незваженому циклічному графіку. Якщо графік не зважений, то BFS можна застосувати для SP незалежно від наявності циклів.
Андрій Кайгородов

3
To print the path, simple loop through the previous[] array from source till you reach destination.Але попереднє джерело недійсне. Я думаю, що ти мав на увазі, це backtrace from destination using the previous array until you reach the source.
aandis

2
Чому, на вашу думку, DFS працюватиме за аналогічних умов? Чи не можливо для DFS пройти наївний обхідний шлях, щоб дістатися з вузлів start-> end, і тому дати вам шлях, який не найкоротший?
Джеймс Wierzba

26

З підручника тут

"Це надзвичайно корисна властивість, що якщо всі ребра в графіку не зважені (або однакова вага), тоді перший раз, коли відвідується вузол, це найкоротший шлях до цього вузла від вихідного вузла"


Це добре для прямодоступного вузла (1-> 2) (2 досягається безпосередньо з 1). Для не безпосередньо доступного вузла існує більше роботи (1-> 2-> 3, 3 не досягається безпосередньо з 1). Звичайно, це все-таки вірно, враховуючи індивідуально, тобто 1-> 2 і 2-> 3 окремо - це найкоротші шляхи.
Manohar Reddy Poreddy

11

Я витратив 3 дні в
кінцевому підсумку вирішив графічне запитання, яке
використовується для
пошуку найкоротшої відстані
за допомогою BFS

Хочете поділитися досвідом.

When the (undirected for me) graph has
fixed distance (1, 6, etc.) for edges

#1
We can use BFS to find shortest path simply by traversing it
then, if required, multiply with fixed distance (1, 6, etc.)

#2
As noted above
with BFS
the very 1st time an adjacent node is reached, it is shortest path

#3
It does not matter what queue you use
   deque/queue(c++) or
   your own queue implementation (in c language)
   A circular queue is unnecessary

#4
Number of elements required for queue is N+1 at most, which I used
(dint check if N works)
here, N is V, number of vertices.

#5
Wikipedia BFS will work, and is sufficient.
    https://en.wikipedia.org/wiki/Breadth-first_search#Pseudocode

Я втратив 3 дні, пробуючи всі вищезазначені альтернативи, знову і знову перевіряючи і повторюючи, що вище
вони не є проблемою.
(Спробуйте витратити час на пошуки інших проблем, якщо ви знайдете будь-які проблеми з вище 5).


Більше пояснення з коментаря нижче:

      A
     /  \
  B       C
 /\       /\
D  E     F  G

Припустімо вище, що ваш графік
графіка йде вниз
Для A, суміжні - B & C
For B, суміжні - D & E
For C, суміжні - F&G

скажімо, пусковий вузол - A

  1. коли ви дістанетесь до пункту A, до, B & C, найкоротша відстань до B&C від A дорівнює 1

  2. коли ви досягнете D або E, через B, найкоротша відстань до A&D становить 2 (A-> B-> D)

аналогічно A-> E дорівнює 2 (A-> B-> E)

також A-> F & A-> G дорівнює 2

Отже, тепер замість 1 відстані між вузлами, якщо це 6, то просто помножте відповідь на 6
приклад,
якщо відстань між кожним дорівнює 1, то A-> E - 2 (A-> B-> E = 1 + 1 )
якщо відстань між кожним дорівнює 6, то A-> E дорівнює 12 (A-> B-> E = 6 + 6)

так, bfs може взяти будь-який шлях,
але ми обчислюємо всі шляхи

якщо вам доведеться пройти від А до Я, тоді ми проїдемо всі шляхи від А до проміжного I, і оскільки буде багато шляхів, ми відкидаємо весь, крім найкоротшого шляху до I, то продовжуємо з найкоротшим шляхом вперед до наступного вузла J
знову, якщо Є декілька шляхів від I до J, ми беремо лише найкоротший один
приклад,
припустимо,
A -> I ми маємо відстань 5
(КРОК) припустимо, I -> J у нас є кілька шляхів, відстань 7 і 8, оскільки 7 найкоротший
ми приймаємо A -> J як 5 (A-> я найкоротший) + 8 (найкоротший зараз) = 13,
тому A-> J зараз 13,
ми повторюємося вище (КРОК) для J -> K і так далі, поки не отримаємо до Z

Прочитайте цю частину 2 або 3 рази, і намалюйте на папері, ви обов'язково отримаєте те, що я говорю, найкраще



Чи можете ви, будь ласка, пояснити, як вам вдалося знайти найкоротший шлях за допомогою першого пошуку вшир. Перший пошук в основному шукає вузол, може бути n шляхів до вузла цілі з вихідного вузла, а bfs може пройти будь-який шлях. Як ти визначаєш найкращий шлях?
Андергор

я додав частину "більше пояснень" до вищезгаданої відповіді, дайте мені знати, чи це задовольняє
Манохар Редді Поредді

1
Я бачу, ви намагаєтеся запустити BFS на зваженому графіку. З відстаней 7 і 8, чому ви вибрали 8? чому б не 7? що робити, якщо у 8 вузла немає інших країв призначення? Тоді доведеться вибирати 7 тоді.
андерґог

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

вибачте, не знаєте, що ви маєте на увазі, дивіться en.wikipedia.org/wiki/Dijkstra's_algorithm
Manohar Reddy Poreddy

2

На основі acheron55 відповідь я відправив можливу реалізацію тут .
Ось короткий літній про це:

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


0

Відвідавши цю тему після певного періоду бездіяльності, але враховуючи, що я не бачу ґрунтовної відповіді, ось два мої центи.

Пошук за шириною завжди знайде найкоротший шлях у невагомому графіку. Графік може бути циклічним або ациклічним.

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

mark all vertices as unvisited
set the distance value of all vertices to infinity
set the distance value of the start vertex to 0
push the start vertex on the queue
while(queue is not empty)   
    dequeue one vertex (well call it x) off of the queue
    if the value of x is the value of the end vertex: 
        return the distance value of x
    otherwise, if x is not marked as visited:
        mark it as visited
        for all of the unmarked children of x:
            set their distance values to be the distance of x + 1
            enqueue them to the queue
if here: there is no path connecting the vertices

Зауважте, що цей підхід не працює для зважених графіків - про це див. Алгоритм Діккстри.


-6

Наступне рішення працює для всіх тестових випадків.

import java.io.*;
import java.util.*;
import java.text.*;
import java.math.*;
import java.util.regex.*;

public class Solution {

   public static void main(String[] args)
        {
            Scanner sc = new Scanner(System.in);

            int testCases = sc.nextInt();

            for (int i = 0; i < testCases; i++)
            {
                int totalNodes = sc.nextInt();
                int totalEdges = sc.nextInt();

                Map<Integer, List<Integer>> adjacencyList = new HashMap<Integer, List<Integer>>();

                for (int j = 0; j < totalEdges; j++)
                {
                    int src = sc.nextInt();
                    int dest = sc.nextInt();

                    if (adjacencyList.get(src) == null)
                    {
                        List<Integer> neighbours = new ArrayList<Integer>();
                        neighbours.add(dest);
                        adjacencyList.put(src, neighbours);
                    } else
                    {
                        List<Integer> neighbours = adjacencyList.get(src);
                        neighbours.add(dest);
                        adjacencyList.put(src, neighbours);
                    }


                    if (adjacencyList.get(dest) == null)
                    {
                        List<Integer> neighbours = new ArrayList<Integer>();
                        neighbours.add(src);
                        adjacencyList.put(dest, neighbours);
                    } else
                    {
                        List<Integer> neighbours = adjacencyList.get(dest);
                        neighbours.add(src);
                        adjacencyList.put(dest, neighbours);
                    }
                }

                int start = sc.nextInt();

                Queue<Integer> queue = new LinkedList<>();

                queue.add(start);

                int[] costs = new int[totalNodes + 1];

                Arrays.fill(costs, 0);

                costs[start] = 0;

                Map<String, Integer> visited = new HashMap<String, Integer>();

                while (!queue.isEmpty())
                {
                    int node = queue.remove();

                    if(visited.get(node +"") != null)
                    {
                        continue;
                    }

                    visited.put(node + "", 1);

                    int nodeCost = costs[node];

                    List<Integer> children = adjacencyList.get(node);

                    if (children != null)
                    {
                        for (Integer child : children)
                        {
                            int total = nodeCost + 6;
                            String key = child + "";

                            if (visited.get(key) == null)
                            {
                                queue.add(child);

                                if (costs[child] == 0)
                                {
                                    costs[child] = total;
                                } else if (costs[child] > total)
                                {
                                    costs[child] = total;
                                }
                            }
                        }
                    }
                }

                for (int k = 1; k <= totalNodes; k++)
                {
                    if (k == start)
                    {
                        continue;
                    }

                    System.out.print(costs[k] == 0 ? -1 : costs[k]);
                    System.out.print(" ");
                }
                System.out.println();
            }
        }
}

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