I am implementing a Bellman-ford shortest path algorithm. Based on the source and destination node, it outputs the shortest distance, and the path through a network.
Now, I need to add a capacity component to the algorithm. So if the demand is 2 but the capacity is 1, that path is no longer usable.
My initial idea was to add a dictionary for the capacity and a variable for the demand. Then if the demand exceeded the capacity of a node, the lenght of the path would be arbritrarily large. I was thinking something like:
if capacity[neighbour] < demand:
distance[neighbour], predecessor[neighbour] = 999
This gives me the following error message:
TypeError: '<' not supported between instances of 'dict' and 'int'
Is there a workaround for this issue, or could I potentially add the demand-constraint in a smarter way?
Full code:
source = 'e'
destination = 'd'
demand = 2
def bellman_ford(graph, source, capacity):
# Step 1: Prepare the distance and predecessor for each node
distance, predecessor = dict(), dict()
for node in capacity:
for node in graph:
distance[node], predecessor[node] = float('inf'), None
distance[source] = 0
# Step 2: Relax the edges
for _ in range(len(graph) - 1):
for node in graph:
for neighbour in graph[node]:
# If the distance between the node and the neighbour is lower than the current, store it
if distance[neighbour] > distance[node] + graph[node][neighbour]:
distance[neighbour], predecessor[neighbour] = distance[node] + graph[node][neighbour], node
if capacity[node] < demand:
distance[neighbour], predecessor[neighbour] = 100
# Step 3: Check for negative weight cycles
for node in graph:
for neighbour in graph[node]:
assert distance[neighbour] <= distance[node] + graph[node][neighbour], "Negative weight cycle."
return distance, predecessor
#Initial graph
graph = {
'a': {'b': 1, 'd': 1},
'b': {'c': 1, 'd': 2},
'c': {},
'd': {'b': 1, 'c': 8, 'e': 1},
'e': {'a': 2, 'd': 7}
}
capacity = {
'a': {'b': 4, 'd': 1},
'b': {'c': 5, 'd': 4},
'c': {},
'd': {'b': 1, 'c': 3, 'e': 3},
'e': {'a': 5, 'd': 3}
}
distance, predecessor = bellman_ford(graph, source, capacity)
print("The cost of shipping from from", source, "to", destination, "is", distance[destination])
for i in graph:
print("node",i,"is reached through node", predecessor[i])
Related
I am trying to get the following code working. After every for ends heappop gives me an integer instead of Vertex. In addition when I got it working, with changing the Vertex in the priority queue with integer. I have wrong result. Please help.
Thanks in advance
import heapq
class Vertex:
def __init__(self, id):
self.id = id
self.adjList = []
self.adjWeights = []
def shortestPath(vertices, N, source, destination):
distTo = [float('inf') for _ in range(N+1)]
edgeTo = [float('inf') for _ in range(N+1)]
# Set initial distance from source
# to the highest value
distTo[source] = 0.0
edgeTo[source] = float('inf')
pq = [vertices[source]]
heapq.heapify(pq)
while True:
closest = heapq.heappop(pq)
for i in range(len(closest.adjList)):
# Checks if the edges are decreasing and
# whether the current directed edge will
# create a shorter path
if closest.adjWeights[i] < edgeTo[closest.id] and distTo[closest.id] + closest.adjWeights[i] < distTo[closest.adjList[i]]:
edgeTo[closest.adjList[i]] = closest.adjWeights[i]
distTo[closest.adjList[i]] = closest.adjWeights[i] + distTo[closest.id];
heapq.heappush(pq, closest.adjList[i])
print(distTo)
print(distTo[destination])
def main ()
N = 6
M = 9
'''
edges = {{0, 2, 1.1}, {0, 4, 2}, {0, 5, 3.3}, {1, 4, 2.7},
{2, 3, 2}, {2, 4, 1.1}, {3, 1, 2.3}, {4, 5, 2.4}, {5, 1, 3}}
'''
# Create an array of vertices
vertices = [Vertex(i) for i in range(0, N)]
i=0
vertices[0].adjList.append(2)
vertices[0].adjWeights.append(1.1)
vertices[0].adjList.append(4)
vertices[0].adjWeights.append(2.0)
vertices[0].adjList.append(5)
vertices[0].adjWeights.append(3.3)
vertices[1].adjList.append(4)
vertices[1].adjWeights.append(2.7)
vertices[2].adjList.append(3)
vertices[2].adjWeights.append(2.0)
vertices[2].adjList.append(4)
vertices[2].adjWeights.append(1.1)
vertices[3].adjList.append(1)
vertices[3].adjWeights.append(2.3)
vertices[4].adjList.append(5)
vertices[4].adjWeights.append(2.4)
vertices[5].adjList.append(1)
vertices[5].adjWeights.append(3.0)
# Source and destination vertices
src = 0
target = 1
print(shortestPath(vertices, N, src, target))
I'm currently trying to make an algorithm that gives me the total cost of a graph when all nodes have been visited but am failing miserably and honestly out of ideas. My goal is to get the total costs of the graphs below, using the Dijkstra algorithm.
Here's what I have so far:
from collections import defaultdict
import heapq
def build_graph():
# Create the graph with the all given nodes and costs
edges = aeroDistances
graph = defaultdict(dict)
for edge in edges.items():
tuple1 = edge[0][0]
tuple2 = edge[0][1]
cost = edge[1]
connection1 = {tuple2 : cost}
connection2 = {tuple1 : cost}
graph[tuple1].update(connection1)
graph[tuple2].update(connection2)
return dict(graph)
def dijkstra(graph, starting_vertex):
# All the distances set to infinity
distances = {vertex: float('infinity') for vertex in graph}
# Distance from the starting vertex
distances[starting_vertex] = 0
# Priority queue
pq = [(0, starting_vertex)]
while len(pq) > 0:
current_distance, current_vertex = heapq.heappop(pq)
# Nodes can get added to the priority queue multiple times. We only
# process a vertex the first time we remove it from the priority queue
if current_distance > distances[current_vertex]:
continue
for neighbor, weight in graph[current_vertex].items():
distance = current_distance + weight
# Only consider this new path if it's better than any path we've
# already found
if distance < distances[neighbor]:
distances[neighbor] = distance
heapq.heappush(pq, (distance, neighbor))
return distances, distance
numCidades = 0
numAeroportos = 0
numEstradas = 0
#custoAeroportos = {}
#custoEstradas = {}
#custoAeroportos = {(1, 2): 2, (1, 3): 4}
#custoEstradas = {(3, 1, 'E'): 2}
#custoAeroportos = {(1, 2): 1, (2, 3): 2, (3, 4): 1, (4, 1): 1}
custoAeroportos = {(1, 2): 1, (1, 3): 6, (2, 4): 2, (3, 4): 2}
custoEstradas = {(2, 3): 3}
listCidades = [1,2,3]
distances = []
indexValue = 0
indexKey = 0
currentIndex = 0
# Deconstruct the dict into a list of keys (tuples)
# Deconstruct the dict into a list of values
# Make it easier to sort the connections by creating a list of tuples and
# their respective weights and zip them toghether
distancesAeroKeys = list(custoAeroportos.keys())
distancesAeroValues = list(custoAeroportos.values())
aeroDistances = dict(map(list, zip(*[distancesAeroKeys, distancesAeroValues])))
print()
print("AeroDistances: " + str(aeroDistances))
graph = build_graph()
print()
print("Graph: " + str(graph))
print()
print("Dijkstra: " + str(dijkstra(graph, 1)))
The two graphs, dicts, I'm currently trying this with are named custoAeroportos and I can't seem to get the total minimum cost when all nodes are visited.
Here're the graphs, they are fairly simple:
This one has a total cost of 5
This one has a total cost of 3
The total cost I'm getting is wrong and I can't figure it out.
For the first graph:
AeroDistances: {(1, 2): 1, (1, 3): 6, (2, 4): 2, (3, 4): 2}
Graph: {1: {2: 1, 3: 6}, 2: {1: 1, 4: 2}, 3: {1: 6, 4: 2}, 4: {2: 2, 3: 2}}
Dijkstra: ({1: 0, 2: 1, 3: 5, 4: 3}, 7)
For the second graph, which somehow is correct:
AeroDistances: {(1, 2): 1, (2, 3): 2, (3, 4): 1, (4, 1): 1}
Graph: {1: {2: 1, 4: 1}, 2: {1: 1, 3: 2}, 3: {2: 2, 4: 1}, 4: {3: 1, 1: 1}}
Dijkstra: ({1: 0, 2: 1, 3: 2, 4: 1}, 3)
I really appreciate your help, thank you.
Your function returns the distance of the path from the starting vertex to whichever was the last node that was added to the heap. This is not really what you want to return. Certainly when the BFS-tree has multiple outgoing edges from some vertices, this path has little to do with the total distance.
Instead you need to accumulate the weights of the edges that are "accepted", i.e. those that are (implicitly) popped from the heap and improve the distance for that node.
So I would suggest extending the tuples on the heap with one more information: the weight of the last edge that brought us to that node. When the node is accepted, then this edge becomes part of the spanning tree, and its weight should then be added to an accumulating total.
Here is the adapted code. The changes have accompanying comments:
def dijkstra(graph, starting_vertex):
distances = {vertex: float('infinity') for vertex in graph}
distances[starting_vertex] = 0
graph_distance = 0 # this will be returned
pq = [(0, 0, starting_vertex)] # middle value is edge weight
while len(pq) > 0:
current_distance, edge_weight, current_vertex = heapq.heappop(pq)
if current_distance > distances[current_vertex]:
continue
graph_distance += edge_weight # accumulate
for neighbor, weight in graph[current_vertex].items():
distance = current_distance + weight
if distance < distances[neighbor]:
distances[neighbor] = distance
heapq.heappush(pq, (distance, weight, neighbor)) # include weight
return distances, graph_distance # ...return it
I would like to understand in the following WORKING AND FINISHED code, why when updating pq_update, it is written as pq_update[neighbour][1].
Instead of writing pq_update[neighbour] (which is how I did it), it does not seem to change anything so why is it included ?
Thank you
import heapq
def dijkstra(graph, start):
distances = {vertex:float('inf') for vertex in graph}
pq = []
pq_update = {}
distances[start] = 0
for vertex, value in distances.items():
entry = [vertex, value]
heapq.heappush(pq, entry)
pq_update[vertex] = entry
while pq:
getmin = heapq.heappop(pq)[0]
for neighbour, distance_neigh in graph[getmin].items():
dist = distances[getmin] + distance_neigh
if dist < distances[neighbour]:
distances[neighbour] = dist
pq_update[neighbour][1] = dist # THIS LINE !!!
print(distances)
return distances
if __name__ == '__main__':
example_graph = {
'U': {'V': 2, 'W': 5, 'X': 1},
'V': {'U': 2, 'X': 2, 'W': 3},
'W': {'V': 3, 'U': 5, 'X': 3, 'Y': 1, 'Z': 5},
'X': {'U': 1, 'V': 2, 'W': 3, 'Y': 1},
'Y': {'X': 1, 'W': 1, 'Z': 1},
'Z': {'W': 5, 'Y': 1},
}
dijkstra(example_graph, 'X')
Note: the implementation you have is broken and doesn't correctly implement Dijkstra. More on that below.
The pq_update dictionary contains lists, each with two entries:
for vertex, value in distances.items():
entry = [vertex, value]
heapq.heappush(pq, entry)
pq_update[vertex] = entry
So pq_update[neighbour] is a list with both the vertex and the distance. You want to update the distance, not replace the [vertex, value] list, so pq_update[neighbour][1] is used.
Note that the entry list is also shared wit the heapq. The pq heap has a reference to the same list object, so changes to pq_update[neightbor][1] will also be visible in entries still to be processed on heap!
When you assign directly to pq_update[neighbour], you remove that connection.
The reason you don't see any difference is because the implementation of the algorithm is actually broken, as the heap is not used correctly. The heap is sorted by first by the first value in the list items you pushed in. In your code that's the node name, not the distance, and the heapq order of items is never updated when the distances in the list items are altered. Because the heapq is not used correctly, you always traverse the nodes in alphabetical order.
To use the heapq correctly, you need to put the edge length first, and you don't alter the values on the heap; if you use tuples you can't accidentally do this. You only need to push nodes onto the heap that you reached, really; you'll end up with multiple entries for some of the nodes (reached by multiple paths), but the heapq will still present the shortest path to that node first. Just keep a set of visited nodes so you know to skip any longer paths. The point is that you visit the shorter path to a given node before the longer path, and you don't need to alter the heapq items in-place to achieve that.
You could re-write your function (with better variable names) to:
def dijkstra(graph, start):
"""Visit all nodes and calculate the shortest paths to each from start"""
queue = [(0, start)]
distances = {start: 0}
visited = set()
while queue:
_, node = heapq.heappop(queue) # (distance, node), ignore distance
if node in visited:
continue
visited.add(node)
dist = distances[node]
for neighbour, neighbour_dist in graph[node].items():
if neighbour in visited:
continue
neighbour_dist += dist
if neighbour_dist < distances.get(neighbour, float('inf')):
heapq.heappush(queue, (neighbour_dist, neighbour))
distances[neighbour] = neighbour_dist
return distances
I have a cyclical directed graph. Below is the representation of the graph as a python dict
graph = {
'A': {'B': 5, 'D': 5, 'E': 7 },
'B': {'C': 4},
'C': {'D': 8, 'E': 2},
'D': {'C': 8, 'E': 6},
'E': {'B': 3}
}
I have wrote a simple implementation of a Dijkstra's shortest path. Which seems to work for given two points. Below is my implementation.
def shortestpath(self, start, end, visited=[],distances={},predecessors={}):
# initialize a big number
maxint = 10000
if start==end:
path=[]
while end != None:
path.append(end)
end=predecessors.get(end, None)
return distances[start], path[::-1]
# detect if it's the first time through, set current distance to zero
if not visited: distances[start]=0
# process neighbors as per algorithm, keep track of predecessors
for neighbor in self.graph[start]:
if neighbor not in visited:
neighbordist = distances.get(neighbor,maxint)
tentativedist = distances[start] + self.graph[start][neighbor]
if tentativedist < neighbordist:
distances[neighbor] = tentativedist
predecessors[neighbor]=start
# neighbors processed, now mark the current node as visited
visited.append(start)
# finds the closest unvisited node to the start
unvisiteds = dict((k, distances.get(k,maxint)) for k in self.graph if k not in visited)
closestnode = min(unvisiteds, key=unvisiteds.get)
# now we can take the closest node and recurse, making it current
return self.shortestpath(closestnode,end,visited,distances,predecessors)
now this simple implementation seems to work. For example if I do somthing like this
shortestpath('A', 'C')
it will give me the path and shortest weight
(9, ['A', 'B', 'C'])
in this case.
However, whenever I shortestpath('B', 'B') the program will break.
Now there is a shortest path from B to B since it is a cyclic graph the path is B-C-E-B. I just don't know how to check for that and modify the Dijktra's algorithm accordingly to have it check for cyclic cases like this one. Any suggestion is greatly appreciated. Thanks :)
I am making a uniform cost search algorithm (for fun, not an assignment or anything) and I have an array that keeps tracks of the nodes and edge values that it passes. However I am trying to get it to add the edge values together based on the path it took.
How does one get the parent node based on the current node and edge value?
Trail of nodes it has visited (for example) [Started from A, visited C, and now on B (from A)]:
Starting point: B
Graph of current node: {'A': 3, 'D': 8}
Trail {'A': 0, 'C': 2, 'B': 3}
Nodes_to_expand {'C': 2, 'B': 3, 'E': 5, 'D': 6}
Graph (Python dictionary):
graph = {'A': {'B':3, 'C':2, 'D': 6},
'B': {'A':3, 'D':8},
'C': {'D':7, 'E':5},
'D': {'E':-2},
'E':{}}