Professional Documents
Culture Documents
Graphs
Introduction to Graphs
Isn’t this the first image that comes to your mind when you
hear the word graphs???
● If you have noticed, despite being non-linear data structures, trees and heaps
have some sort of hierarchical flow from parent nodes to children nodes,
which is clearly undesirable in our problem here.
● So, this brings us to our newest and one of the most versatile and used
abstract data structure, GRAPH.
● Graphs help in finding the shortest distance between any two given
locations, where nodes can be the different locations in a city and edges
represent the routes available between the locations.
● Graph data structures do not follow any restriction like that of the tree data
structure on how different nodes are connected to each other.
● Tree data structures have usage restrictions in representing real-world
scenarios as each child node can have only one parent node.
Introduction to Graphs
● The directed graphs that have cycles in them, i.e. if you start from any node
in the graph and traverse through its connecting edges, you return to the
same node where you started, are called Directed Cyclic Graphs.
● Similarly, the directed graphs that don’t have any cycle in them are called
Directed Acyclic Graphs(DAG).
● The example of a Directed Cyclic Graph can be our instagram social network
where A follows B, B follows C and C follows A. If no such cycle exists, it
would be an example of DAG.
Introduction to Graphs
● This is the same example we saw while learning about Directed Graphs. Is
this an example of Directed Cyclic Graph or Directed Acyclic Graph???
Introduction to Graphs
● Therefore, we can
safely say that all the
trees are examples of
graphs (Directed
Acyclic Graphs).
Introduction to Graphs
Let’s summarise what we learnt about the type of graphs:
Introduction to Graphs
● In the first image( the top one), all the nodes seem to
have one or more connection(s) with other nodes and
hence all the nodes are connected to each other, either
directly or indirectly. It is therefore, a connected graph.
Let’s now see the various terms related to this data structure:
● Degree: The number of edges that are connected to a node is called the
degree of that node. In directed graphs, the degree can be classified into:
Node 1 {2, 3, 4, 5} 4
Node 2 {1, 4} 2
Node 3 {1} 1
Node 4 {1, 2, 5} 3
Node 5 {1, 4} 2
Terminology
● Path:
○ When a series of vertices are
connected by a sequence of edges
between two specific nodes in a
graph, the sequence is called a path.
● Weighted graph:
● Unweighted graph:
● How we used to traverse in a single direction from a root node till end and
then backtrack and change direction...
● From the start node, it traverses through any one of its neighbours and
explores the farthest possible UNVISITED node in each branch before
backtracking.
Depth First Search
● Then, the DFS algorithm traces back to the previous node and
traverses any neighbour if it is left unvisited.
● This is where we backtrack for the first All nodes visited. DFS Complete
time, i.e. visit 1 again. Now, we choose
to visit another neighbour, either 4 or 5.
Nodes
2 21 213 2131
Traversed:
Now that you have understood the algorithm, let’s check the pseudocode:
Visited ← { }
procedure dfs(n)
add n to visited set
for all n` ∈ neighbours(n) do
if (n` ∉ visited) then
dfs(n`)
end if
end for
end procedure
Depth First Search
Well, we saw the flow of logic first and then, the pseudocode for our algorithm as
well. Let’s solidify our DFS concepts by dry-running our algorithm on our example:
● Now, we visit one of the neighbours of 2( 1 and 4). Let’s proceed with 1 as we
did earlier.
● Now, we check all the neighbours of 1(2, 3, 4 and 5). Since 2 is already in the
visited set, we move to the next neighbour which is 3.
● Here, 3 has only one neighbour, the node 1 which has already been visited.
So, we backtrack from 3 to our previous call, i.e. DFS 1. Now we move on
the neighbour, after node 3, which is 4.
● So, we now call the
DFS function on
node 4.
● Here, 4 has three neighbours 1, 2 and 5. But since 1 and 2 are already visited
and 5 is not, we move on with the node 5.
● Here, 5 has two neighbours 1 and 4, both of which have been visited. Since,
there is no unvisited neighbour of 5, we perform another backtracking and
check for the unvisited neighbours in our previous DFS call.
● The previous DFS
call, which has no
unvisited neighbours.
● Similarly, on
backtracking to all
the previous calls
including, top-most
DFS 2, all the nodes
are visited and our
DFS is complete.
Depth First Search
As we saw, the algorithm uses a set called Visited to store all the visited
elements during our traversal. Let’s further break down this algorithm into steps:
1. The start node of the DFS algorithm is added to the visited set.
2. The ‘for’ loop instruction set is executed for all the neighbours of the start
node.
3. Then if the neighbour node is unvisited, only then it recursively calls the
dfs() method.
4. Now, the unvisited neighbour node becomes the start node and repeats the
above steps.
Depth First Search
5. The DFS traversal reaches a node from where there are no unvisited
neighbour nodes; here, the recursive algorithm backtracks to the earlier
unvisited, neighbour nodes that are stored in the stack and visits the
remaining nodes.
● Thus, the depth-first search recursively visits all the nodes along one branch
and then backtracks to the unvisited neighbour nodes.
● Once all the nodes connected from the start node are visited, the algorithm
ends.
Depth First Search
● BFS is a traversing algorithm where traversing starts from the start node and
then explores the immediate neighbours of the start node; then the traversing
moves towards the next-level neighbours of the graph structure.
● To implement the breadth-first search, you need to consider the stage of each
node. Nodes, in general, are considered to be in three different stages as:
○ Not visited
○ Visited
○ Completed
● In BFS algorithm, nodes are marked as visited during the traversal, in order to
avoid the infinite loops caused because of the possibilities of cycles in a
graph structure.
Breadth First Search
As you can see, we will start with all the nodes in the not visited section.
Breadth First Search
● So, the first thing to do weill be to remove 2 from not visited column and add
it to the visited one.
Breadth First Search
● Therefore, 1 and 4 are removed from the not visited column and added
to the visited section.
● After this step, 2 will be removed from the visited column and added to
completed column.
Breadth First Search
● We will follow the same procedure for 4 now and visit all its neighbours.
Breadth First Search
● Since the neighbours 1 and 2 are already been visited, we ignore them and
add the unvisited neighbours( 3 and 5) to the visited list.
● And since all the neighbours of 4 are visited now, it is removed from the
visited list and added to the completed list.
Breadth First Search
● Now we move on to the next element in the visited list i.e. 1. But, since all
the neighbours of 1 are already been visited, it is removed from the visited
column and added to the completed one.
● The same step is repeated for the next two entries in our visited column.
They are removed from the visited column and added to the completed one.
Breadth First Search
● Since the visited column is empty now, we can safely say that the BFS
traversal is complete.
● It means that what comes first in the visited column, also is the first one to
get completed( FIFO nature)
Breadth First Search
Now let’s take a look at the pseudocode of BFS algorithm and analyze it.
Breadth First Search
Let’s quickly dry run the pseudocode on our example first before go in detailed
explanation of the same:
● The loop checks if the queue is empty or not. Since it is not empty here, we
proceed with the body of the loop.
● The loop checks if the queue is empty or not. Since it is not empty here, we
proceed with the body of the loop once again.
● The loop checks if the queue is empty or not. Since it is not empty here, we
proceed with the body of the loop once again.
● If you see the graph, you can clearly see that all
the nodes have been visited at least once.
● The loop checks if the queue is empty or not again. Since it is not empty
here, we proceed with the body of the loop.
● Unlike the DFS which had a recursive logic, the BFS algorithm follows an
iterative approach.
Step 1: The start node is enqueued and also marked as visited in the following
set of instructions:
● enqueue (Q, n)
● Add n to visited set
Step 2: ‘While’ loop instruction set is executed when the queue is not empty
Step 4: Now ‘for’ loop runs till all the unvisited neighbours of the dequeued
node(n) are enqueued and also marked as visited.
Breadth First Search
Step 5: For the first iteration of the while loop, all the neighbour nodes of the
start node are enqueued and on the second iteration, all the next level unvisited
neighbour nodes of one of the neighbour node are enqueued.
This way, all the neighbour nodes are enqueued and visited level wise from the
start node and after a certain number of iterations, all the nodes are dequeued
and the algorithm ends.
Breadth First Search
Now let’s run our pseudocode on an another graph to further strengthen our
understanding of BFS. The following image explains all the steps by itself:
Breadth First Search
Below are the steps which explain the steps in the previous image:
Step 1: Enqueue the node ‘1’ to the queue and add it to the visited list.
● Enqueue all the neighbours of the popped element, which are not in the
visited list, to the queue and also add them to the visited list.
● Enqueue 2 and 3 to the queue and add them to the visited list.
Breadth First Search
● Enqueue all the neighbours of the popped element, which are not in the
visited list, to the queue and also add them to the visited list.
● Enqueue all the neighbours of the popped element, which are not in the
visited list, to the queue and also add them to the visited list.
● Enqueue all the neighbours of the popped element, which are not in the
visited list, to the queue and also add them to the visited list.
● Since there are no neighbours of ‘5’, which are not in the visited list.
Therefore, do nothing.
● Enqueue all the neighbours of the popped element, which are not in the
visited list, to the queue and also add them to the visited list.
Breadth First Search
Since there are no neighbours of ‘5’, which are not in the visited list. Therefore,
do nothing.
Since the queue is empty stop.The visited list is the bfs of the graph.
Introduction to Edge Lists
● We completed both the traversal techniques for the graphs and now it’s time
to study the implementations.
● There are three key implementations of the graph abstract data structure:
○ Edge list
○ Adjacency matrix
○ Adjacency list
Introduction to Edge Lists
● Let’s check how much you understood about the Edge List representation.
Since it’s a weighted graph, we need to add another value in our edge list
object.
● Set of edges: {(0, 1, 9), (1, 2, 8), (3, 2, 6), (3, 0, 10), (4, 0, 7), (3, 4, 5)}
(0, 1, 9) indicates that there is an edge between ‘0’ and ‘1’ with edge weight
as 9.
Introduction to Adjacency Matrix
● Now let’s move on to the next representation of graph called the Adjacency
Matrix.
● Here, the number of rows and the number of columns are the same and
equal to the number of nodes in the graph.
● The logic to populate this matrix is that if there exists, an edge between the
node I and node J then you would place a true value in the cell
corresponding to the Ith row and Jth column.
● The Adjacency Matrix of the graph we’ve been using in our examples will be:
Introduction to Adjacency Matrix
● Now, we will calculate the time complexity for some operations on the
adjacency matrix, specifically for the following four operations:
● getAllNodes:
● addNode:
● addNode:
○ Let’s say we have to insert a new node in a graph containing only two
nodes which are joined.
○ The first step will be to create a new 3X3 matrix and copy the first two
rows and columns into it and filling the last row and column with False.
○ Now, depending on which nodes is the new node connected to, the
corresponding entry in the matrix is made True.
Introduction to Adjacency Matrix
● addEdge:
○ This function updates the adjacency matrix value to TRUE at the row and
column corresponding to the nodes between which the edge is to be
added.
○ This updation is very fast, but finding the row and column corresponding
to these nodes uses the “IndexOf’’ operation on the node list.
○ The worst case time complexity of “IndexOf” and hence, the entire
function is O(n).
Introduction to Adjacency Matrix
● addEdge:
● getAllNeighbours:
○ This function also first finds, the row/column number of the given node
using IndexOf function just like addEdge.
○ In the next step, the entire row/column corresponding the node index is
traversed and all the columns/rows with entry as TRUE are shown as
neighbours.
○ Both the IndexOf function call and the row traversal for all columns take
O(n) time and hence the overall time complexity of the function is O(n) as
well.
Introduction to Adjacency Matrix
Note that V represents the number of nodes/vertices and E the number of edges.
Introduction to Adjacency Matrix
● Sparse graphs: Sparse graphs are connected graphs with the minimum
or a small number of edges connecting nodes. In sparse graphs, there
may or may not be an edge between two nodes.
Now, can you figure out a way to delete an edge, say from X to U?
Introduction to Adjacency Matrix
1. You have to traverse through the vertex list and find the indexes of the X and
U, here they are 3 and 0 respectively.
○ If the size of the vertex list is V then this step takes linear time which is
O(V) in the worst case.
● Adjacency List
● Dijkstra's Algorithm
● Topological Sorting
Introduction to Adjacency List
● The key set will be all the nodes and the value for a key will be the set of
all its neighbours.
● To add an edge, we need to identify the two nodes which will be connected by
the edge. In our case, they will be 2 and 3.
● We need to find the first node(2) in our key set and add the second node(3) in
its value set of nodes.
● Similarly, we add the first node(2) in the value set of second node(3).
● What will be the time complexity of the addEdge operation in Adjacency List
implementation?
Introduction to Adjacency List
● Edge Lists and Adjacency Lists are recommended for sparse graphs and
Adjacency Matrices for dense graphs on the basis of space complexities.
● The adjacency list can be concluded as behaving the most optimally, but,
again, this depends on the requirements.
Dijkstra's Algorithm
● Google Maps has become a part of our day-to-day lives; the tool comes to the
rescue whenever we want to travel.
● But, millions of people use Google Maps. So doing all the calculations to find
the shortest possible route and display the same in a couple of seconds is
surely not possible for each person who uses the tool and each request.
● Well, there’s an algorithm at work behind each request that you send to
Google Maps, and it is popularly known as ‘Dijkstra’s algorithm’. Let’s learn
more about it.
Dijkstra's Algorithm
Well, here’s a task for you. What will be shortest distances of all the nodes from C2?
Dijkstra's Algorithm
● Initially, all the nodes except the source node itself are
assigned maximum possible value in the table and the
source node is assigned 0.
Dijkstra's Algorithm
● This edge relaxation keeps on updating the table in a while loop until the
priority queue is empty.
Dijkstra's Algorithm
Let’s break down the pseudocode into steps for better understanding:
Step 1: First perform relaxation for all the outgoing edges of the starting node.
Step 2: ‘While’ loop instruction set is executed when the queue is not empty
Step 3: For each iteration of the while loop, the node in the front gets dequeued
Step 4: nextNode will store the node in front of priority queue after the last deque
Step 5: We will check if the vale of nextNode is null or not, if It is not null we will
proceed and do edge relaxation on the nodes where require.
Dijkstra's Algorithm
Let’s look at a problem now. You are given a directed graph. Your task is to
find out whether the graph contains a cycle or not.
● This problem can be solved using a depth-first search (DFS). For each
node in a graph, you need to perform depth-first traversal.
● While performing depth-first traversal, if you reach the same node from
where you started, then it means that a cycle exists in the graph.
● If you are unable to reach the same node for any of the nodes in the graph,
then it means that a cycle does not exist.
Topological Sorting
We can find topological sorting using both DFS and BFS. First, let’s see how
you can find a topological sorting using DFS:
● Start from a vertex and recursively call topological sorting for all its adjacent
vertices, then push it to the stack (when all adjacent vertices are on stack).
● Compute the in-degree for each of the vertex present in the DAG.
● Pick all the vertices with in-degree as 0 and add them into a queue.
● Remove a vertex from the queue and add it to the result array.Then,
decrease in-degree by 1 for all its neighbouring nodes. If in-degree of a
neighbouring nodes is reduced to zero, then add it to the queue.
t = []
visited = []
in_degree = []
for i = 0 to n
in _degree[i] = visited[i] = 0
for i = 0 to n
for j = 0 to n
If adj[i][j] is True
in_degree+=1
Topological Sorting
for i = 0 to n
If in_degree is 0
enqueue(Queue,I)
visited[i] = True
vertex = get_front(Queue)
dequeue(Queue)
t.append(vertex)
for j = 0 to n
in_degree -=1
if in_degree is 0
enqueue(Queue,j)
visited[j] = True
return t
Complexity Analysis:
V is the number of vertices in the graph, and E is the number of edges in the graph.
Thanks for
Listening!