我想找到从图中的源节点到负循环的最短路径,而不是两次循环任何循环。 如果有明确的答案,请回答。否则,我想表明我是如何处理这个问题的,希望其他人可以告诉我我的错误。
下面是我的代码的简化版本,它实现了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
我想在图中找到每个负循环的最小加权路径。我相信这可以通过在完成负循环之后完成,
迭代第二个最小加权路径到源(因为第一个导致负循环)。
要从循环到源(而不是从源到循环),我添加了一个predecessor_from数据结构,它跟踪从每个节点到源的最短路径(而predecessor_to跟踪从源到源的最短路径)节点)。
我创建了一个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)