使用Bellman Ford从源头到负周期的最短路径

时间:2018-03-28 19:37:09

标签: python graph-theory graph-algorithm bellman-ford

我想找到从图中的源节点到负循环的最短路径,而不是两次循环任何循环。 如果有明确的答案,请回答。否则,我想表明我是如何处理这个问题的,希望其他人可以告诉我我的错误。

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

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])

我还实现了一种回溯负循环的方法:

def _retrace_negative_loop(self, start):
  loop = [start]
  while True:
    next_node = self.predecessor_to[next_node]
    # if negative cycle is complete
    if next_node in loop:
      loop = loop[:loop.index(next_node) + 1]
      loop.insert(0, next_node)
      return loop

我想在图中找到每个负循环的最小加权路径。我相信这可以通过在完成负循环之后完成,

  1. 迭代第二个最小加权路径到源(因为第一个导致负循环)。

  2. 要从循环到源(而不是从源到循环),我添加了一个predecessor_from数据结构,它跟踪从每个节点到源的最短路径(而predecessor_to跟踪从源到源的最短路径)节点)。

  3. 我创建了一个hacky数据结构来跟踪每个节点与源之间的最小加权路径,它基本上像一个优先级队列,只允许唯一元素(两个相等元素的最高优先级优先于另一个)。这个&#34;优先级设置的代码&#34;在下面

    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)
    

0 个答案:

没有答案