Least Cost Algorithms
Introduction
There are two outstanding algorithms for computing the shortest paths from a source node in a graph. These two algorithms are the Dijkstra’s algorihtm and the Bellman-Ford algorithm. The two algorithms converges to an identical sink tree rooted at the source node.
Dijkstra’s Algorithm
Below is a Python implementation of the Dijkstra’s algorithm. This implementation requires that the graph is connected otherwise the algorithm will be in an infinite loop.
#
# dijkstra.py
#
import pandas as pd
import networkx as nx
import matplotlib.pyplot as plt
def dijkstra(graph, srcid):
node_list = [] # list of nodes to work on
dist_to_src = dict() # distance to source on shortest paths
prec_node_to_dst = dict() # preceding node to destination on shortest paths
for node in graph.nodes:
dist_to_src[node] = float('inf')
prec_node_to_dst[node] = None
node_list.append(node)
dist_to_src[list(graph.nodes)[srcid]] = 0
while node_list:
(curr_node,path_cost) = \
min([(node,dist_to_src[node]) \
for node in node_list], key = lambda t: t[1])
print('(current node,path cost to source) = ', (curr_node,path_cost))
node_list.remove(curr_node)
print('node_list = ', node_list)
for neighbor in graph.neighbors(curr_node):
link_cost = graph.get_edge_data(curr_node, neighbor)[0]['cost']
alt_cost = dist_to_src[curr_node] + link_cost
if alt_cost < dist_to_src[neighbor]:
# path to the neighbor with shorter dist_to_srcance found
dist_to_src[neighbor] = alt_cost
prec_node_to_dst[neighbor] = curr_node
print('dist_to_src = ', dist_to_src)
print('prec_node_to_dst = ', prec_node_to_dst)
return dist_to_src, prec_node_to_dst
def plot_graph(graph):
plt.subplot(121)
pos = nx.spring_layout(graph)
nx.draw(graph, pos, with_labels=True)
edge_labels=dict([((u,v,),d['cost']) for u,v,d in graph.edges(data=True)])
nx.draw_networkx_edge_labels(graph, pos, edge_labels=edge_labels, \
label_pos=0.3, font_size=7)
if __name__=="__main__":
edgesDf = pd.read_csv('graph_edges.csv')
nodesDf = pd.read_csv('graph_nodes.csv')
graph = nx.MultiDiGraph()
for i, node in nodesDf.iterrows():
graph.add_node(node['id'])
for i,edge in edgesDf.iterrows():
graph.add_edge(edge['src'], edge['dst'], cost=edge['cost'])
nodeName = 'v1'
srcid = [i for i,name in enumerate(graph.nodes()) if name == nodeName]
dist_to_src,prec_node_to_dst = dijkstra(graph, srcid[0])
print('dist_to_src = ', dist_to_src)
print('prec_node_to_dst = ', prec_node_to_dst)
plot_graph(graph)
# show graph. this is blocking
plt.show()
The program takes a graph edge file and a graph node file, both of them
are in the CSV format (i.e., the comma-separated-value format). Below is
an example of a graph edge file called graph_edges.csv
.
src,dst,cost
v1,v2,2
v2,v1,3
v1,v4,1
v4,v1,7
v2,v3,3
v3,v2,6
v4,v2,2
v2,v4,2
v4,v3,3
v3,v4,3
v4,v5,1
v5,v4,1
v3,v6,5
v6,v3,8
v3,v5,1
v5,v3,1
v5,v6,2
v6,v5,4
The following is an example of a graph node file called graph_nodes.csv
.
id,x,y
v1,0,0
v2,10,10
v3,20,10
v4,10,-10
v5,20,-10
v6,0,30
Notice that columns x and y are unused in the program above and can be safely removed. The intention of these two columns is to control the layout of the graph when we plot it.
Exercise and Exploration
The dijkstra.py program finds the shortest paths from a source node to all nodes in a graph. What are the challenges to apply the algorithm like this to solve the routing problem?
Programming
The dijkstra.py returns two list, one is the list of the costs of the shortest
paths to all node in the graph from the source node (i.e., the dist_to_src
in
the program), the other the list of the preceding node to the desination node
along a shortest path (i.e., the prec_node_to_dst
. Following the Bellman’s
optimality we can obtain the shortest path to all destination nodes from the
source. Here are two exercises,
- Add a function to the dijkstra.py program to build the graph of the sink tree from
the source node, e.g., the function with the following interface,
build_shortest_paths_tree(graph, prec_node_to_dst)
- What minor modification can you apply to the dijkstra.py program so that the program also works for graphs that are not connected.
Bellman-Ford Algorithm
Below is a Python implementation of the Bellman-Ford algorithm.
import pandas as pd
import networkx as nx
import matplotlib.pyplot as plt
def bellmanford(graph, srcid):
node_dict = dict() # list of nodes to work on
dist_to_src = dict() # distance to source on shortest paths
prec_node_to_dst = dict() # preceding node to destination on shortest paths
for node in graph.nodes:
dist_to_src[node] = float('inf')
prec_node_to_dst[node] = None
node_dict[str(node)] = node
dist_to_src[list(graph.nodes)[srcid]] = 0
for node_key in node_dict:
node_u = node_dict[node_key]
edges_v = graph.out_edges(node_u, data=True)
for edge in edges_v:
edge_cost = edge[2]['cost']
node_v = node_dict[str(edge[1])]
distance = dist_to_src[node_u] + edge_cost
if distance < dist_to_src[node_v]:
dist_to_src[node_v] = distance
prec_node_to_dst[node_v] = node_u
# logic to detect negative cycle ignored
return dist_to_src, prec_node_to_dst
def plot_graph(graph):
plt.subplot(121)
pos = nx.spring_layout(graph)
nx.draw(graph, pos, with_labels=True)
edge_labels=dict([((u,v,),d['cost']) for u,v,d in graph.edges(data=True)])
nx.draw_networkx_edge_labels(graph, pos, edge_labels=edge_labels, \
label_pos=0.3, font_size=7)
if __name__=="__main__":
edgesDf = pd.read_csv('graph_edges.csv')
nodesDf = pd.read_csv('graph_nodes.csv')
graph = nx.MultiDiGraph()
for i, node in nodesDf.iterrows():
graph.add_node(node['id'])
for i,edge in edgesDf.iterrows():
graph.add_edge(edge['src'], edge['dst'], cost=edge['cost'])
nodeName = 'v1'
srcid = [i for i,name in enumerate(graph.nodes()) if name == nodeName]
dist_to_src,prec_node_to_dst = bellmanford(graph, srcid[0])
print('dist_to_src = ', dist_to_src)
print('prec_node_to_dst = ', prec_node_to_dst)
plot_graph(graph)
# show graph. this is blocking
plt.show()
Execise and Exploration
The Bellman-Ford algorithms permits a graph to have negative weight. It can detect whether there is a negative cycle reachable from a source node. The logic is negelected in the above program.
- What is a negative cycle? Can you give a graph that contains a negative cycle?
- Can you add logic to the graph to detect the existence of a negative cycle?
Program Files and Sample Graph Files
For your convenience, you may download these files below (using your favorite Web browser or wget or some other command line tools),