Python 使用Bellman-Ford从源到负循环的最短路径

Python 使用Bellman-Ford从源到负循环的最短路径,python,graph-theory,graph-algorithm,bellman-ford,Python,Graph Theory,Graph Algorithm,Bellman Ford,我想找到从图中的源节点到负循环的最短路径,而不必遍历任何循环两次如果对此有明确的答案,请回答。否则,我想展示我是如何处理这个问题的,希望其他人能告诉我哪里出了错。 下面是我的代码的简化版本,它实现了Bellman-Ford算法来检测负循环(使用)。这是该算法的简单实现。我进一步讨论我对它的修改 class NegativeWeightFinder: def __init__(self, graph: nx.Graph): self.graph = graph self.pre

我想找到从图中的源节点到负循环的最短路径,而不必遍历任何循环两次如果对此有明确的答案,请回答。否则,我想展示我是如何处理这个问题的,希望其他人能告诉我哪里出了错。

下面是我的代码的简化版本,它实现了Bellman-Ford算法来检测负循环(使用)。这是该算法的简单实现。我进一步讨论我对它的修改

class NegativeWeightFinder:
  def __init__(self, graph: nx.Graph):
    self.graph = graph
    self.predecessor_to = {}
    self.distance_to = {}

  def initialize(self, source):
    for node in self.graph:
      # Initialize all distance_to values to infinity and all predecessor_to values to None
      self.distance_to[node] = float('Inf')
      self.predecessor_to[node] = None

    # The distance from any node to (itself) == 0
    self.distance_to[source] = 0

    def bellman_ford(self, source):

      self.initialize(source)
      for i in range(len(self.graph) - 1):
        for edge in self.graph.edges(data=True):
          self.relax(edge)

      for edge in self.graph.edges(data=True):
        if self.distance_to[edge[0]] + edge[2]['weight'] <
          self.distance_to[edge[1]]:
          yield self._retrace_negative_loop(edge[1])
我想找出图中每个负循环的最小加权路径。我相信,在完成负循环之后

  • 迭代第二条到源的最小加权路径(因为第一条路径导致负循环)

  • 为了从循环到源(而不是从源到循环),我在数据结构中添加了一个前置_,它跟踪从每个节点到源的最短路径(而前置_跟踪从源到节点的最短路径)

  • 我创建了一个粗糙的数据结构来跟踪从每个节点到源节点和从源节点的最小加权路径,它的功能基本上类似于只允许唯一元素的优先级队列(两个相等元素的最高优先级优先于另一个)。此“优先级设置”的代码如下所示

    以下是我对该算法的实现:

    class NegativeWeightFinder:
    
        def __init__(self, graph: nx.Graph):
            self.graph = graph
            self.predecessor_to = {}
            self.distance_to = {}
            self.predecessor_from = {}
            self.distance_from = {}
    
            self.seen_nodes = set()
    
        def initialize(self, source):
            for node in self.graph:
                # Initialize all distance_to values to infinity and all predecessor_to values to None
                self.distance_to[node] = float('Inf')
                self.predecessor_to[node] = PrioritySet()
                self.distance_from[node] = float('Inf')
                self.predecessor_from[node] = PrioritySet()
    
            # The distance from any node to (itself) == 0
            self.distance_to[source] = 0
            self.distance_from[source] = 0
    
        def bellman_ford(self, source):
            self.initialize(source)
            # After len(graph) - 1 passes, algorithm is complete.
            for i in range(len(self.graph) - 1):
                # for each node in the graph, test if the distance to each of its siblings is shorter by going from
                for edge in self.graph.edges(data=True):
                    self.relax(edge)
    
            for edge in self.graph.edges(data=True):
                if self.distance_to[edge[0]] + edge[2]['weight'] < self.distance_to[edge[1]]:
                    yield self._retrace_negative_loop(edge[1], 
                                                      source=source)
        def relax(self, edge):
            if self.distance_to[edge[0]] + edge[2]['weight'] < self.distance_to[edge[1]]:
                self.distance_to[edge[1]] = self.distance_to[edge[0]] + edge[2]['weight']
    
            # no matter what, adds this edge to the PrioritySet in distance_to
            self.predecessor_to[edge[1]].add(edge[0], self.distance_to[edge[0]] + edge[2]['weight'])
    
            if self.distance_from[edge[1]] + edge[2]['weight'] < self.distance_from[edge[0]]:
                self.distance_from[edge[0]] = self.distance_from[edge[1]] + edge[2]['weight']
    
            self.predecessor_from[edge[0]].add(edge[1],
                                               self.distance_from[edge[1]] + edge[2]['weight'])
    
            return True
    
        def _retrace_negative_loop(self, start, source=''):
            arbitrage_loop = [start]
            if source not in self.graph:
                raise ValueError("source not in graph.")
    
            # todo: i do not remember to which edge case this refers, test to see which then specify in the comment.
            # adding the predecessor to start to arbitrage loop outside the while loop prevents an edge case.
            next_node = self.predecessor_to[arbitrage_loop[0]].peek()[1]
            if unique_paths and next_node in self.seen_nodes:
                raise SeenNodeError(next_node)
    
            arbitrage_loop.insert(0, next_node)
    
            # todo: refactor this so it is not while True, instead while not next_to_each_other
            while True:
                next_node = self.predecessor_to[arbitrage_loop[0]].peek()[1]
    
                # if this edge has been traversed over, negative cycle is complete.
                if next_to_each_other(arbitrage_loop, next_node, arbitrage_loop[0]):
                    arbitrage_loop.insert(0, next_node)
                    arbitrage_loop = arbitrage_loop[:last_index_in_list(arbitrage_loop, next_node) + 1]
    
    
    
                    self.predecessor_to[arbitrage_loop[0]].pop()
    
                    def _pop_arbitrage_loop(loop, predecessor):
                        while predecessor[loop[0]].empty:
                            loop.pop(0)
    
                    # add the path from source -> min_distance_to_node to the beginning of arbitrage_loop
                    while arbitrage_loop[0] != source:
                        _pop_arbitrage_loop(arbitrage_loop, self.predecessor_to)
                        next_node = self.predecessor_to[arbitrage_loop[0]].pop()[1]
                        # if this edge has already been traversed over/ added to arbitrage_loop, must exit the cycle.
                        if next_to_each_other(arbitrage_loop, next_node, arbitrage_loop[0]):
                                self.predecessor_to[arbitrage_loop[0]].pop()
                            # this prevents an error where every edge from a node has been traversed over.
                            _pop_arbitrage_loop(arbitrage_loop, self.predecessor_to)
    
                            next_node = self.predecessor_to[arbitrage_loop[0]].pop()[1]
    
                        arbitrage_loop.insert(0, next_node)
    
                    # add the path from arbitrage_loop[-1] -> source to the end of arbitrage_loop
                    while arbitrage_loop[-1] != source:
                        next_node = self.predecessor_from[arbitrage_loop[-1]].peek()[1]
                        if next_to_each_other(arbitrage_loop, arbitrage_loop[-1], next_node):
                                self.predecessor_from[arbitrage_loop[-1]].pop()
    
                            arbitrage_loop.append(next_node)
    
                        self.reset_predecessor_iteration()
                        return arbitrage_loop
    
                    arbitrage_loop.insert(0, next_node)
    
        def reset_predecessor_iteration(self):
            for node in self.predecessor_to.keys():
                self.predecessor_to[node].reset()
                # predecessor_to and predecessor_to have the same keys
                self.predecessor_from[node].reset()
    
    def next_to_each_other(li: list, *args):
        for i in range(len(li) - (len(args) - 1)):
            for j in range(len(args)):
                if li[i + j] != args[j]:
                    break
                if j == len(args) - 1:
                    return True
        return False
    
    
    def last_index_in_list(li: list, element):
        return len(li) - next(i for i, v in enumerate(reversed(li), 1) if v == element)
    
    class NegativeWeightFinder:
    定义初始化(self,图形:nx.graph):
    self.graph=graph
    self.u to={}
    self.distance_to={}
    self.preference_from={}
    self.distance_from={}
    self.seen_nodes=set()
    def初始化(自身、源):
    对于self.graph中的节点:
    #将所有距离_值初始化为无穷大,将所有前置距离_值初始化为无
    self.distance_到[node]=浮点('Inf')
    self.preference_to[node]=PrioritySet()
    self.distance_from[node]=float('Inf')
    self.preference_from[node]=PrioritySet()
    #从任何节点到(自身)的距离=0
    到[源]的自距离=0
    距离[源]的自距离=0
    def bellman_ford(自身,来源):
    自我初始化(源)
    #len(graph)-1通过后,算法完成。
    对于范围内的i(len(self.graph)-1):
    #对于图中的每个节点,通过从
    对于self.graph.edges中的边(data=True):
    自我放松(边缘)
    对于self.graph.edges中的边(data=True):
    如果self.distance_到[edge[0]]+edge[2]['weight']min\u distance\u到\u节点的路径添加到套利\u循环的开头
    而套利_循环[0]!=资料来源:
    _pop_套利_循环(套利_循环,self.previous_to)
    next_node=self.preducer_to[套利_循环[0]].pop()[1]
    #如果此边已被穿过/添加到套利循环,则必须退出循环。
    如果下一个到另一个(套利循环,下一个节点,套利循环[0]):
    self.preducer_to[willidge_loop[0]].pop()
    #这可以防止节点的每条边都已遍历的错误。
    _pop_套利_循环(套利_循环,self.previous_to)
    next_node=self.preducer_to[套利_循环[0]].pop()[1]
    套利循环。插入(0,下一个节点)
    #将套利_循环[-1]->source的路径添加到套利_循环的末尾
    
    class PrioritySet:
      def __init__(self):
        self.heap = []
        self.popped = {}
    
      def add(self, d, pri):
        heapq.heappush(self.heap, (pri, d))
    
        return True
    
      def pop(self):
        popped = heapq.heappop(self.heap)
        while popped[1] in self.popped.keys():
          # Raises IndexError if done popping
          try:
              popped = heapq.heappop(self.heap)
          # for debugging
          except Exception as e:
              raise e
    
        self.popped[popped[1]] = popped[0]
        return popped
    
      def peek(self):
        # self.heap[0][1] is the name of the element
        try:
          while self.heap[0][1] in self.popped.keys():
            # Raises IndexError if done popping
            heapq.heappop(self.heap)
        # for debugging
        except Exception as e:
          raise e
    
        return self.heap[0]
    
      def reset(self):
        for key, value in self.popped.items():
          heapq.heappush(self.heap, (value, key))
        self.popped = {}
    
      @property
      def empty(self):
        for elem in self.heap:
          if elem[1] not in self.popped.keys():
            return False
    
        return True
    
      def __str__(self):
        return str(list(self.heap))
    
      def __repr__(self):
        return str(self)
    
      def __len__(self):
        total = 0
        seen = set()
        for elem in self.heap:
          if elem not in self.popped and elem not in seen:
            total += 1
            seen.add(elem)
    
        return total
    
    class NegativeWeightFinder:
    
        def __init__(self, graph: nx.Graph):
            self.graph = graph
            self.predecessor_to = {}
            self.distance_to = {}
            self.predecessor_from = {}
            self.distance_from = {}
    
            self.seen_nodes = set()
    
        def initialize(self, source):
            for node in self.graph:
                # Initialize all distance_to values to infinity and all predecessor_to values to None
                self.distance_to[node] = float('Inf')
                self.predecessor_to[node] = PrioritySet()
                self.distance_from[node] = float('Inf')
                self.predecessor_from[node] = PrioritySet()
    
            # The distance from any node to (itself) == 0
            self.distance_to[source] = 0
            self.distance_from[source] = 0
    
        def bellman_ford(self, source):
            self.initialize(source)
            # After len(graph) - 1 passes, algorithm is complete.
            for i in range(len(self.graph) - 1):
                # for each node in the graph, test if the distance to each of its siblings is shorter by going from
                for edge in self.graph.edges(data=True):
                    self.relax(edge)
    
            for edge in self.graph.edges(data=True):
                if self.distance_to[edge[0]] + edge[2]['weight'] < self.distance_to[edge[1]]:
                    yield self._retrace_negative_loop(edge[1], 
                                                      source=source)
        def relax(self, edge):
            if self.distance_to[edge[0]] + edge[2]['weight'] < self.distance_to[edge[1]]:
                self.distance_to[edge[1]] = self.distance_to[edge[0]] + edge[2]['weight']
    
            # no matter what, adds this edge to the PrioritySet in distance_to
            self.predecessor_to[edge[1]].add(edge[0], self.distance_to[edge[0]] + edge[2]['weight'])
    
            if self.distance_from[edge[1]] + edge[2]['weight'] < self.distance_from[edge[0]]:
                self.distance_from[edge[0]] = self.distance_from[edge[1]] + edge[2]['weight']
    
            self.predecessor_from[edge[0]].add(edge[1],
                                               self.distance_from[edge[1]] + edge[2]['weight'])
    
            return True
    
        def _retrace_negative_loop(self, start, source=''):
            arbitrage_loop = [start]
            if source not in self.graph:
                raise ValueError("source not in graph.")
    
            # todo: i do not remember to which edge case this refers, test to see which then specify in the comment.
            # adding the predecessor to start to arbitrage loop outside the while loop prevents an edge case.
            next_node = self.predecessor_to[arbitrage_loop[0]].peek()[1]
            if unique_paths and next_node in self.seen_nodes:
                raise SeenNodeError(next_node)
    
            arbitrage_loop.insert(0, next_node)
    
            # todo: refactor this so it is not while True, instead while not next_to_each_other
            while True:
                next_node = self.predecessor_to[arbitrage_loop[0]].peek()[1]
    
                # if this edge has been traversed over, negative cycle is complete.
                if next_to_each_other(arbitrage_loop, next_node, arbitrage_loop[0]):
                    arbitrage_loop.insert(0, next_node)
                    arbitrage_loop = arbitrage_loop[:last_index_in_list(arbitrage_loop, next_node) + 1]
    
    
    
                    self.predecessor_to[arbitrage_loop[0]].pop()
    
                    def _pop_arbitrage_loop(loop, predecessor):
                        while predecessor[loop[0]].empty:
                            loop.pop(0)
    
                    # add the path from source -> min_distance_to_node to the beginning of arbitrage_loop
                    while arbitrage_loop[0] != source:
                        _pop_arbitrage_loop(arbitrage_loop, self.predecessor_to)
                        next_node = self.predecessor_to[arbitrage_loop[0]].pop()[1]
                        # if this edge has already been traversed over/ added to arbitrage_loop, must exit the cycle.
                        if next_to_each_other(arbitrage_loop, next_node, arbitrage_loop[0]):
                                self.predecessor_to[arbitrage_loop[0]].pop()
                            # this prevents an error where every edge from a node has been traversed over.
                            _pop_arbitrage_loop(arbitrage_loop, self.predecessor_to)
    
                            next_node = self.predecessor_to[arbitrage_loop[0]].pop()[1]
    
                        arbitrage_loop.insert(0, next_node)
    
                    # add the path from arbitrage_loop[-1] -> source to the end of arbitrage_loop
                    while arbitrage_loop[-1] != source:
                        next_node = self.predecessor_from[arbitrage_loop[-1]].peek()[1]
                        if next_to_each_other(arbitrage_loop, arbitrage_loop[-1], next_node):
                                self.predecessor_from[arbitrage_loop[-1]].pop()
    
                            arbitrage_loop.append(next_node)
    
                        self.reset_predecessor_iteration()
                        return arbitrage_loop
    
                    arbitrage_loop.insert(0, next_node)
    
        def reset_predecessor_iteration(self):
            for node in self.predecessor_to.keys():
                self.predecessor_to[node].reset()
                # predecessor_to and predecessor_to have the same keys
                self.predecessor_from[node].reset()
    
    def next_to_each_other(li: list, *args):
        for i in range(len(li) - (len(args) - 1)):
            for j in range(len(args)):
                if li[i + j] != args[j]:
                    break
                if j == len(args) - 1:
                    return True
        return False
    
    
    def last_index_in_list(li: list, element):
        return len(li) - next(i for i, v in enumerate(reversed(li), 1) if v == element)