Флойд-Уоршалл: все кратчайшие пути

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

Есть ли способ заставить его возвращать каждый кратчайший путь, даже если есть несколько путей, связанных для кратчайшего, для каждой пары узлов? (Я просто хочу знать, не трачу ли я время на попытки)


person user1507844    schedule 06.07.2012    source источник
comment
сохраните все кратчайшие пути в HashMap с помощью key=path-length и value={set of shortest paths at this length}. Сохраните длину Shotest-path в отдельной переменной, и после того, как ваш алгоритм будет выполнен, просто извлеките минимальное значение из HashMap.   -  person Nir Alfasi    schedule 07.07.2012


Ответы (2)


Если вам просто нужно подсчитать, сколько существует различных кратчайших путей, вы можете сохранить массив count в дополнение к массиву shortestPath. Вот быстрая модификация псевдокода из wiki.

procedure FloydWarshall ()
    for k := 1 to n
        for i := 1 to n
            for j := 1 to n
                if path[i][j] == path[i][k]+path[k][j] and k != j and k != i
                    count[i][j] += 1;
                else if path[i][j] > path[i][k] + path[k][j]
                    path[i][j] = path[i][k] + path[k][j]
                    count[i][j] = 1

Если вам нужен способ найти все пути, вы можете сохранить vector/arraylist-подобную структуру для каждой пары, которая будет разворачиваться и сворачиваться. Вот модификация псевдокода из той же wiki.

procedure FloydWarshallWithPathReconstruction ()
    for k := 1 to n
        for i := 1 to n
            for j := 1 to n
                if path[i][k] + path[k][j] < path[i][j]
                    path[i][j] := path[i][k]+path[k][j];
                    next[i][j].clear()
                    next[i][j].push_back(k) // assuming its a c++ vector
                else if path[i][k] + path[k][j] == path[i][j] and path[i][j] != MAX_VALUE and k != j and k != i
                    next[i][j].push_back(k)

Примечание: если k==j или k==i, это означает, что вы проверяете либо path[i][i]+path[i][j], либо path[i][j]+path[j][j], оба должны быть равны path[i][j], и это не помещается в next[i][j].

Реконструкция пути должна быть изменена для обработки vector. Подсчетом в этом случае будет размер каждого vector. Вот модификация псевдокода (python) из той же wiki .

procedure GetPath(i, j):
    allPaths = empty 2d array
    if next[i][j] is not empty:
        for every k in next[i][j]:
            if k == -1: // add the path = [i, j]
                allPaths.add( array[ i, j] ) 
            else: // add the path = [i .. k .. j]
                paths_I_K = GetPath(i,k) // get all paths from i to k
                paths_K_J = GetPath(k,j) // get all paths from k to j
                for every path between i and k, i_k in paths_I_K:
                    for every path between k and j, k_j in paths_K_J:
                        i_k = i_k.popk() // remove the last element since that repeats in k_j
                        allPaths.add( array( i_k + j_k) )

    return allPaths

Примечание: path[i][j] - это список смежности. При инициализации path[i][j] вы также можете инициализировать next[i][j], добавив -1 в массив. Например, инициализация next[i][j] будет

for every edge (i,j) in graph:
   next[i][j].push_back(-1)

Это позаботится о том, чтобы край был кратчайшим путем. Вам придется обработать этот особый случай при реконструкции пути, чем я и занимаюсь в GetPath.

Изменить: «MAX_VALUE» - это инициализированное значение в массиве расстояний.

person deebee    schedule 07.07.2012
comment
Мне удалось заставить его работать, добавив and k != j в оператор else if. Затем я написал рекурсивную функцию для пошагового выполнения next. Он интерпретирует значение в next, которое равно текущему узлу, как означающее, что к следующему узлу можно получить прямой доступ. Является ли это разумным способом интерпретации / разгадки следующей матрицы? Или есть более чистый / ясный способ сделать это? - person user1507844; 07.07.2012
comment
@ user1507844 Пропустил k != j часть. Отредактировал свой ответ, чтобы отразить это. - person deebee; 08.07.2012
comment
@ user1507844 Я добавил код для восстановления пути. Я использовал -1 в качестве индекса, если есть прямой край; но ваша техника хранения одного из узлов тоже хороша. - person deebee; 08.07.2012
comment
Я заметил, что вы также добавили k != i, для чего это? Не уверен, что понимаю -1 часть. Где запись в next устанавливается на -1? Это то, чем он инициализирован? Спасибо еще раз за помощь - person user1507844; 08.07.2012
comment
@ user1507844 Добавлены примечания для уточнения. - person deebee; 08.07.2012
comment
Еще один (надеюсь, последний) вопрос. Функция GetPath возвращает 1,2,2,2,3,3,3,4, когда я ожидал 1,2,3,4. Также, когда есть несколько маршрутов через узел, определенные части возвращаются только один раз. Следует ли добавить переменную pathSoFar и передать ее рекурсивной функции? Возможно инициализируется начальным i - person user1507844; 09.07.2012
comment
Есть ли что-то вроде оболочки и внутренней функции в вопросе / ответе 10543943? Пример, дающий мне проблемы, - это ситуация, когда переход от 1 до ›4 может быть выполнен 1,2,3,4 1,2,4 или 1,3,4 - person user1507844; 09.07.2012
comment
@Amadan Может ли здесь помочь ваш ответ на 10543943? - person user1507844; 09.07.2012
comment
Я предполагаю, что моя проблема в том, как обрабатывать все ветки из нескольких записей в следующем. Извините за все сообщения - person user1507844; 09.07.2012
comment
@ user1507844 Вы правы; это требует лучшего обращения. Я попробую что-нибудь, когда вернусь с работы. - person deebee; 09.07.2012
comment
@ user1507844 Надеюсь, эта новая процедура (GetPath) вам поможет. Дайте мне знать, если я пропустил очевидную уловку или оптимизацию. - person deebee; 10.07.2012
comment
Спасибо, работает. Он действительно возвращает некоторые повторяющиеся пути, но не уверен, есть ли какой-либо более чистый способ справиться с этим, используя только уникальные пути перед возвратом allPaths - person user1507844; 10.07.2012
comment
Одна маленькая вещь, я думаю, где-то нужна проверка, чтобы предотвратить бесконечную рекурсию из-за цикличности. Например, граф с расстояниями между всеми узлами, равными нулю, застрянет в getPaths. Возможно, массив посещенных узлов? - person user1507844; 10.07.2012
comment
@ user1507844 Цикл не должен происходить по кратчайшему пути. У вас есть отрицательные циклы? Если вы это сделаете, то для графика не будет правильного кратчайшего пути. - person deebee; 11.07.2012
comment
Нет отрицательных циклов, но попробовал пример с циклами с нулевой стоимостью, и именно здесь я получил бесконечную рекурсию. Можно ли добавить массив, указывающий, что узел был посещен, чтобы избежать бесконечной рекурсии в этой ситуации? - person user1507844; 11.07.2012
comment
давайте продолжим обсуждение в чате - person deebee; 11.07.2012

Функция «подсчета» в текущем утвержденном ответе в некоторых случаях дает сбой. Более полное решение было бы:

procedure FloydWarshallWithCount ()
for k := 1 to n
    for i := 1 to n
        for j := 1 to n
            if path[i][j] == path[i][k]+path[k][j]
                count[i][j] += count[i][k] * count[k][j]
            else if path[i][j] > path[i][k] + path[k][j]
                path[i][j] = path[i][k] + path[k][j]
                count[i][j] = count[i][k] * count[k][j]

Причина этого в том, что для любых трех вершин i, j и k может существовать несколько кратчайших путей, идущих от i через k к j. Например, на графике:

       3             1
(i) -------> (k) ---------> (j)
 |            ^
 |            |
 | 1          | 1
 |     1      |
(a) -------> (b)

Где есть два пути от i до j через k. count[i][k] * count[k][j] находит количество путей от i до k и количество путей от k до j, и умножает их, чтобы найти количество путей i -> k -> j.

person user4336087    schedule 08.12.2014