Алгоритм Беллмана-Форда для объяснения отрицательных циклов

Я попытался реализовать алгоритм BF для обнаружения отрицательного цикла.

Я следил за примечаниями лекции, чтобы реализовать алгоритм. Мое решение было следующим:

def belman_ford(s, adj, cost):
    arr_len = len(adj)
    dist_arr = [MAX_VAL]*arr_len
    prev_arr = [None]*arr_len
    dist_arr[s] = 0

    for u in range(arr_len):
        for i, v in enumerate(adj[u]):
            if dist_arr[v] == MAX_VAL:
                break
            if dist_arr[v] > dist_arr[u] + cost[u][i]:
                dist_arr[v] = dist_arr[u] + cost[u][i]
                prev_arr[v] = u


    #detect negative cycle
    for u in range(arr_len):
        for i, v in enumerate(adj[u]):
            if dist_arr[v] == MAX_VAL:
                break
            if dist_arr[v] < dist_arr[u] + cost[u][i]:
                return 1

    return 0


def negative_cycle(adj, cost):
    #write your code here
    return belman_ford(0,adj, cost)

Однако я нашел другое решение, которое помогло мне пройти испытание кодирования.

def negative_cycle(adj, cost):
    distance=[0]*len(adj)
    distance[0] = 0
    for ind in range(len(adj)):
        for u in range(len(adj)):
            for v in adj[u]:
                v_index = adj[u].index(v)
                if distance[v] > distance[u] + cost[u][v_index]:
                    distance[v] = distance[u] + cost[u][v_index]
                    if ind==len(adj) - 1:
                        return 1
    return 0

Я не могу понять логику этого. Почему на самом деле этот оператор if ind==len(adj) - 1 if приводит к обнаружению цикла

В качестве входных данных я получаю список смежности и стоимости


person Daniel Chepenko    schedule 28.12.2018    source источник


Ответы (1)


Основная идея алгоритма Беллмана-Форда представлена ​​в кратком обзоре из Википедии:

Алгоритм Беллмана-Форда просто ослабляет все ребра и делает это |V|-1 раз, где |V| — количество вершин в графе.

Тогда объяснение вашей строки if ind==len(adj) - 1

Поскольку самый длинный возможный путь без цикла может состоять из |V|-1 ребер, ребра должны быть просканированы |V|-1 раз, чтобы убедиться, что кратчайший путь найден для всех узлов.

Выполняется окончательное сканирование всех ребер, и если какое-либо расстояние обновляется, то найден путь длиной |V| ребер, который может возникнуть только в том случае, если в графе существует хотя бы один отрицательный цикл.

Беллман-Форд предполагает, что расстояния сначала очень велики (бесконечность), а затем постепенно уменьшают их до минимально возможных. Это называется расслаблением. На каждой итерации ребра, удаленные на один шаг от источника, ослабевают.

S --> A --> B --> C --> D --> E --> F --> G --> H --> I

Теперь предположим, что у вас есть граф без отрицательных циклов. Скажем, у него 10 вершин, вам нужно не более 9 релаксаций, чтобы достичь (получить кратчайший путь) самую дальнюю вершину от источника. Что делать, если после 9 релаксаций вы все еще получаете улучшение? На вашем графике должны быть отрицательные циклы.

ind в вашем коде - это счетчик. Когда ind == len(adj) - 1 это означает, что вы смягчили дистанцию ​​|V| раз, что говорит о существовании отрицательного цикла(ов).

Также проверьте третью страницу от последней в вашей собственной заметке.

person aafulei    schedule 28.12.2018
comment
@DanielChepenko Это не пропущено по сравнению с тем, что в вашей ссылке. Они одинаковые. В версии по вашей ссылке релаксация сначала запускается V - 1 раз, потом последний раз. Если вы объедините этот последний раз с первым V - 1 разом, это будет код в вашем вопросе. - person aafulei; 28.12.2018
comment
В качестве бонуса, в реализации алгоритма Беллмана-Форда есть несколько дополнительных приемов раннего отказа. Скажем, когда бы вы ни обнаружили, что кратчайшее расстояние от источника до самого себя, то есть distance[0], становится странно отрицательным, вы можете остановиться и сделать вывод, что нашли отрицательный цикл. Таким образом, вам может даже не понадобиться ждать |V| сканирования. - person aafulei; 28.12.2018
comment
Спасибо, кстати заметил, что в этой реализации они инициируют дистанцию ​​с 0 на первом шаге. Есть ли причина делать это вместо инициализации с бесконечно большими значениями? - person Daniel Chepenko; 28.12.2018
comment
@DanielChepenko Только что заметил эту строку (distance=[0]*len(adj)). Странный. Не могли бы вы еще раз проверить, есть ли эта строка в коде, который проходит тестовые случаи? Если да, то какой смысл снова ставить distance[0] = 0? - person aafulei; 28.12.2018