Why is understanding the representation of graphs important? One of the key reasons is that it helps us efficiently model real-life graph problems based on various properties and constraints. In other words, several fundamental properties of graphs impact the choice of data structures used to represent them. So, let’s move forward to understand this idea!
Just like other data structures, we can represent graphs using sequential and list representations: Adjacency list and adjacency matrix. We can apply both representations to model directed and undirected graphs.
In the adjacency-list representation, we create an array Adj[V] of size V, where each array element represents a graph vertex, and the array index corresponds to the vertex itself. On the other side, for each vertex u, Adj[u] is a list (array or linked list) that contains all the vertices adjacent to vertex u. So, the elements within the list at Adj[u] represent vertices that share an edge with vertex u. In other words, for each vertex v in Adj[u], there is an edge (u, v) in the graph.
Now, let’s consider another insight: How does this representation differ for directed and undirected graphs? In a directed graph, every edge is unidirectional. So, for any directed edge (u, v), node v will only appear in the adjacency list of vertex u. On the other hand, in an undirected graph, every edge is bidirectional, i.e., if (u, v) is an undirected edge, then u appears in v’s adjacency list, and vice versa. So, what can be derived from this observation?
We can also use adjacency lists to represent weighted graphs. For example, in a directed graph, we can simply store the weight w(u, v) of the edge (u, v) in vertex v within the adjacency list of vertex u. Overall, the adjacency-list representation is quite flexible, allowing us to modify it to implement various types of graphs.
For the adjacency-matrix representation of a graph G = (V, E), we assume that the vertices are numbered from 1, 2, …, V. So, the adjacency-matrix representation of a graph G consists of a V x V matrix A, such that A[u][v] = 1 if there is an edge between u and v, and A[u][v] = 0 if there is no edge between u and v.
Here are some observations:
Now, the critical question is: When should we use an adjacency list, and when should we use an adjacency matrix? Selecting the right graph data structure can have a significant impact on performance. Let’s explore this critical comparison.
In an undirected graph with V vertices, the maximum possible number of edges that can exist is V * (V — 1) / 2, which is O(V²). Here, we are assuming that there are no self-loops or parallel edges. So, the choice between an adjacency list and an adjacency matrix depends on the density of the graph.
As we can see from the above table, adjacency lists make it harder to verify whether a given edge (u, v) exists, as we must search through the appropriate list to find the edge. However, it is easy to design graph algorithms that avoid the need for such queries. For example, we can explore all the edges of the graph in one pass via a breadth-first or depth-first traversal and perform operations on the current edge as we visit it. In this way, adjacency lists are the right data structure for most graph applications.
In this code, the Graph class uses an adjacency list representation for the graph and supports various operations such as adding edges, deleting edges and storing information about the vertices and edges.
struct Edge
{
int destination;
int weight;
};
class Graph
{
private:
int V;
// Flag to indicate if the graph is directed
bool directed;
// Vector of vectors to represent adjacency list for each vertex.
vector<vector<Edge>> adj;
// Store the degree of each vertex.
vector<int> degree;
public:
// Constructor for the Graph class
Graph(int vertices, bool isDirected)
{
V = vertices;
directed = isDirected;
adj.resize(V);
degree.resize(V, 0);
}
// Function to add an edge with weight
void addEdge(int u, int v, int weight)
{
Edge edge1 = {v, weight};
adj[u].push_back(edge1);
degree[u] = degree[u] + 1;
if (directed == false)
{
Edge edge2 = {u, weight};
// Add the reverse edge for an undirected graph
adj[v].push_back(edge2);
degree[v] = degree[v] + 1;
}
}
// Function to delete an edge
void deleteEdge(int u, int v)
{
// Search for the edge in the adjacency list of u and remove it
for (auto it = adj[u].begin(); it != adj[u].end(); ++it)
{
if (it->destination == v)
{
adj[u].erase(it);
degree[u] = degree[u] - 1;
break;
}
}
// If the graph is undirected, also search and remove
// the reverse edge in the adjacency list of v.
if (directed == false)
{
for (auto it = adj[v].begin(); it != adj[v].end(); ++it)
{
if (it->destination == u)
{
adj[v].erase(it);
degree[v] = degree[v] - 1;
break;
}
}
}
}
// Add other graph operations and algorithm here
};
// Node to represent an edge
struct Node
{
int destination; // Destination vertex
int weight; // Weight of the edge
Node* next; // Pointer to the next edge
// Constructor to initialize a Node
Node(int dest, int w)
{
destination = dest;
weight = w;
next = NULL;
}
};
// Graph class with linked list representation
class Graph
{
private:
int V; // Number of vertices
bool directed; // Indicates whether the graph is directed or undirected
Node** adj; // Array of pointers to linked lists representing the adjacency list
int* degree; // Array to store the degree of each vertex in the graph
public:
// Constructor to initialize the graph
Graph(int vertices, bool isDirected)
{
V = vertices;
directed = isDirected;
// Allocate memory for adjacency list and degree arrays
adj = new Node*[V];
degree = new int[V];
// Initialize each vertex's adjacency list to NULL and degree to 0
for (int i = 0; i < V; i++)
{
adj[i] = NULL;
degree[i] = 0;
}
}
// Function to add an edge to the graph
void addEdge(int u, int v, int weight)
{
// Create an edge from u to v with the given weight
Node* newNode = new Node(v, weight);
// Add the new edge to the beginning of u's adjacency list
newNode->next = adj[u];
adj[u] = newNode;
// Increase the degree of vertex u
degree[u] = degree[u] + 1;
if (directed == false)
{
// If the graph is undirected, add the reverse edge from v to u
newNode = new Node(u, weight);
// Add the reverse edge to the beginning of v's adjacency list
newNode->next = adj[v];
adj[v] = newNode;
// Increase the degree of vertex v as well
degree[v] = degree[v] + 1;
}
}
// Function to delete an edge from the graph
void deleteEdge(int u, int v)
{
Node* current = adj[u];
Node* prev = NULL;
// Search for the edge from u to v in the adjacency list of u
while (current != NULL && current->destination != v)
{
prev = current;
current = current->next;
}
// If the edge is not found, return
if (current == NULL)
return;
// If the edge is found, remove it from the linked list
if (prev != NULL)
prev->next = current->next;
else
adj[u] = current->next;
// Delete the removed edge's memory
delete current;
// Decrease the degree of vertex u
degree[u] = degree[u] - 1;
if (directed == false)
{
// If the graph is undirected, repeat the process for the reverse edge from v to u
current = adj[v];
prev = NULL;
while (current != NULL && current->destination != u)
{
prev = current;
current = current->next;
}
if (current == NULL)
return;
if (prev != NULL)
prev->next = current->next;
else
adj[v] = current->next;
delete current;
degree[v] = degree[v] - 1;
}
}
// Add other graph operations and algorithms here
};
class Graph
{
private:
int V;
bool directed;
// 2D vector to represent the adjacency matrix of the graph,
// which stores the edge weights between vertices.
vector<vector<int>> adjMatrix;
// Vector to store the degree of each vertex
vector<int> degree;
public:
// Constructor to initialize the graph
Graph(int vertices, bool isDirected)
{
V = vertices;
directed = isDirected;
// Initialize the adjacency matrix with zeros (no edges)
adjMatrix.resize(V, vector<int>(V, 0));
// Initialize the degree vector with zeros (no edges)
degree.resize(V, 0);
}
// Function to add an edge to the graph
void addEdge(int u, int v, int weight)
{
// Set the edge weight in the adjacency matrix from u to v
adjMatrix[u][v] = weight;
// Increase the degree of vertex u
degree[u] = degree[u] + 1;
if (directed == false)
{
// If the graph is undirected, set the edge weight from v to u
adjMatrix[v][u] = weight;
// Increase the degree of vertex v as well
degree[v] = degree[v] - 1;
}
}
// Function to delete an edge from the graph
void deleteEdge(int u, int v)
{
// Remove the edge by setting the edge weight to 0 from u to v
adjMatrix[u][v] = 0;
// Decrease the degree of vertex u
degree[u] = degree[u] - 1;
if (directed == false)
{
// If the graph is undirected, remove the edge from v to u as well
adjMatrix[v][u] = 0;
// Decrease the degree of vertex v as well
degree[v] = degree[v] - 1;
}
}
// You can add other graph operations and algorithms here
};
Enjoy learning, enjoy algorithms!