Graph - 3rd Sem - Data Structure
Graph - 3rd Sem - Data Structure
- Complete Graph: All nodes are related to each other with unique edge
- Cycle: last node is connected to the first node making a graph cycle
- Bipartite: If nodes can be aligned in 2 groups that do not have any edge in the same
group. Then this is a bipartite graph.
Graph Representation
We can represent graphs in 2 ways.
a. Adjacency Matrix
b. Adjacency List
Adjacency Matrix
A matrix that shows connection / edge between 2 nodes is the adjacency matrix. If I have n
nodes in graph I have to take an 𝑛 × 𝑛 matrix to represent the graph.
1 —------------------ 2 —--------------- 3
| | |
| | |
4—--------------------5 6
Graph Theory and Algorithms Data Structures
1 2 3 4 5 6
1 0 1 0 1 0 0
2 1 0 1 0 1 0
3 0 1 0 0 0 1
4 1 0 0 0 1 0
5 0 1 0 1 0 0
6 0 0 1 0 0 0
starting with 1 indexing each row and col reads out its number. The (x,y) coordinate in the matrix
if - 1 represents x and y has an edge. But if 0 then it is supposed that x,y coordinates do not
share an edge.
Adjacency List
In the adjacency list each node represents a list and the list stores the information of nodes they
are connected to. For the graph above, the adjacency list is,
1 -> 2 , 4
2 -> 1 , 5
3 -> 2 , 6
4 -> 1 , 5
5 -> 2 , 4
6 -> 3
2
Now the constraint is O(E) the number of edges. Now edges can be as much as 𝑛 . In that case
the matrix and list will give the same result time… The less amount of edge means less amount
of memory will be in use.
Adib Sakhawat (210042106), Software Engineering, Dept of CSE, Islamic University of Technology - 2
Graph Theory and Algorithms Data Structures
class GraphMat{
public:
int V;
bool isDirected;
int **adj;
GraphMat(int V, bool dir){
this->V = V;
isDirected = dir;
adj = new int*[V];
for(int i=0;i<V;i++){
adj[i] = new int[V];
for(int j=0;j<V;j++){
adj[i][j] = INF;
}
}
for(int i=0;i<V;i++){
adj[i][i] = 0;
}
}
void printMatrix(){
for(int i=0;i<V;i++){
for(int j=0;j<V;j++){
cout<<adj[i][j]<<" ";
}
cout<<endl;
}
}
};
Adib Sakhawat (210042106), Software Engineering, Dept of CSE, Islamic University of Technology - 3
Graph Theory and Algorithms Data Structures
void printGraph(){
for(int i=0; i<v; i++){
cout << i << " -> ";
for(int j=0; j<adj[i].size(); j++){
cout << adj[i][j] << " ";
}
cout << endl;
}
}
};
q.push(source);
Adib Sakhawat (210042106), Software Engineering, Dept of CSE, Islamic University of Technology - 4
Graph Theory and Algorithms Data Structures
while(!q.empty()){
int u = q.front();
q.pop();
if(v == destination){
return true;
}
if(level[v] == -1){
level[v] = 1;
q.push(v);
}
}
}
return false;
}
q.push(source);
level[source] = 0;
while(!q.empty()){
int u = q.front();
q.pop();
Adib Sakhawat (210042106), Software Engineering, Dept of CSE, Islamic University of Technology - 5
Graph Theory and Algorithms Data Structures
Shortest Path
To find the shortest path I have to first do 2 things.
1. Find the shortest path by finding levels.
2. Labeling the node parents with a previous array
int previous[100];
q.push(source);
while(!q.empty()){
int u = q.front();
q.pop();
Adib Sakhawat (210042106), Software Engineering, Dept of CSE, Islamic University of Technology - 6
Graph Theory and Algorithms Data Structures
Now after finding the shortest path, we can code the recursive function to print the path.
void printPath(int source, int destination){
if(source == destination){
cout << source;
}
else{
printPath(source, previous[destination]);
cout << " -> " << destination;
}
}
Bi-Coloring
If we can color each node of a graph from 2 colors that no adjacent nodes share the same color
then we can say “ This Graph is Bi-Colorable ”
bool isBicolorable(graph g, int source){
int color[100];
init(color, 100, -1);
queue<int> q;
q.push(source);
color[source] = 1;
while(!q.empty()){
int u = q.front();
q.pop();
Adib Sakhawat (210042106), Software Engineering, Dept of CSE, Islamic University of Technology - 7
Graph Theory and Algorithms Data Structures
s.push(source);
visited[source] = 1;
while(!s.empty()){
int u = s.top();
s.pop();
Adib Sakhawat (210042106), Software Engineering, Dept of CSE, Islamic University of Technology - 8
Graph Theory and Algorithms Data Structures
if(visited[destination] == 1){
cout << "Path exists" << endl;
}
else{
cout << "Path does not exist" << endl;
}
}
void printPath(int source, int destination){
if(source == destination){
cout << source;
}
else{
printPath(source, parent[destination]);
cout << " -> " << destination;
}
}
Adib Sakhawat (210042106), Software Engineering, Dept of CSE, Islamic University of Technology - 9
Graph Theory and Algorithms Data Structures
s.push(source);
visited[source] = 1;
while(!s.empty()){
int u = s.top();
s.pop();
for(int i=0; i<g.adj[u].size(); i++){
int v = g.adj[u][i];
if(visited[v] == 0){
visited[v] = 1;
s.push(v);
parent[v] = u;
}
else if(visited[v] == 1 && parent[u] != v){
return true;
}
}
}
return false;
}
For a directed graph, the process is a little different. Here we have to keep track of nodes that
are fully processed.
stack<int> s;
s.push(source);
while (!s.empty()) {
int u = s.top();
s.pop();
visited[u] = 1;
Adib Sakhawat (210042106), Software Engineering, Dept of CSE, Islamic University of Technology - 10
Graph Theory and Algorithms Data Structures
int v = g.adj[u][i];
if (visited[v] == 0) {
s.push(v);
parent[v] = u;
} else if (visited[v] == 1) {
return true;
}
}
visited[u] = 2;
}
return false;
}
s.push(source);
color[source] = 1;
while(!s.empty()){
int u = s.top();
s.pop();
return true;
}
Adib Sakhawat (210042106), Software Engineering, Dept of CSE, Islamic University of Technology - 11
Graph Theory and Algorithms Data Structures
Weighted Graph
A weighted graph is a graph in which a number (the weight) is assigned to each edge. These
weights can represent things like distance, cost, or any arbitrary value.
A -1- B -2- D
| |
3 4
| |
C -5- E
Here red alphabets are nodes. And numbers are cost of that path. Here from node A to node E
there are 2 paths,
A -> B -> E
A -> C -> E
A -> B -> E
In Adjacency List we can store edges between nodes with their weight as pairs. Like this
Adib Sakhawat (210042106), Software Engineering, Dept of CSE, Islamic University of Technology - 12
Graph Theory and Algorithms Data Structures
class weightedGraph{
public:
int n;
vector<pair<int, int>> *adj;
weightedGraph(int n){
this->n = n;
adj = new vector<pair<int, int>>[n];
}
void addEdge(int u, int v, int w){
adj[u].push_back(make_pair(v, w));
adj[v].push_back(make_pair(u, w));
}
};
1. Dijkstra’s Algorithm: This algorithm solves the shortest path problem for a graph with
non-negative edge path costs, producing the shortest path tree.
Adib Sakhawat (210042106), Software Engineering, Dept of CSE, Islamic University of Technology - 13
Graph Theory and Algorithms Data Structures
Each of these algorithms has its own strengths and weaknesses and is suited to different types
of problems and scenarios. Understanding these algorithms and knowing when to apply each
one is a key skill in computer science and operations research.
In conclusion, the shortest path problem is a classic and important problem in graph theory and
computer science. The ability to efficiently find the shortest or least costly path in a weighted
graph has wide-ranging applications in many fields, from network design to transportation
planning to logistics.
Path Relaxation
Path relaxation is a fundamental concept in graph algorithms, particularly those that deal with
finding the shortest path between nodes in a graph. It’s a process that iteratively improves the
estimate of the shortest path between nodes.
In the context of graph theory, relaxation is the process of shortening the preliminary value of
the shortest path from one node to another. Initially, we set the distance from the source node to
all other nodes as infinity, except for the source node itself, which is zero.
During the relaxation process, we traverse the graph and continually update the shortest path
estimates. If we find a shorter path to a node than the one previously recorded, we “relax” the
path, meaning we replace the old path length with the new, shorter one.
Consider an edge in the graph from node u to node v with a weight of w(u, v). If we have a
shorter path from the source node to v that goes through u, we update the shortest path value
of v. In other words, if d(v) > d(u) + w(u, v), then we update d(v) = d(u) + w(u,
v). Here, d(x) represents the current shortest path estimate from the source to node x.
This process is repeated until no more updates are possible, meaning we’ve found the shortest
paths to all nodes reachable from the source.
Adib Sakhawat (210042106), Software Engineering, Dept of CSE, Islamic University of Technology - 14
Graph Theory and Algorithms Data Structures
Path relaxation is crucial for the functioning of several shortest-path algorithms, including
Dijkstra’s algorithm and the Bellman-Ford algorithm. These algorithms use relaxation to
iteratively refine the estimate of the shortest path until they arrive at the optimal solution.
In Dijkstra’s algorithm, for example, relaxation is used in conjunction with a priority queue. The
algorithm continually selects the node with the smallest shortest path estimate, relaxes all of its
outgoing edges, and repeats this process until it has found the shortest path to every node.Here
the weighted graph is being used as a graph. The code is provided above for the weighted
graph.
while(!q.empty()){
int u = q.front();
q.pop();
1. Initialization: The inf constant is set to a large number, representing infinity. The dist
array will hold the shortest path estimates from the source to each node.
2. PathRelaxation Function: This function performs the path relaxation process on a
given graph g, from a source node to a destination node. It initializes a queue and adds
the source node to it, setting its distance to 0. Then, it enters a loop that continues until
the queue is empty.
a. In each iteration of the loop, it dequeues a node u and iterates over all of u’s
adjacent nodes. For each adjacent node v with an edge weight w, it checks if the
current shortest path to v can be improved by going through u. If so, it updates
v’s shortest path estimate and adds v to the queue.
Adib Sakhawat (210042106), Software Engineering, Dept of CSE, Islamic University of Technology - 15
Graph Theory and Algorithms Data Structures
The PathRelaxation function returns the shortest path length from the source to the
destination after performing the path relaxation process. If there’s no path from the source to the
destination, it will return inf, representing infinity.
Dijkstra Algorithm
Dijkstra’s algorithm is a popular method used in computing and graph theory to find the shortest
path between two nodes in a graph. Named after its creator, Dutch computer scientist Edsger
W. Dijkstra, the algorithm works by iteratively selecting the vertex with the smallest tentative
distance from the start, and then examining its neighbors. The algorithm keeps track of the
accumulated shortest distance from the start node to all other nodes in the graph. It continues
until it has processed all nodes, resulting in a shortest path tree. However, it’s important to note
that Dijkstra’s algorithm only works with graphs with non-negative edge weights.
pq.push(make_pair(0, source));
distance[source] = 0;
while(!pq.empty()){
int u = pq.top().second;
pq.pop();
Adib Sakhawat (210042106), Software Engineering, Dept of CSE, Islamic University of Technology - 16
Graph Theory and Algorithms Data Structures
The Dijkstra function is implementing Dijkstra’s algorithm, which is used to find the shortest
path from a source node to all other nodes in a weighted graph. Here’s a step-by-step
explanation:
1. Initialization: The function starts by initializing an array distance of size 100 with a
large value (INT_MAX). This array will store the shortest distance from the source node
to the other node. A priority queue pq is also initialized, which will be used to select the
next node to visit.
2. Source Node: The source node is added to the priority queue with a distance of 0, and
its distance is set to 0 in the distance array.
3. Main Loop: The function enters a loop that continues until the priority queue is empty. In
each iteration of the loop, it removes the node u with the smallest distance from the
priority queue.
4. Relaxation: For each neighbor v of u, it checks if the current shortest distance to v can
be improved by going through u. If so, it updates the shortest distance to v and adds v
to the priority queue.
5. Output: After the priority queue is empty, meaning all reachable nodes have been
visited, it prints the shortest distance from the source node to each node.
This function effectively implements Dijkstra’s algorithm. It’s important to note that Dijkstra’s
algorithm assumes that all edge weights are non-negative, as negative weights can lead to
incorrect results. Also, the graph is represented as an adjacency list, where g.adj[u] is a
vector of pairs representing the neighbors of u and their corresponding edge weights.
The algorithm operates on a 2D matrix, where each cell [i][j] represents the shortest
distance from node i to node j. Initially, the diagonal of the matrix is filled with zeros (since the
distance from a node to itself is zero), and the rest of the matrix is filled with the direct distances
between the nodes. The algorithm then iteratively updates the matrix, considering each node as
a possible intermediate step in the path between two other nodes.
By the end of the algorithm, the matrix contains the shortest possible distances between all
pairs of nodes. The Floyd-Warshall algorithm has a time complexity of O(n^3), where n is the
number of nodes in the graph. This makes it less suitable for large graphs, but it’s very useful for
smaller graphs or graphs where all pairwise shortest paths are needed.
Adib Sakhawat (210042106), Software Engineering, Dept of CSE, Islamic University of Technology - 17
Graph Theory and Algorithms Data Structures
1. The function takes a GraphMat object g as an argument. This object represents a graph
with g.V vertices, and its adjacency matrix is stored in g.adj.
2. The function consists of three nested loops, each iterating over the graph's vertices. The
outer loop variable i represents the intermediate vertex considered for the shortest path
between every pair of vertices.
3. The middle and inner loops, with variables j and k, represent all possible pairs of
vertices in the graph. For each pair (j, k), the algorithm checks if the path from j to k
passing through i is shorter than the current shortest path from j to k (which is initially
the direct edge from j to k, or infinity if there is no direct edge).
4. If the path through i is shorter, the algorithm updates the shortest path from j to k in the
adjacency matrix (g.adj[j][k] = g.adj[j][i] + g.adj[i][k]).
5. After all pairs of vertices have been considered for each intermediate vertex, the
adjacency matrix g.adj contains the shortest path distances between all pairs of
vertices.
This function effectively implements the Floyd-Warshall algorithm, which is used to find the
shortest paths between all pairs of vertices in a weighted graph. It’s important to note that this
function modifies the adjacency matrix of the graph in place, so after calling this function,
g.adj[j][k] will give the shortest path distance from vertex j to vertex k.
What if the i loop or the bridge element is placed inside most of the three loops then
what will happen?
void floydWarshall(GraphMat g){
for(int j=0; j<g.V; j++){
for(int k=0; k<g.V; k++){
for(int i=0; i<g.V; i++){
Adib Sakhawat (210042106), Software Engineering, Dept of CSE, Islamic University of Technology - 18
Graph Theory and Algorithms Data Structures
If the i loop is placed inside most of the 3 loops, the Floyd-Warshall algorithm will not work
correctly. The reason is that the Floyd-Warshall algorithm relies on a principle called “optimal
substructure,” which means the shortest path from node j to node k through an intermediate
node i depends on the shortest paths from j to i and from i to k.
In the correct implementation, the outer loop over i ensures that when considering paths that go
through i, all shorter paths that go through nodes 0 to i-1 have already been considered. This
is because the algorithm assumes that the shortest path from j to k through i has already been
found before it moves on to i+1.
If i is looped inside j and k, then when trying to update the shortest path from j to k through i,
the algorithm might not have found the shortest path from j to i or from i to k yet, leading to
incorrect results.
So, the order of the loops is crucial for the Floyd-Warshall algorithm to work correctly. The outer
loop must be i, followed by j and k as inner loops. This ensures that all shorter paths are
considered before longer ones, satisfying the optimal substructure property and leading to the
correct shortest paths.
Bellman-Ford Algorithm
The Bellman-Ford algorithm is a graph search algorithm that finds the shortest path between a
given source vertex and all other vertices in the graph. This algorithm can handle graphs in
which some of the edge weights are negative.
1. Initialization: The algorithm initializes the distance from the source vertex to itself as 0
and to all other vertices as infinity.
2. Edge Relaxation: The algorithm then repeatedly iterates over all edges in the graph. For
each edge (u, v) with weight w, if the current distance estimate to v can be improved
Adib Sakhawat (210042106), Software Engineering, Dept of CSE, Islamic University of Technology - 19
Graph Theory and Algorithms Data Structures
by going through u, the algorithm updates the distance of v to the sum of the estimate of
u and the weight of the edge (u, v). This is known as “relaxing” the edge.
3. Negative Cycle Detection: After iterating over all edges |V|-1 times, where |V| is the
number of vertices in the graph, the algorithm has produced the shortest path tree. If the
algorithm can still relax an edge, then there is a negative cycle in the graph. The
Bellman-Ford algorithm can detect negative cycles and report their existence.
The Bellman-Ford algorithm is more versatile than Dijkstra’s algorithm, another well-known
shortest path algorithm, because it can handle graphs with negative edge weights. However, it
is also slower, with a time complexity of O(|V||E|), where |V| is the number of vertices and
|E| is the number of edges in the graph. Despite its slower speed, the Bellman-Ford algorithm
is still widely used in network routing protocols, most notably in the Internet’s Border Gateway
Protocol (BGP).
int dist[1000];
return dist[destination];
}
Adib Sakhawat (210042106), Software Engineering, Dept of CSE, Islamic University of Technology - 20
Graph Theory and Algorithms Data Structures
The Bellman-Ford algorithm is a graph search algorithm that finds the shortest path between a
given source vertex and all other vertices in the graph. This algorithm can handle graphs with
negative weight edges, unlike Dijkstra’s algorithm.
The function bellmanFord takes a weighted graph g, a source vertex source, and a
destination vertex destination as input. It initializes the distance from the source to all
vertices as infinity, except for the source itself, which is initialized with a distance of 0. Then, it
relaxes the edges of the graph g.n-1 times, where g.n is the number of vertices in the graph.
In each iteration, it checks all edges (u, v) and updates the shortest distance to vertex v if the
current distance to v is greater than the distance to u plus the weight of the edge (u, v).
After the main loop, the function checks for negative weight cycles in the graph. If it can still
relax an edge, it means that edge is part of a negative weight cycle. In such cases, the function
prints a warning and returns -1. This is because in a graph with a negative weight cycle, the
concept of a “shortest path” becomes undefined. If no negative weight cycle is detected, the
function returns the shortest distance from the source to the destination. This implementation of
the Bellman-Ford algorithm allows for the handling of graphs with negative weight edges and
provides a way to detect negative weight cycles.
Adib Sakhawat (210042106), Software Engineering, Dept of CSE, Islamic University of Technology - 21