# 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),