Поиск всех кратчайших путей между двумя узлами в невзвешенного неориентированного графа

Мне нужна помощь в поиске всех кратчайших путей между двумя вершинами в невзвешенный неориентированный граф.

Я могу найти один из самых коротких путей с помощью BFS, но пока я потерян, как я мог бы найти и распечатать их все.

любая идея алгоритма / псевдокода, который я мог бы использовать?

8 ответов


в качестве предостережения помните, что между двумя узлами в графе может быть экспоненциально много кратчайших путей. Любой алгоритм для этого потенциально займет экспоненциальное время.

тем не менее, существует относительно простая модификация BFS, которую вы можете использовать в качестве шага предварительной обработки для ускорения генерации всех возможных путей. Помните, что при запуске BFS он продолжается наружу в "слоях", получая один кратчайший путь ко всем узлам на расстоянии 0, затем расстояние 1, затем на расстоянии 2, и т. д. Мотивирующая идея BFS заключается в том, что любой узел на расстоянии k + 1 от начального узла должен быть подключен ребром к некоторому узлу на расстоянии k от начального узла. BFS обнаруживает этот узел на расстоянии k + 1, находя некоторый путь длины k к узлу на расстоянии k, а затем расширяя его на некоторое ребро.

Если ваша цель-найти все кратчайший путь, затем вы можете изменить BFS, расширив каждый путь к узлу на расстоянии k ко всем узлам на расстояние k + 1, к которому они подключаются, а не выбор одного края. Для этого измените BFS следующим образом: всякий раз, когда вы обрабатываете ребро, добавляя его конечную точку в очередь обработки, не сразу отмечайте этот узел как выполняемый. Вместо этого вставьте этот узел в очередь с аннотацией, с которой вы следовали, чтобы добраться до него. Это потенциально позволит вам вставлять один и тот же узел в очередь несколько раз, если есть несколько узлов, которые ссылаются на него. При удалении узла из очередь, затем вы отмечаете это как выполняемое и никогда не вставляете его в очередь снова. Аналогично, вместо хранения одного родительского указателя вы будете хранить несколько родительских указателей, по одному для каждого узла, связанного с этим узлом.

Если вы сделаете это измененное BFS, вы получите DAG, где каждый узел будет либо начальным узлом и не будет иметь исходящих ребер, либо будет на расстоянии k + 1 от начального узла и будет иметь указатель на каждый узел расстояния k, к которому он подключен. Оттуда вы можете восстановить все кратчайшие пути от некоторого узла до начального узла, перечислив все возможные пути от выбранного узла до начального узла в DAG. Это можно сделать рекурсивно:

  • есть только один путь от начального узла к самому себе, а именно пустой путь.
  • для любого другого узла пути можно найти, следуя каждому исходящему краю, а затем рекурсивно расширяя эти пути, чтобы получить путь обратно к началу узел.

надеюсь, что это помогает!


@templatetypedef является правильным, но он забыл упомянуть о проверке расстояния, которая должна быть выполнена до добавления родительских ссылок в узел. Это означает, что se сохраняет расстояние от источника в каждом из узлов и увеличивает на единицу расстояние для детей. Мы должны пропустить это приращение и родительское добавление в случае, если ребенок уже был посещен и имеет меньшее расстояние.

public void addParent(Node n) {
    // forbidding the parent it its level is equal to ours
    if (n.level == level) {
        return;
    }
    parents.add(n);

    level = n.level + 1;
}

полная реализация java может быть найдена следующим образом ссылка на сайт.

http://ideone.com/UluCBb


Я столкнулся с аналогичной проблемой при решении этого https://oj.leetcode.com/problems/word-ladder-ii/

способ, которым я пытался справиться, - это сначала найти кратчайшее расстояние с помощью BFS, скажем, кратчайшее расстояние-d. Теперь примените DFS и в рекурсивном вызове DFS не выходите за рекурсивный уровень D.

однако это может привести к изучению всех путей, как указано в @templatetypedef.


во-первых, найдите расстояние до начала всех узлов, используя поиск по ширине.

(если есть много узлов, вы можете использовать* и остановиться, когда начало очереди и distance-to-start > distance-to-start(end-node). Это даст вам все узлы, которые принадлежат к некоторому кратчайшему пути)

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


templatetypedef ваш ответ был очень хорошим, большое спасибо за это (!!), но он упустил один момент:

если у вас есть график такой:

A-B-C-E-F
  |     |
  D------

теперь давайте представим, что я хочу этот путь:

A -> E.

он будет расширяться следующим образом:

 A-> B -> D-> C -> F -> E.

проблема есть, что у вас будет F как родитель E, но

 A->B->D->F-E 
- это больше, чем

 A->B->C->E.
вам придется взять отслеживания расстояний родителей вы так радостно добавляем.

Шаг 1: пройдите график от источника BFS и назначьте каждому узлу минимальное расстояние от источника

Шаг 2: расстояние, назначенное целевому узлу, является самой короткой длиной

Шаг 3: от источника выполните поиск DFS по всем путям, где минимальное расстояние увеличивается один за другим, пока не будет достигнут целевой узел или достигнута самая короткая длина. Печать пути при достижении целевого узла.


BFS останавливается, когда вы найдете то, что хотите.

вы должны изменить алгоритм, чтобы он продолжал свое выполнение, когда будет найден первый путь. (удалить return заявление и сохранить путь как-то.

вы можете завершить выполнение после проверки последний узел уровня, который имеет конечные узлы кратчайших путей. (Все конечные узлы кратчайших путей находятся на одном уровне)


также известен алгоритм, который находит все кратчайшее пути:

Флойд–Warshall алгоритм (это псевдокод)

Джонсон


Как насчет этого : я нашел его в Интернете

http://code.google.com/p/k-shortest-paths/