0% found this document useful (0 votes)
19 views

Graphs & Algorithms

Uploaded by

MEENU
Copyright
© © All Rights Reserved
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
19 views

Graphs & Algorithms

Uploaded by

MEENU
Copyright
© © All Rights Reserved
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
You are on page 1/ 14

Graph

Graph Data Structure is a collection of nodes connected by edges. It’s used to represent
relationships between different entities. Graph algorithms are methods used to manipulate and
analyze graphs, solving various problems like finding the shortest path or detecting cycles.

Components of a Graph:
 Vertices: Vertices are the fundamental units of the graph. Sometimes, vertices are also known as
vertex or nodes. Every node/vertex can be labeled or unlabeled.
 Edges: Edges are drawn or used to connect two nodes of the graph. It can be ordered pair of
nodes in a directed graph. Edges can connect any two nodes in any possible way. There are no
rules. Sometimes, edges are also known as arcs. Every edge can be labelled/unlabelled.

Operations on Graphs:
Basic Operations:
 Insertion of Nodes/Edges in the graph – Insert a node into the graph.
 Deletion of Nodes/Edges in the graph – Delete a node from the graph.
 Searching on Graphs – Search an entity in the graph.
 Traversal of Graphs – Traversing all the nodes in the graph.
More Operations:
 Shortest Paths : From a source to a destination, a source to all other nodes and between all pairs.
 Minimum Spanning Tree : In a weighted, connected undirected graph, finding the minimum weight
edges to connect all.

1. Graph Representation
Graphs can be represented in various ways, with the two most common being adjacency matrices and
adjacency lists.

a. Adjacency Matrix

 Structure: A 2D array where cell (i,j)(i, j)(i,j) indicates the presence (1) or absence (0) of an edge
between vertex iii and vertex jjj.
 Space Complexity: O(V2)O(V^2)O(V2), where VVV is the number of vertices.
 Usage: Efficient for dense graphs.

Figure:

A B C D
A 0 1 1 0
B 1 0 0 1
C 1 0 0 1
D 0 1 1 0
b. Adjacency List

 Structure: An array (or list) where each index represents a vertex and contains a list of adjacent
vertices.
 Space Complexity: O(V+E)O(V + E)O(V+E), where EEE is the number of edges.
 Usage: Efficient for sparse graphs.

Figure:

A: [B, C]
B: [A, D]
C: [A, D]
D: [B, C]

2. Minimum Spanning Tree (MST)


An MST connects all vertices with the minimum possible total edge weight and no cycles.

Algorithms:

a. Kruskal’s Algorithm

1. Sort all edges in non-decreasing order of their weight.


2. Pick the smallest edge. Check for cycles using Union-Find.
3. Repeat until V−1V - 1V−1 edges are picked.

In Kruskal’s algorithm, sort all edges of the given graph in increasing order. Then it keeps on adding
new edges and nodes in the MST if the newly added edge does not form a cycle. It picks the
minimum weighted edge at first and the maximum weighted edge at last. Thus we can say that it
makes a locally optimal choice in each step in order to find the optimal solution. Hence this is
a Greedy Algorithm.
How to find MST using Kruskal’s algorithm?
Below are the steps for finding MST using Kruskal’s algorithm:
1. Sort all the edges in non-decreasing order of their weight.
2. Pick the smallest edge. Check if it forms a cycle with the spanning tree formed so far. If the cycle
is not formed, include this edge. Else, discard it.
3. Repeat step#2 until there are (V-1) edges in the spanning tree.

Weight Source Destination


1 7 6
2 8 2
2 6 5
4 0 1
4 2 5
6 8 6
7 2 3
7 7 8
8 0 7
8 1 2
9 3 4
10 5 4
11 1 7
14 3 5
Time Complexity: O(E * logE) or O(E * logV)
 Sorting of edges takes O(E * logE) time.
 After sorting, we iterate through all edges and apply the find-union algorithm. The find and union
operations can take at most O(logV) time.
 So overall complexity is O(E * logE + E * logV) time.
 The value of E can be at most O(V 2), so O(logV) and O(logE) are the same. Therefore, the overall
time complexity is O(E * logE) or O(E*logV)
Auxiliary Space: O(V + E), where V is the number of vertices and E is the number of edges in the
graph.

b. Prim’s Algorithm

1. Start from an arbitrary vertex.


2. Grow the MST by adding the smallest edge from the tree to a vertex not yet in the tree.
3. Repeat until all vertices are included.

Prim's Algorithm is a greedy algorithm that is used to find the minimum spanning tree from a graph. Prim's algorithm finds
the subset of edges that includes every vertex of the graph such that the sum of the weights of the edges can be minimized.

Prim's algorithm starts with the single node and explores all the adjacent nodes with all the connecting edges at every step.
The edges with the minimal weights causing no cycles in the graph got selected.

How does the prim's algorithm work?

Prim's algorithm is a greedy algorithm that starts from one vertex and continue to add the edges with the smallest weight
until the goal is reached. The steps to implement the prim's algorithm are given as follows -

o First, we have to initialize an MST with the randomly chosen vertex.


o Now, we have to find all the edges that connect the tree in the above step with the new vertices. From the
edges found, select the minimum edge and add it to the tree.
o Repeat step 2 until the minimum spanning tree is formed.

The applications of prim's algorithm are -

o Prim's algorithm can be used in network designing.


o It can be used to make network cycles.
o It can also be used to lay down electrical wiring cables.

Example of prim's algorithm

Now, let's see the working of prim's algorithm using an example. It will be easier to understand the prim's algorithm using an
example.
Cost of MST = 4 + 2 + 1 + 3 = 10 units.

Algorithm

1. Step 1: Select a starting vertex


2. Step 2: Repeat Steps 3 and 4 until there are fringe vertices
3. Step 3: Select an edge 'e' connecting the tree vertex and fringe vertex that has minimum weight
4. Step 4: Add the selected edge and the vertex to the minimum spanning tree T
5. [END OF LOOP]
6. Step 5: EXIT

Complexity of Prim's algorithm

Sample Program (Prim’s Algorithm)


import sys

class Graph:
def __init__(self, vertices):
self.V = vertices
self.graph = [[0] * vertices for _ in range(vertices)]

def add_edge(self, u, v, w):


self.graph[u][v] = w
self.graph[v][u] = w # Undirected graph

def min_key(self, key, mst_set):


min_value = sys.maxsize
min_index = -1

for v in range(self.V):
if key[v] < min_value and not mst_set[v]:
min_value = key[v]
min_index = v
return min_index

def prim_mst(self):
key = [sys.maxsize] * self.V
parent = [-1] * self.V
key[0] = 0
mst_set = [False] * self.V

for _ in range(self.V):
u = self.min_key(key, mst_set)
mst_set[u] = True

for v in range(self.V):
if (0 < self.graph[u][v] < key[v]) and not mst_set[v]:
key[v] = self.graph[u][v]
parent[v] = u

for i in range(1, self.V):


print(f"Edge: {parent[i]} - {i} with weight: {self.graph[parent[i]][i]}")

# Example usage
if __name__ == "__main__":
g = Graph(5)
g.add_edge(0, 1, 2)
g.add_edge(0, 3, 6)
g.add_edge(1, 2, 3)
g.add_edge(1, 3, 8)
g.add_edge(1, 4, 5)
g.add_edge(2, 4, 7)
g.add_edge(3, 4, 9)

print("Minimum Spanning Tree edges:")


g.prim_mst()

3. Single Source Shortest Paths


Algorithms:

a. Dijkstra’s Algorithm

1. Initialize the distance to the source as 0 and all others as infinity.


2. Use a priority queue to explore the nearest vertex.
3. Update the distances of adjacent vertices.
4. Repeat until all vertices are processed.

Find Shortest Paths from Source to all Vertices using Dijkstra’s Algorithm.

Given a weighted graph and a source vertex in the graph, find the shortest paths from the source to
all the other vertices in the given graph.
Note: The given graph does not contain any negative edge.
Examples:
Output: 0 4 12 19 21 11 9 8 14
Explanation: The distance from 0 to 1 = 4.
The minimum distance from 0 to 2 = 12. 0->1->2
The minimum distance from 0 to 3 = 19. 0->1->2->3
The minimum distance from 0 to 4 = 21. 0->7->6->5->4
The minimum distance from 0 to 5 = 11. 0->7->6->5
The minimum distance from 0 to 6 = 9. 0->7->6
The minimum distance from 0 to 7 = 8. 0->7
The minimum distance from 0 to 8 = 14. 0->1->2->8

b. Bellman-Ford Algorithm

Bellman ford algorithm is a single-source shortest path algorithm. This algorithm is used to find the shortest distance from
the single vertex to all the other vertices of a weighted graph. There are various other algorithms used to find the shortest
path like Dijkstra algorithm, etc.

1. Initialize distances as above.


2. Relax all edges V−1V-1V−1 times.
3. Check for negative weight cycles.

Imagine you have a map with different cities connected by roads, each road having a certain
distance. The Bellman–Ford algorithm is like a guide that helps you find the shortest path
from one city to all other cities, even if some roads have negative lengths. It’s like a GPS for
computers, useful for figuring out the quickest way to get from one point to another in a
network.

Rule of this algorithm

1. We will go on relaxing all the edges (n - 1) times where,


2. n = number of vertices
Consider the below graph:

As we can observe in the above graph that some of the weights are negative. The above graph contains 6
vertices so we will go on relaxing till the 5 vertices. Here, we will relax all the edges 5 times. The loop will
iterate 5 times to get the correct answer. If the loop is iterated more than 5 times then also the answer will be
the same, i.e., there would be no change in the distance between the vertices.

How it works:

1. Set initial distance to zero for the source vertex, and set initial distances to infinity for all other
vertices.
2. For each edge, check if a shorter distance can be calculated, and update the distance if the
calculated distance is shorter.
3. Check all edges (step 2) V−1�−1 times. This is as many times as there are vertices (V�), minus
one.
4. Optional: Check for negative cycles.
Manual Run Through
The Bellman-Ford algorithm is actually quite straight forward, because it checks all edges, using the
adjacency matrix. Each check is to see if a shorter distance can be made by going from the vertex on one
side of the edge, via the edge, to the vertex on the other side of the edge.

And this check of all edges is done V−1�−1 times, with V� being the number of vertices in the graph.

This is how the Bellman-Ford algorithm checks all the edges in the adjacency matrix in our graph 5-1=4
times:

The first four edges that are checked in our graph are A->C, A->E, B->C, and C->A. These first four edge
checks do not lead to any updates of the shortest distances because the starting vertex of all these edges
has an infinite distance.

After the edges from vertices A, B, and C are checked, the edges from D are checked. Since the starting
point (vertex D) has distance 0, the updated distances for A, B, and C are the edge weights going out from
vertex D.

The next edges to be checked are the edges going out from vertex E, which leads to updated distances for
vertices B and C.
The Bellman-Ford algorithm have now checked all edges 1 time. The algorithm will check all edges 3 more
times before it is finished, because Bellman-Ford will check all edges as many times as there are vertices in
the graph, minus 1.

The algorithm starts checking all edges a second time, starting with checking the edges going out from
vertex A. Checking the edges A->C and A->E do not lead to updated distances.

The next edge to be checked is B->C, going out from vertex B. This leads to an updated distance from vertex
D to C of 5-4=1.

Checking the next edge C->A, leads to an updated distance 1-3=-2 for vertex A.

The check of edge C->A in round 2 of the Bellman-Ford algorithm is actually the last check that leads to an
updated distance for this specific graph. The algorithm will continue to check all edges 2 more times without
updating any distances.

Checking all edges V−1�−1 times in the Bellman-Ford algorithm may seem like a lot, but it is done this
many times to make sure that the shortest distances will always be found.
Sample Program (Dijkstra’s Algorithm)
python
Copy code
import heapq

class Graph:
def __init__(self, vertices):
self.V = vertices
self.graph = {i: [] for i in range(vertices)}

def add_edge(self, u, v, w):


self.graph[u].append((v, w))
self.graph[v].append((u, w)) # Undirected graph

def dijkstra(self, src):


distances = [float('inf')] * self.V
distances[src] = 0
pq = [(0, src)] # (distance, vertex)

while pq:
current_distance, current_vertex = heapq.heappop(pq)

for neighbor, weight in self.graph[current_vertex]:


distance = current_distance + weight

if distance < distances[neighbor]:


distances[neighbor] = distance
heapq.heappush(pq, (distance, neighbor))

for vertex, distance in enumerate(distances):


print(f"Distance from {src} to {vertex}: {distance}")

# Example usage
if __name__ == "__main__":
g = Graph(5)
g.add_edge(0, 1, 10)
g.add_edge(0, 2, 3)
g.add_edge(1, 2, 1)
g.add_edge(1, 3, 2)
g.add_edge(2, 1, 4)
g.add_edge(2, 3, 8)
g.add_edge(2, 4, 2)
g.add_edge(3, 4, 7)
g.add_edge(4, 3, 9)

g.dijkstra(0)

4. All Pairs Shortest Paths


Algorithm:

a. Floyd-Warshall Algorithm

1. Initialize a distance matrix with edge weights.


2. For each vertex, update the matrix to check if a path through that vertex is shorter.
3. Repeat for all vertices.

Figure:

 Use a matrix to show the distances between all pairs of vertices before and after applying the
algorithm.

Sample Program (Floyd-Warshall Algorithm)


python
Copy code
class Graph:
def __init__(self, vertices):
self.V = vertices
self.graph = [[float('inf')] * vertices for _ in range(vertices)]
for i in range(vertices):
self.graph[i][i] = 0

def add_edge(self, u, v, w):


self.graph[u][v] = w

def floyd_warshall(self):
dist = self.graph

for k in range(self.V):
for i in range(self.V):
for j in range(self.V):
if dist[i][k] + dist[k][j] < dist[i][j]:
dist[i][j] = dist[i][k] + dist[k][j]

for i in range(self.V):
for j in range(self.V):
if dist[i][j] == float('inf'):
print(f"Distance from {i} to {j}: INF")
else:
print(f"Distance from {i} to {j}: {dist[i][j]}")

# Example usage
if __name__ == "__main__":
g = Graph(4)
g.add_edge(0, 1, 5)
g.add_edge(0, 3, 10)
g.add_edge(1, 2, 3)
g.add_edge(2, 3, 1)

print("All pairs shortest paths:")


g.floyd_warshall()
5. Maximum Flow
Algorithm:

a. Ford-Fulkerson Method

1. Start with a flow of 0.


2. Find augmenting paths using BFS (Edmonds-Karp) or DFS.
3. Increase the flow along the path until no more augmenting paths can be found.

Figure:

 Create a directed graph and illustrate flow values on the edges, showing how the flow increases with
each augmenting path.

Sample Program (Ford-Fulkerson Algorithm)


python
Copy code
from collections import deque

class Graph:
def __init__(self, vertices):
self.V = vertices
self.graph = [[0] * vertices for _ in range(vertices)]

def add_edge(self, u, v, w):


self.graph[u][v] = w

def bfs(self, s, t, parent):


visited = [False] * self.V
queue = deque([s])
visited[s] = True

while queue:
u = queue.popleft()

for v

You might also like