PDSA - Week 5Weighted GraphWeighted directed graph Adjacency matrix representation in PythonAdjacency list representation in PythonWeighted undirected graph Adjacency matrix representation in PythonAdjacency list representation in PythonShortest PathSingle source shortest path algorithmDijkstra's AlgorithmAlgorithm StepsWorking Visualization Implementation Dijkstra's For Adjacency matrixImplementation Dijkstra's For Adjacency ListBellman Ford algorithmAlgorithm StepsWorking VisualizationImplementation of Bellman Ford for adjacency matrixImplementation of Bellman Ford for adjacency listAll pair of shortest pathFloyd-Warshall algorithmAlgorithm StepsImplementation of Floyd Warshall AlgorithmSpanning Tree(ST)Minimum Cost Spanning Tree(MCST)Prim's AlgorithmAlgorithm StepsWorking VisualizationImplementation of Prim's AlgorithmKruskal's AlgorithmAlgorithm StepsWorking VisualizationImplementation of Kruskal's Algorithm
1# Each tuple (u,v,d) in list is representing the edge from u to v with weight d
2dedges = [(0,1,10),(0,2,80),(1,2,6),(1,4,20),(2,3,70),(4,5,50),(4,6,5),(5,6,10)]
3size = 7
4import numpy as np
5W = np.zeros(shape=(size,size,2))
6for (i,j,w) in dedges:
7 W[i,j,0] = 1
8 W[i,j,1] = w
9print(W)
xxxxxxxxxx
81dedges = [(0,1,10),(0,2,80),(1,2,6),(1,4,20),(2,3,70),(4,5,50),(4,6,5),(5,6,10)]
2size = 7
3WL = {}
4for i in range(size):
5 WL[i] = []
6for (i,j,d) in dedges:
7 WL[i].append((j,d))
8print(WL)
xxxxxxxxxx
101dedges = [(0,1,10),(0,2,80),(1,2,6),(1,4,20),(2,3,70),(4,5,50),(4,6,5),(5,6,10)]
2# Each edge represented by two entry in data structure ((u,v,d) and (v,u,d) for undirected graph
3edges = dedges + [(j,i,w) for (i,j,w) in dedges]
4size = 7
5import numpy as np
6W = np.zeros(shape=(size,size,2))
7for (i,j,w) in edges:
8 W[i,j,0] = 1
9 W[i,j,1] = w
10print(W)
xxxxxxxxxx
91dedges = [(0,1,10),(0,2,80),(1,2,6),(1,4,20),(2,3,70),(4,5,50),(4,6,5),(5,6,10)]
2edges = dedges + [(j,i,w) for (i,j,w) in dedges]
3size = 7
4WL = {}
5for i in range(size):
6 WL[i] = []
7for (i,j,d) in edges:
8 WL[i].append((j,d))
9print(WL)
Find shortest paths from a source vertex to every other vertex.
Dijkstra's Algorithm
Bellman Ford algorithm
Dijkstra's algorithm is a popular algorithm for finding the shortest path between two vertices in a weighted graph. Some of its important properties and points are:
Single-source shortest path: Dijkstra's algorithm finds the shortest path from a single source vertex to all other vertices in the graph.
Weighted graph: Dijkstra's algorithm only works on weighted graphs, where each edge has a weight or cost associated with it.
Non-negative weights: Dijkstra's algorithm can only be used on graphs with non-negative edge weights. If the graph contains negative weights, Bellman-Ford algorithm is a better choice.
Greedy algorithm: Dijkstra's algorithm is a greedy algorithm that selects the vertex with the smallest distance from the starting vertex and explores its neighbors. This process is repeated until the shortest path to all vertices is found.
Optimal substructure: Dijkstra's algorithm relies on the optimal substructure property, which means that the optimal solution to a problem can be obtained by combining the optimal solutions to its subproblems.
Can handle disconnected graphs: Dijkstra's algorithm can handle disconnected graphs, but it will only find the shortest path for the connected component containing the source vertex.
Here are the steps for Dijkstra's algorithm for finding the single source shortest path:
Create a table to store the distances from the source vertex to each vertex in the graph. Initialize the source vertex with a distance of 0 and all other vertices with infinity. Also create a set of unvisited vertices and mark all vertices as unvisited.
While the set of unvisited vertices is not empty, do the following:
Select the unvisited vertex with the smallest distance from the source vertex. This vertex is now considered visited.
For each neighbor of the visited vertex that is still unvisited, calculate the distance to that neighbor by adding the weight of the edge between the visited vertex and the neighbor to the distance of the visited vertex. If this distance is smaller than the distance currently stored for the neighbor in the table, update the table with the new distance.
After updating the distances for all neighbors, mark the visited vertex as visited.
Repeat steps 3 to 5 until all vertices have been visited or the destination vertex has been visited.
Once the algorithm has visited all vertices, the table will contain the shortest distances from the source vertex to each vertex in the graph.
To find the shortest path from the source vertex to a destination vertex, backtrack from the destination vertex to the source vertex by following the path with the smallest distance at each step. This will give you the shortest path from the source vertex to the destination vertex.
Select Dijkstra(s) form left bottom menu
For given graph
x1def dijkstra(WMat,s):
2 #Initialization
3 (rows,cols,x) = WMat.shape
4 infinity = np.max(WMat)*rows+1
5 (visited,distance) = ({},{})
6 for v in range(rows):
7 (visited[v],distance[v]) = (False,infinity)
8 distance[s] = 0
9
10 # Computing shortest distance for each vertex from source
11 for u in range(rows):
12 # Find minimum distance value on vertices which are not visited
13 min_dist = min([distance[v] for v in range(rows) if not visited[v]])
14
15 # Find vertices which have minimum distance value min_dist
16 nextv_list = [v for v in range(rows)if (not visited[v]) and distance[v] == min_dist]
17
18 # Select minimum level vertex which have minimum distance value min_dist and mark visited
19 nextv = min(nextv_list)
20 visited[nextv] = True
21
22 # Check for each adjacent of nextv vertex which is not visited
23 for v in range(cols):
24 if WMat[nextv,v,0] == 1 and (not visited[v]):
25 #If distance of v through nextv is smaller than the current distance on v, then update
26 if distance[nextv] + WMat[nextv,v,1] < distance[v]:
27 distance[v] = distance[nextv] + WMat[nextv,v,1]
28 return(distance)
29
30
31
32dedges = [(0,1,10),(0,2,80),(1,2,6),(1,4,20),(2,3,70),(4,5,50),(4,6,5),(5,6,10)]
33size = 7
34import numpy as np
35W = np.zeros(shape=(size,size,2))
36for (i,j,w) in dedges:
37 W[i,j,0] = 1
38 W[i,j,1] = w
39print(dijkstra(W,0))
Output
xxxxxxxxxx
11{0: 0, 1: 10.0, 2: 16.0, 3: 86.0, 4: 30.0, 5: 80.0, 6: 35.0}
Complexity
xxxxxxxxxx
381def dijkstralist(WList,s):
2 #Initialization
3 infinity = 1 + len(WList.keys())*max([d for u in WList.keys() for (v,d) in WList[u]])
4 (visited,distance) = ({},{})
5 for v in WList.keys():
6 (visited[v],distance[v]) = (False,infinity)
7 distance[s] = 0
8
9 # Computing shortest distance for each vertex from source
10 for u in WList.keys():
11 # Find minimum distance value on vertices which are not visited
12 min_dist = min([distance[v] for v in WList.keys() if not visited[v]])
13
14 # Find vertices which have minimum distance value min_dist
15 nextv_list = [v for v in WList.keys() if (not visited[v]) and distance[v] == min_dist]
16
17 # Select minimum level vertex which have minimum distance value min_dist and mark visited
18 nextv = min(nextv_list)
19 visited[nextv] = True
20
21 # Check for each adjacent of nextv vertex which is not visited
22 for (v,d) in WList[nextv]:
23 if not visited[v]:
24 # If distance of v through nextv is smaller than the current distance of v, then update
25 if distance[nextv]+d < distance[v]:
26 distance[v] = distance[nextv]+d
27 return(distance)
28
29
30
31dedges = [(0,1,10),(0,2,80),(1,2,6),(1,4,20),(2,3,70),(4,5,50),(4,6,5),(5,6,10)]
32size = 7
33WL = {}
34for i in range(size):
35 WL[i] = []
36for (i,j,d) in dedges:
37 WL[i].append((j,d))
38print(dijkstralist(WL,0))
Output
xxxxxxxxxx
11{0: 0, 1: 10, 2: 16, 3: 86, 4: 30, 5: 80, 6: 35}
Code Execution Flow
Complexity
Bellman-Ford algorithm is a dynamic programming based algorithm to find the shortest path in a weighted graph, where the edge weights may be negative. Here are some important points and properties of the algorithm:
It can handle graphs with negative edge weights: Unlike Dijkstra's algorithm, which can only handle non-negative edge weights, the Bellman-Ford algorithm can handle graphs with negative edge weights. However, it cannot handle graphs with negative cycles, which are cycles that have a negative sum of edge weights.
It can detect negative cycles: The Bellman-Ford algorithm can detect negative cycles in a graph. If there is a negative cycle, the algorithm will report that there is no shortest path from the source vertex to some other vertex.
It can handle disconnected graphs: The Bellman-Ford algorithm can handle disconnected graphs by finding the shortest paths from the source vertex to all other vertices in each connected component.
It uses dynamic programming: The Bellman-Ford algorithm uses dynamic programming to solve the shortest path problem. It maintains an array of distances that is gradually updated until it converges to the correct values.
Bellman-Ford works for both directed and undirected graphs with non-negative edges weights.
Here are the step-by-step instructions for the Bellman-Ford algorithm:
Initialize the distance array: Set the distance of the source vertex to 0 and the distances of all other vertices to infinity.
Relax all edges: Repeat the following step (V-1) times, where V is the number of vertices in the graph. For each edge (u,v) with weight w, if the distance to u plus w is less than the distance to v, update the distance to v to the distance to u plus w.
Check for negative weight cycles: After relaxing all edges V-1 times, check for negative weight cycles. For each edge (u,v) with weight w, if the distance to u plus w is less than the distance to v, there exists a negative weight cycle.
Print the distance array: If there are no negative weight cycles, print the distance array, which contains the shortest path distances from the source vertex to all other vertices.
Select BellmanFord(s) form left bottom menu
For given graph
xxxxxxxxxx
301def bellmanford(WMat,s):
2 # Initialization
3 (rows,cols,x) = WMat.shape
4 infinity = np.max(WMat)*rows+1
5 distance = {}
6 for v in range(rows):
7 distance[v] = infinity
8 distance[s] = 0
9
10 # Computing shortest distance for each vertex from source
11 # Repeat the process n times where n is number of vertices
12 for i in range(rows):
13 # Check for each adjacent of u vertex
14 for u in range(rows):
15 for v in range(cols):
16 if WMat[u,v,0] == 1:
17 # If distance of v through u is smaller than the current distance of v, then update
18 if distance[u] + WMat[u,v,1] < distance[v]:
19 distance[v] = distance[u] + WMat[u,v,1]
20 return(distance)
21
22
23edges = [(0,1,10),(0,7,8),(1,5,2),(2,1,1),(2,3,1),(3,4,3),(4,5,-1),(5,2,-2),(6,1,-4),(6,5,-1),(7,6,1)]
24size = 8
25import numpy as np
26W = np.zeros(shape=(size,size,2))
27for (i,j,w) in edges:
28 W[i,j,0] = 1
29 W[i,j,1] = w
30print(bellmanford(W,0))
Output
xxxxxxxxxx
11{0: 0, 1: 5.0, 2: 5.0, 3: 6.0, 4: 9.0, 5: 7.0, 6: 9.0, 7: 8.0}
Complexity
xxxxxxxxxx
281def bellmanfordlist(WList,s):
2 # Initialization
3 infinity = 1 + len(WList.keys())*max([d for u in WList.keys() for (v,d) in WList[u]])
4 distance = {}
5 for v in WList.keys():
6 distance[v] = infinity
7 distance[s] = 0
8
9 # Computing shortest distance for each vertex from source
10 # Repeat the process n times where n is number of vertices
11 for i in WList.keys():
12 # Check for each adjacent of u vertex
13 for u in WList.keys():
14 for (v,d) in WList[u]:
15 # If distance of v through u is smaller than the current distance of v, then update
16 if distance[u] + d < distance[v]:
17 distance[v] = distance[u] + d
18 return(distance)
19
20
21edges = [(0,1,10),(0,7,8),(1,5,2),(2,1,1),(2,3,1),(3,4,3),(4,5,-1),(5,2,-2),(6,1,-4),(6,5,-1),(7,6,1)]
22size = 8
23WL = {}
24for i in range(size):
25 WL[i] = []
26for (i,j,d) in edges:
27 WL[i].append((j,d))
28print(bellmanfordlist(WL,0))
Output
xxxxxxxxxx
11{0: 0, 1: 5, 2: 5, 3: 6, 4: 9, 5: 7, 6: 9, 7: 8}
Code Execution Flow
Complexity
Find the shortest paths between every pair of vertices i
and j
.
It is equivalent to if run Dijkstra or Bellman-Ford from each vertex.
The Floyd-Warshall algorithm is an efficient algorithm for finding the shortest path between all pairs of nodes in a weighted graph. Some important points and properties of the Floyd-Warshall algorithm are:
All-pairs shortest path: The Floyd-Warshall algorithm computes the shortest path between all pairs of nodes in the graph.
Negative cycles: The Floyd-Warshall algorithm can detect negative cycles in the graph. A negative cycle is a cycle in the graph where the sum of the weights of the edges is negative. If there is a negative cycle in the graph, then the algorithm will report that there is a negative cycle.
Dynamic programming: The Floyd-Warshall algorithm uses dynamic programming to compute the shortest path between all pairs of nodes in the graph. The algorithm builds a table of shortest paths between pairs of nodes by iteratively considering intermediate nodes along the path.
No guarantee of uniqueness: The shortest path between two nodes in a graph may not be unique. If there are multiple shortest paths between two nodes, then the Floyd-Warshall algorithm may return any one of them.
Floyd-Warshall's works for both directed and undirected graphs with non-negative edges weights.
Floyd-Warshall's does not work with an undirected graph with negative edges weight, as it will be declared as a negative weight cycle.
Here are the steps for the algorithm:
Initialization: Create a 2-dimensional array SP
of size n x n
, where n
is the number of vertices in the graph. For each pair of vertices (i,j)
, initialize SP[i][j]
to the weight of the edge from vertex i
to vertex j
. If there is no edge between vertices i
and j
, then set SP[i][j]
to infinity.
For each vertex k
from 1
to n
, compute the shortest path between every pair of vertices (i,j)
that passes through k
. To do this, update SP[i][j]
as follows:
SP[i][j] = min(SP[i][j], SP[i][k] + SP[k][j])
This means that the shortest path from vertex i
to vertex j
that passes through k
is the minimum of the current shortest path from i
to j
and the sum of the shortest path from i
to k
and the shortest path from k
to j
.
After the step 2 is complete, the SP
array will contain the shortest path between every pair of vertices in the graph.
For given input graph
xxxxxxxxxx
331def floydwarshall(WMat):
2 # Initialization
3 (rows,cols,x) = WMat.shape
4 infinity = float('inf')
5 SP = np.zeros(shape=(rows,cols,cols+1))
6
7 # Filling the initial graph entry in matrix
8 for i in range(rows):
9 for j in range(cols):
10 if WMat[i,j,0] == 1:
11 SP[i,j,0] = WMat[i,j,1]
12 else:
13 SP[i,j,0] = infinity
14
15 # Repeat the process n times where n is number of vertices
16 for k in range(1,cols+1):
17 # Checking The shortest path distance for each pair in matrix
18 for i in range(rows):
19 for j in range(cols):
20 SP[i,j,k] = min(SP[i,j,k-1],SP[i,k-1,k-1]+SP[k-1,j,k-1])
21
22 # Retuen the last updated matrix
23 return(SP[:,:,cols])
24
25
26edges = [(0,1,10),(0,7,8),(1,5,2),(2,1,1),(2,3,1),(3,4,3),(4,5,-1),(5,2,-2),(6,1,-4),(6,5,-1),(7,6,1)]
27size = 8
28import numpy as np
29W = np.zeros(shape=(size,size,2))
30for (i,j,w) in edges:
31 W[i,j,0] = 1
32 W[i,j,1] = w
33print(floydwarshall(W))
Output
xxxxxxxxxx
81[[inf 5. 5. 6. 9. 7. 9. 8.]
2[inf 1. 0. 1. 4. 2. inf inf]
3[inf 1. 1. 1. 4. 3. inf inf]
4[inf 1. 0. 1. 3. 2. inf inf]
5[inf -2. -3. -2. 1. -1. inf inf]
6[inf -1. -2. -1. 2. 1. inf inf]
7[inf -4. -4. -3. 0. -2. inf inf]
8[inf -3. -3. -2. 1. -1. 1. inf]]
Complexity
Retain a minimal set of edges so that graph remains connected
Recall that a minimally connected graph is a tree
Adding an edge to a tree creates a loop
Removing an edge disconnects the graph
Want a tree that connects all the vertices — spanning tree
More than one spanning tree, in general
Add the cost of all the edges in the tree
Among the different spanning trees, choose one with minimum cost
Some facts about trees
A tree on n vertices has exactly n − 1 edges
Adding an edge to a tree must create a cycle.
In a tree, every pair of vertices is connected by a unique path
Algorithms:-
Prim's Algorithm
Kruskal's Algorithm
Prim's algorithm is a greedy algorithm that finds a minimum spanning tree (MST) for a weighted undirected graph. Here are some of the key points or properties of Prim's algorithm:
Greedy approach: Prim's algorithm is a greedy algorithm that works by building up the MST one vertex at a time, always choosing the cheapest edge that connects the growing tree to an outside vertex.
Works on connected graphs: Prim's algorithm only works on connected graphs, meaning that there must be a path between any two vertices in the graph.
Weighted edges: Prim's algorithm requires that the graph have weighted edges. The weights represent the cost or distance of traveling between two vertices.
Spanning tree: Prim's algorithm always produces a spanning tree, which is a subset of the edges that connects all vertices in the graph and contains no cycles.
Unique solution: If the weights of the edges are unique, then the MST produced by Prim's algorithm is unique.
Here are the steps for the algorithm:
Choose any vertex as the starting vertex and add it to the MST set. Initialize the weight of this vertex to 0.
Find the edge with the minimum weight that connects the starting vertex to any other vertex.
Add the adjacent vertex connected by the minimum weight edge to the MST set. Set its weight to the weight of the edge.
Repeat steps 2 and 3 until all vertices are included in the MST set.
Select Prim's Algorithm from bottom-left menu
Source:- https://visualgo.net/en/mst
For given input graph
xxxxxxxxxx
451def primlist(WList):
2 # Initialization
3 infinity = 1 + max([d for u in WList.keys() for (v,d) in WList[u]])
4 (visited,distance,TreeEdges) = ({},{},[])
5 for v in WList.keys():
6 (visited[v],distance[v]) = (False,infinity)
7
8 # Start from vertex 0, marked visited and update the distance of adjacents from 0
9 visited[0] = True
10 for (v,d) in WList[0]:
11 distance[v] = d
12
13 # Repeat the below process (number of vertices-1) times
14 for i in range(1,len(WList.keys())):
15 mindist = infinity
16 nextv = None
17 #Finding the minimum weight edge (u,v) where vertex u is visited and v is not visited
18 for u in WList.keys():
19 for (v,d) in WList[u]:
20 if visited[u] and (not visited[v]) and d < mindist:
21 mindist = d
22 nextv = v
23 nexte = (u,v)
24
25 # Mark nextv as visited and append the nexte in MST
26 visited[nextv] = True
27 TreeEdges.append(nexte)
28
29 # Update the distance of unvisited adjacents of nextv if smaller than current
30 for (v,d) in WList[nextv]:
31 if not visited[v]:
32 if d < distance[v]:
33 distance[v] = d
34 return(TreeEdges)
35
36
37dedges = [(0,1,10),(0,3,18),(1,2,20),(1,3,6),(2,4,8),(3,4,70)]
38edges = dedges + [(j,i,w) for (i,j,w) in dedges]
39size = 5
40WL = {}
41for i in range(size):
42 WL[i] = []
43for (i,j,d) in edges:
44 WL[i].append((j,d))
45print(primlist(WL))
Output
xxxxxxxxxx
11[(0, 1), (1, 3), (1, 2), (2, 4)]
Output minimum spanning tree with cost 44
Code Execution Flow
Other Implementation
xxxxxxxxxx
411def primlist2(WList):
2 # Initialization
3 infinity = float("inf")
4 (visited,distance,nbr) = ({},{},{})
5 for v in WList.keys():
6 (visited[v],distance[v],nbr[v]) = (False,infinity,-1)
7
8 # Set start vertex distance to 0
9 distance[0] = 0
10
11 # Repeat the below process (number of vertices-1) times
12 for i in range(0,len(WList.keys())):
13 # Find minimum distance value on vertices which are not visited
14 min_dist = min([distance[v] for v in WList.keys() if not visited[v]])
15
16 # Find all vertices that have minimum distance value min_dist and not visited
17 nextv_list = [v for v in WList.keys() if (not visited[v]) and distance[v] == min_dist]
18
19 # Select the minimum level value vertex from nextv_list ans mark visited
20 nextv = min(nextv_list)
21 visited[nextv] = True
22
23 # Update the nbr or parent value for v with minimum eadge distance
24 for (v,d) in WList[nextv]:
25 if not visited[v]:
26 if d < distance[v]:
27 nbr[v] = nextv
28 distance[v] = d
29 return(nbr)
30
31
32
33dedges = [(0,1,10),(0,3,18),(1,2,20),(1,3,6),(2,4,8),(3,4,70)]
34edges = dedges + [(j,i,w) for (i,j,w) in dedges]
35size = 5
36WL = {}
37for i in range(size):
38 WL[i] = []
39for (i,j,d) in edges:
40 WL[i].append((j,d))
41print(primlist2(WL))
Output
xxxxxxxxxx
11{0: -1, 1: 0, 2: 1, 3: 1, 4: 2}
Code Execution Flow
Complexity
Kruskal's algorithm is a greedy algorithm used to find the minimum spanning tree (MST) of a connected, undirected graph. The algorithm works by iteratively adding the edges of the graph with the smallest weights that do not create a cycle in the MST. Here are some important points and properties of Kruskal's algorithm:
Kruskal's algorithm is a greedy algorithm used to find the minimum spanning tree (MST) of a connected, undirected graph.
The algorithm works by iteratively adding the edges of the graph with the smallest weights that do not create a cycle in the MST.
Kruskal's algorithm guarantees that the MST is unique if the graph has unique edge weights.
Here are the steps for the of Kruskal's algorithm:
Sort all the edges of the graph in non-decreasing order of their weights.
Create a new empty set for the minimum spanning tree (MST).
Iterate through the sorted edges, starting with the edge with the smallest weight.
For each edge, check if adding it to the MST will create a cycle.
If the edge does not create a cycle, add it to the MST.
Repeat steps 4 and 5 until all vertices are included in the MST, or until the desired number of edges have been added.
Return the MST.
Select Kruskal's Algorithm from bottom-left menu
Source:- https://visualgo.net/en/mst
For given input graph
xxxxxxxxxx
331def kruskal(WList):
2 # Initialization
3 (edges,component,TE) = ([],{},[])
4 for u in WList.keys():
5 # Weight as first value in tuple to sort easily
6 edges.extend([(d,u,v) for (v,d) in WList[u]])
7 # Initially each vertex as single components and assign leader of each component
8 component[u] = u
9
10 # Sort the edges in increasing order of their weights
11 edges.sort()
12
13 for (d,u,v) in edges:
14 # If (u,v) edge is not creating the cycle in MST, add the edge in MST
15 if component[u] != component[v]:
16 TE.append((u,v))
17 c = component[u]
18 # Update of component leader
19 for w in WList.keys():
20 if component[w] == c:
21 component[w] = component[v]
22 return(TE)
23
24
25dedges = [(0,1,10),(0,2,18),(1,2,6),(1,4,20),(2,3,70),(4,5,10),(4,6,10),(5,6,5)]
26edges = dedges + [(j,i,w) for (i,j,w) in dedges]
27size = 7
28WL = {}
29for i in range(size):
30 WL[i] = []
31for (i,j,d) in edges:
32 WL[i].append((j,d))
33print(kruskal(WL))
Output
xxxxxxxxxx
11[(5, 6), (1, 2), (0, 1), (4, 5), (1, 4), (2, 3)]
Output minimum spanning tree with cost 121
Code Execution Flow
Complexity