Skip to main content

Graph

Breadth First Search (BFS): Level Order Traversal

  1. In BFS, we start with a “starting” node, mark it as visited, and push it into the queue data structure.
  2. In every iteration, we pop out the node ‘v’ and put it in the solution vector, as we are traversing this node.
  3. All the unvisited adjacent nodes from ‘v’ are visited next and are pushed into the queue. The list of adjacent neighbors of the node can be accessed from the adjacency list.
  4. Repeat steps 2 and 3 until the queue becomes empty, and this way you can easily traverse all the nodes in the graph.
class Solution {
public:
// Function to return Breadth First Traversal of given graph.
vector<int> bfsOfGraph(int V, vector<int> adj[]) {
int vis[V] = {0};
vis[0] = 1;
queue<int> q;
// push the initial starting node
q.push(0);
vector<int> bfs;
// iterate till the queue is empty
while(!q.empty()) {
// get the topmost element in the queue
int node = q.front();
q.pop();
bfs.push_back(node);
// traverse for all its neighbours
for(auto it : adj[node]) {
// if the neighbour has previously not been visited,
// store in Q and mark as visited
if(!vis[it]) {
vis[it] = 1;
q.push(it);
}
}
}
return bfs;
}
};

void addEdge(vector <int> adj[], int u, int v) {
adj[u].push_back(v);
adj[v].push_back(u);
}

void printAns(vector <int> &ans) {
for (int i = 0; i < ans.size(); i++) {
cout << ans[i] << " ";
}
}

int main()
{
vector <int> adj[6];

addEdge(adj, 0, 1);
addEdge(adj, 1, 2);
addEdge(adj, 1, 3);
addEdge(adj, 0, 4);

Solution obj;
vector <int> ans = obj.bfsOfGraph(5, adj);
printAns(ans);

return 0;
}

Output: 0 1 4 2 3
Time Complexity: O(N) + O(2E), Where N = Nodes, 2E is for total degrees as we traverse all adjacent nodes.

Space Complexity: O(3N) ~ O(N), Space for queue data structure visited array and an adjacency list

Depth First Search (DFS)

  1. In DFS, we start with a node ‘v’, mark it as visited and store it in the solution vector. It is unexplored as its adjacent nodes are not visited.
  2. We run through all the adjacent nodes, and call the recursive dfs function to explore the node ‘v’ which has not been visited previously. This leads to the exploration of another node ‘u’ which is its adjacent node and is not visited.
  3. The adjacency list stores the list of neighbours for any node. Pick the neighbour list of node ‘v’ and run a for loop on the list of neighbours (say nodes ‘u’ and ‘w’ are in the list). We go in-depth with each node. When node ‘u’ is explored completely then it backtracks and explores node ‘w’.
  4. This traversal terminates when all the nodes are completely explored.
class Solution {
private:
void dfs(int node, vector<int> adj[], int vis[], vector<int> &ls) {
vis[node] = 1;
ls.push_back(node);
// traverse all its neighbours
for(auto it : adj[node]) {
// if the neighbour is not visited
if(!vis[it]) {
dfs(it, adj, vis, ls);
}
}
}
public:
// Function to return a list containing the DFS traversal of the graph.
vector<int> dfsOfGraph(int V, vector<int> adj[]) {
int vis[V] = {0};
int start = 0;
// create a list to store dfs
vector<int> ls;
// call dfs for starting node
dfs(start, adj, vis, ls);
return ls;
}
};

void addEdge(vector <int> adj[], int u, int v) {
adj[u].push_back(v);
adj[v].push_back(u);
}

void printAns(vector <int> &ans) {
for (int i = 0; i < ans.size(); i++) {
cout << ans[i] << " ";
}
}

int main()
{
vector <int> adj[5];

addEdge(adj, 0, 2);
addEdge(adj, 2, 4);
addEdge(adj, 0, 1);
addEdge(adj, 0, 3);

Solution obj;
vector <int> ans = obj.dfsOfGraph(5, adj);
printAns(ans);

return 0;
}

Output: 0 2 4 1 3

Time Complexity: For an undirected graph, O(N) + O(2E), For a directed graph, O(N) + O(E), Because for every node we are calling the recursive function once, the time taken is O(N) and 2E is for total degrees as we traverse for all adjacent nodes.

Space Complexity: O(3N) ~ O(N), Space for dfs stack space, visited array and an adjacency list.

Detect Cycle in an Undirected Graph (using BFS)

  • For BFS traversal, we need a queue data structure and a visited array.
  • Push the pair of the source node and its parent data (<source, parent>) in the queue, and mark the node as visited. The parent will be needed so that we don’t do a backward traversal in the graph, we just move frontwards.
  • Start the BFS traversal, pop out an element from the queue every time and travel to all its unvisited neighbors using an adjacency list.
  • Repeat the steps either until the queue becomes empty, or a node appears to be already visited which is not the parent, even though we traveled in different directions during the traversal, indicating there is a cycle.
  • If the queue becomes empty and no such node is found then there is no cycle in the graph.
class Solution {
private:
bool detect(int src, vector<int> adj[], int vis[]) {
vis[src] = 1;
// store <source node, parent node>
queue<pair<int,int>> q;
q.push({src, -1});
// traverse until queue is not empty
while(!q.empty()) {
int node = q.front().first;
int parent = q.front().second;
q.pop();

// go to all adjacent nodes
for(auto adjacentNode: adj[node]) {
// if adjacent node is unvisited
if(!vis[adjacentNode]) {
vis[adjacentNode] = 1;
q.push({adjacentNode, node});
}
// if adjacent node is visited and is not it's own parent node
else if(parent != adjacentNode) {
// yes it is a cycle
return true;
}
}
}
// there's no cycle
return false;
}
public:
// Function to detect cycle in an undirected graph.
bool isCycle(int V, vector<int> adj[]) {
// initialise them as unvisited
int vis[V] = {0};
for(int i = 0;i<V;i++) {
if(!vis[i]) {
if(detect(i, adj, vis)) return true;
}
}
return false;
}
};

int main() {

// V = 4, E = 2
vector<int> adj[4] = {{}, {2}, {1, 3}, {2}};
Solution obj;
bool ans = obj.isCycle(4, adj);
if (ans)
cout << "1\n";
else
cout << "0\n";
return 0;
}

Output: 0

Time Complexity: O(N + 2E) + O(N), Where N = Nodes, 2E is for total degrees as we traverse all adjacent nodes. In the case of connected components of a graph, it will take another O(N) time.

Space Complexity: O(N) + O(N) ~ O(N), Space for queue data structure and visited array.

Detect Cycle in an Undirected Graph (using DFS)

  • In the DFS function call make sure to store the parent data along with the source node, create a visited array, and initialize to 0. The parent is stored so that while checking for re-visited nodes, we don’t check for parents.
  • We run through all the unvisited adjacent nodes using an adjacency list and call the recursive dfs function. Also, mark the node as visited.
  • If we come across a node that is already marked as visited and is *not a parent node,* then keep on returning true indicating the presence of a cycle; otherwise return false after all the adjacent nodes have been checked and we did not find a cycle.
class Solution {
private:
bool dfs(int node, int parent, int vis[], vector<int> adj[]) {
vis[node] = 1;
// visit adjacent nodes
for(auto adjacentNode: adj[node]) {
// unvisited adjacent node
if(!vis[adjacentNode]) {
if(dfs(adjacentNode, node, vis, adj) == true)
return true;
}
// visited node but not a parent node
else if(adjacentNode != parent) return true;
}
return false;
}
public:
// Function to detect cycle in an undirected graph.
bool isCycle(int V, vector<int> adj[]) {
int vis[V] = {0};
// for graph with connected components
for(int i = 0;i<V;i++) {
if(!vis[i]) {
if(dfs(i, -1, vis, adj) == true) return true;
}
}
return false;
}
};

int main() {

// V = 4, E = 2
vector<int> adj[4] = {{}, {2}, {1, 3}, {2}};
Solution obj;
bool ans = obj.isCycle(4, adj);
if (ans)
cout << "1\n";
else
cout << "0\n";
return 0;
}

Time Complexity: O(N + 2E) + O(N), Where N = Nodes, 2E is for total degrees as we traverse all adjacent nodes. In the case of connected components of a graph, it will take another O(N) time.

Space Complexity: O(N) + O(N) ~ O(N), Space for recursive stack space and visited array.

Detect a Cycle in Directed Graph | Topological Sort | Kahn’s Algorithm |

class Solution {
public:
// Function to detect cycle in a directed graph.
bool isCyclic(int V, vector<int> adj[]) {
int indegree[V] = {0};
for (int i = 0; i < V; i++) {
for (auto it : adj[i]) {
indegree[it]++;
}
}

queue<int> q;
for (int i = 0; i < V; i++) {
if (indegree[i] == 0) {
q.push(i);
}
}

int cnt = 0;
// o(v + e)
while (!q.empty()) {
int node = q.front();
q.pop();
cnt++;
// node is in your topo sort
// so please remove it from the indegree

for (auto it : adj[node]) {
indegree[it]--;
if (indegree[it] == 0) q.push(it);
}
}

if (cnt == V) return false;
return true;
}
};

int main() {

//V = 6;
vector<int> adj[6] = {{}, {2}, {3}, {4, 5}, {2}, {}};
int V = 6;
Solution obj;
bool ans = obj.isCyclic(V, adj);
if (ans) cout << "True";
else cout << "Flase";
cout << endl;
return 0;
}

Topological Sort (BFS)

class Solution {
public:
vector<int> topo(int N, vector<int> adj[]) {
queue<int> q;
vector<int> indegree(N, 0);
for(int i = 0;i<N;i++) {
for(auto it: adj[i]) {
indegree[it]++;
}
}

for(int i = 0;i<N;i++) {
if(indegree[i] == 0) {
q.push(i);
}
}
vector<int> topo;
while(!q.empty()) {
int node = q.front();
q.pop();
topo.push_back(node);
for(auto it : adj[node]) {
indegree[it]--;
if(indegree[it] == 0) {
q.push(it);
}
}
}
return topo;
}
};

int main()
{

vector<int> adj[6];
adj[5].push_back(2);
adj[5].push_back(0);
adj[4].push_back(0);
adj[4].push_back(1);
adj[3].push_back(1);
adj[2].push_back(3);

Solution obj;
vector<int> v=obj.topo(6, adj);
for(auto it:v)
cout<<it<<" ";


return 0;
}

Time Complexity: O(N+E)

Space complexity: O(N)+O(N)

Topological Sort Using DFS

class Solution {
void findTopoSort(int node, vector < int > & vis, stack < int > & st, vector < int > adj[]) {
vis[node] = 1;

for (auto it: adj[node]) {
if (!vis[it]) {
findTopoSort(it, vis, st, adj);
}
}
st.push(node);
}
public:
vector < int > topoSort(int N, vector < int > adj[]) {
stack < int > st;
vector < int > vis(N, 0);
for (int i = 0; i < N; i++) {
if (vis[i] == 0) {
findTopoSort(i, vis, st, adj);
}
}
vector < int > topo;
while (!st.empty()) {
topo.push_back(st.top());
st.pop();
}
return topo;

}
};

// { Driver Code Starts.
int main() {

int N = 6;

vector < int > adj[5 + 1];

adj[5].push_back(2);
adj[5].push_back(0);
adj[4].push_back(0);
adj[4].push_back(1);
adj[2].push_back(3);
adj[3].push_back(1);

Solution obj;
vector < int > res = obj.topoSort(6, adj);

cout << "Toposort of the given graph is:" << endl;
for (int i = 0; i < res.size(); i++) {
cout << res[i] << " ";
}

return 0;
}

ime Complexity: O(N+E)

N = Number of node , E = Number of Edges

Space Complexity: O(N) + O(N)

Visited Array and Stack data structure. Both will be using O(N).

Print Shortest Path – Dijkstra’s Algorithm

  • If the graph contains negative edges.
  • If the graph has a negative cycle (In this case Dijkstra’s algorithm fails to minimize the distance, keeps on running, and goes into an infinite loop. As a result it gives TLE error).
class Solution
{
public:
vector<int> shortestPath(int n, int m, vector<vector<int>> &edges)
{
// Create an adjacency list of pairs of the form node1 -> {node2, edge weight}
// where the edge weight is the weight of the edge from node1 to node2.
vector<pair<int, int>> adj[n + 1];
for (auto it : edges)
{
adj[it[0]].push_back({it[1], it[2]});
adj[it[1]].push_back({it[0], it[2]});
}
// Create a priority queue for storing the nodes along with distances
// in the form of a pair { dist, node }.
priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int,int>>> pq;

// Create a dist array for storing the updated distances and a parent array
//for storing the nodes from where the current nodes represented by indices of
// the parent array came from.
vector<int> dist(n + 1, 1e9), parent(n + 1);
for (int i = 1; i <= n; i++)
parent[i] = i;

dist[1] = 0;

// Push the source node to the queue.
pq.push({0, 1});
while (!pq.empty())
{
// Topmost element of the priority queue is with minimum distance value.
auto it = pq.top();
pq.pop();
int node = it.second;
int dis = it.first;

// Iterate through the adjacent nodes of the current popped node.
for (auto it : adj[node])
{
int adjNode = it.first;
int edW = it.second;

// Check if the previously stored distance value is
// greater than the current computed value or not,
// if yes then update the distance value.
if (dis + edW < dist[adjNode])
{
dist[adjNode] = dis + edW;
pq.push({dis + edW, adjNode});

// Update the parent of the adjNode to the recent
// node where it came from.
parent[adjNode] = node;
}
}
}

// If distance to a node could not be found, return an array containing -1.
if (dist[n] == 1e9)
return {-1};

// Store the final path in the ‘path’ array.
vector<int> path;
int node = n;

// Iterate backwards from destination to source through the parent array.
while (parent[node] != node)
{
path.push_back(node);
node = parent[node];
}
path.push_back(1);

// Since the path stored is in a reverse order, we reverse the array
// to get the final answer and then return the array.
reverse(path.begin(), path.end());
return path;
}
};

int main()
{
// Driver Code

int V = 5, E = 6;
vector<vector<int>> edges = {{1, 2, 2}, {2, 5, 5}, {2, 3, 4}, {1, 4, 1}, {4, 3, 3},
{3, 5, 1}};
Solution obj;
vector<int> path = obj.shortestPath(V, E, edges);

for (int i = 0; i < path.size(); i++)
{

cout << path[i] << " ";
}
cout << endl;
return 0;
}

Time Complexity: O( E log(V) ) { for Dijkstra’s Algorithm } + O(V) { for backtracking in order to find the parent for each node } Where E = Number of edges and V = Number of Nodes.

Space Complexity: O( |E| + |V| ) { for priority queue and dist array } + O( |V| ) { for storing the final path } Where E = Number of edges and V = Number of Nodes.

Bellman Ford Algorithm

Bellman-Ford’s algorithm successfully solves these problems. It works fine with negative edges as well as it is able to detect if the graph contains a negative cycle. But this algorithm is only applicable for directed graphs. In order to apply this algorithm to an undirected graph, we just need to convert the undirected edges into directed edges like the following:

class Solution {
public:
/* Function to implement Bellman Ford
* edges: vector of vectors which represents the graph
* S: source vertex to start traversing graph with
* V: number of vertices
*/
vector<int> bellman_ford(int V, vector<vector<int>>& edges, int S) {
vector<int> dist(V, 1e8);
dist[S] = 0;
for (int i = 0; i < V - 1; i++) {
for (auto it : edges) {
int u = it[0];
int v = it[1];
int wt = it[2];
if (dist[u] != 1e8 && dist[u] + wt < dist[v]) {
dist[v] = dist[u] + wt;
}
}
}
// Nth relaxation to check negative cycle
for (auto it : edges) {
int u = it[0];
int v = it[1];
int wt = it[2];
if (dist[u] != 1e8 && dist[u] + wt < dist[v]) {
return { -1};
}
}

return dist;
}
};

int main() {

int V = 6;
vector<vector<int>> edges(7, vector<int>(3));
edges[0] = {3, 2, 6};
edges[1] = {5, 3, 1};
edges[2] = {0, 1, 5};
edges[3] = {1, 5, -3};
edges[4] = {1, 2, -2};
edges[5] = {3, 4, -2};
edges[6] = {2, 4, 3};

int S = 0;
Solution obj;
vector<int> dist = obj.bellman_ford(V, edges, S);
for (auto d : dist) {
cout << d << " ";
}
cout << endl;

return 0;
}

Time Complexity: O(V*E), where V = no. of vertices and E = no. of Edges.

Space Complexity: O(V) for the distance array which stores the minimized distances.

Floyd Warshall Algorithm

Basically, the Floyd Warshall algorithm is a multi-source shortest path algorithm and it helps to detect negative cycles as well.

class Solution {
public:
void shortest_distance(vector<vector<int>>&matrix) {
int n = matrix.size();
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (matrix[i][j] == -1) {
matrix[i][j] = 1e9;
}
if (i == j) matrix[i][j] = 0;
}
}

for (int k = 0; k < n; k++) {
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
matrix[i][j] = min(matrix[i][j],
matrix[i][k] + matrix[k][j]);
}
}
}

for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (matrix[i][j] == 1e9) {
matrix[i][j] = -1;
}
}
}
}
};

int main() {

int V = 4;
vector<vector<int>> matrix(V, vector<int>(V, -1));
matrix[0][1] = 2;
matrix[1][0] = 1;
matrix[1][2] = 3;
matrix[3][0] = 3;
matrix[3][1] = 5;
matrix[3][2] = 4;

Solution obj;
obj.shortest_distance(matrix);

for (auto row : matrix) {
for (auto cell : row) {
cout << cell << " ";
}
cout << endl;
}

return 0;
}

Time Complexity: O(V3), as we have three nested loops each running for V times, where V = no. of vertices.

Space Complexity: O(V2), where V = no. of vertices. This space complexity is due to storing the adjacency matrix of the given graph.

Prim’s Algorithm – Minimum Spanning Tree

class Solution
{
public:
//Function to find sum of weights of edges of the Minimum Spanning Tree.
int spanningTree(int V, vector<vector<int>> adj[])
{
priority_queue<pair<int, int>,
vector<pair<int, int> >, greater<pair<int, int>>> pq;

vector<int> vis(V, 0);
// {wt, node}
pq.push({0, 0});
int sum = 0;
while (!pq.empty()) {
auto it = pq.top();
pq.pop();
int node = it.second;
int wt = it.first;

if (vis[node] == 1) continue;
// add it to the mst
vis[node] = 1;
sum += wt;
for (auto it : adj[node]) {
int adjNode = it[0];
int edW = it[1];
if (!vis[adjNode]) {
pq.push({edW, adjNode});
}
}
}
return sum;
}
};

int main() {

int V = 5;
vector<vector<int>> edges = {{0, 1, 2}, {0, 2, 1}, {1, 2, 1}, {2, 3, 2}, {3, 4, 1}, {4, 2, 2}};
vector<vector<int>> adj[V];
for (auto it : edges) {
vector<int> tmp(2);
tmp[0] = it[1];
tmp[1] = it[2];
adj[it[0]].push_back(tmp);

tmp[0] = it[0];
tmp[1] = it[2];
adj[it[1]].push_back(tmp);
}

Solution obj;
int sum = obj.spanningTree(V, adj);
cout << "The sum of all the edge weights: " << sum << endl;

return 0;
}

Kruskal’s Algorithm – Minimum Spanning Tree

class DisjointSet {
vector<int> rank, parent, size;
public:
DisjointSet(int n) {
rank.resize(n + 1, 0);
parent.resize(n + 1);
size.resize(n + 1);
for (int i = 0; i <= n; i++) {
parent[i] = i;
size[i] = 1;
}
}

int findUPar(int node) {
if (node == parent[node])
return node;
return parent[node] = findUPar(parent[node]);
}

void unionByRank(int u, int v) {
int ulp_u = findUPar(u);
int ulp_v = findUPar(v);
if (ulp_u == ulp_v) return;
if (rank[ulp_u] < rank[ulp_v]) {
parent[ulp_u] = ulp_v;
}
else if (rank[ulp_v] < rank[ulp_u]) {
parent[ulp_v] = ulp_u;
}
else {
parent[ulp_v] = ulp_u;
rank[ulp_u]++;
}
}

void unionBySize(int u, int v) {
int ulp_u = findUPar(u);
int ulp_v = findUPar(v);
if (ulp_u == ulp_v) return;
if (size[ulp_u] < size[ulp_v]) {
parent[ulp_u] = ulp_v;
size[ulp_v] += size[ulp_u];
}
else {
parent[ulp_v] = ulp_u;
size[ulp_u] += size[ulp_v];
}
}
};
class Solution
{
public:
//Function to find sum of weights of edges of the Minimum Spanning Tree.
int spanningTree(int V, vector<vector<int>> adj[])
{
// 1 - 2 wt = 5
/// 1 - > (2, 5)
// 2 -> (1, 5)

// 5, 1, 2
// 5, 2, 1
vector<pair<int, pair<int, int>>> edges;
for (int i = 0; i < V; i++) {
for (auto it : adj[i]) {
int adjNode = it[0];
int wt = it[1];
int node = i;

edges.push_back({wt, {node, adjNode}});
}
}
DisjointSet ds(V);
sort(edges.begin(), edges.end());
int mstWt = 0;
for (auto it : edges) {
int wt = it.first;
int u = it.second.first;
int v = it.second.second;

if (ds.findUPar(u) != ds.findUPar(v)) {
mstWt += wt;
ds.unionBySize(u, v);
}
}

return mstWt;
}
};

int main() {

int V = 5;
vector<vector<int>> edges = {{0, 1, 2}, {0, 2, 1}, {1, 2, 1}, {2, 3, 2}, {3, 4, 1}, {4, 2, 2}};
vector<vector<int>> adj[V];
for (auto it : edges) {
vector<int> tmp(2);
tmp[0] = it[1];
tmp[1] = it[2];
adj[it[0]].push_back(tmp);

tmp[0] = it[0];
tmp[1] = it[2];
adj[it[1]].push_back(tmp);
}

Solution obj;
int mstWt = obj.spanningTree(V, adj);
cout << "The sum of all the edge weights: " << mstWt << endl;
return 0;
}