332. Constructing and Representing an Undirected Graph Using Adjacency Lists in Java

Ilakkuvaselvi (Ilak) Manoharan
7 min readSep 4, 2024

--

Photo by Andrew Ridley on Unsplash

Introduction

Graphs are fundamental data structures used in various fields, from computer science to biology. They represent networks of interconnected nodes (vertices) with edges connecting pairs of nodes. Graphs can model a wide range of problems, including social networks, transportation systems, and communication networks. In this article, we’ll explore how to construct and represent an undirected graph in Java using adjacency lists, a common and efficient method for handling sparse graphs.

1. What is an Undirected Graph?

An undirected graph is a type of graph where edges have no direction. This means that if there’s an edge between node Aand node B, you can traverse it both ways: from A to B and from B to A. The edge (A, B) is the same as (B, A).

2. What is an Adjacency List?

An adjacency list is a way of representing a graph as a collection of lists. Each node in the graph has a list associated with it, which contains all the nodes it is connected to by an edge. This method is particularly efficient for sparse graphs, where the number of edges is much less than the maximum possible number of edges.

3. Constructing an Undirected Graph Using Adjacency Lists in Java

Let’s break down the process of creating an undirected graph using adjacency lists step by step.

Step 1: Define the Graph Structure

To represent a graph in Java, you can use a list of lists, where each list corresponds to a node, and the elements of the list represent the nodes connected to it.

import java.util.*;

class Graph {
private List<List<Integer>> adjList;

public Graph(int V) {
adjList = new ArrayList<>(V);
for (int i = 0; i < V; i++) {
adjList.add(new ArrayList<>());
}
}

public void addEdge(int u, int v) {
adjList.get(u).add(v);
adjList.get(v).add(u); // Because the graph is undirected
}

public List<Integer> getNeighbors(int u) {
return adjList.get(u);
}

public void printGraph() {
for (int i = 0; i < adjList.size(); i++) {
System.out.print("Node " + i + ": ");
for (int j : adjList.get(i)) {
System.out.print(j + " ");
}
System.out.println();
}
}
}

Step 2: Create a Graph and Add Edges

Let’s create a graph with 5 vertices and add some edges.

public class Main {
public static void main(String[] args) {
Graph graph = new Graph(5);

graph.addEdge(0, 1);
graph.addEdge(0, 4);
graph.addEdge(1, 2);
graph.addEdge(1, 3);
graph.addEdge(1, 4);
graph.addEdge(2, 3);
graph.addEdge(3, 4);

graph.printGraph();
}
}

Output:

Node 0: 1 4 
Node 1: 0 2 3 4
Node 2: 1 3
Node 3: 1 2 4
Node 4: 0 1 3

In this example, we created a graph with 5 nodes (0 through 4) and added edges between them. The printGraph method shows the adjacency list representation of the graph.

4. Example Graphs

Let’s look at a few more examples to illustrate how adjacency lists can represent different types of graphs.

Example 1: A Simple Triangle Graph

Consider a graph with 3 vertices connected in a triangle.

Graph triangleGraph = new Graph(3);
triangleGraph.addEdge(0, 1);
triangleGraph.addEdge(1, 2);
triangleGraph.addEdge(2, 0);

triangleGraph.printGraph();

Output:

Node 0: 1 2 
Node 1: 0 2
Node 2: 1 0

This output shows that each node in the triangle is connected to the other two nodes.

Example 2: A Star Graph

A star graph has one central node connected to all other nodes.

Graph starGraph = new Graph(5);
starGraph.addEdge(0, 1);
starGraph.addEdge(0, 2);
starGraph.addEdge(0, 3);
starGraph.addEdge(0, 4);

starGraph.printGraph();

Output:

Node 0: 1 2 3 4 
Node 1: 0
Node 2: 0
Node 3: 0
Node 4: 0

In this example, node 0 is the central node, connected to all other nodes.

Example 3: A Path Graph

A path graph is a simple linear graph where each node is connected to exactly two other nodes (except for the endpoints).

Graph pathGraph = new Graph(4);
pathGraph.addEdge(0, 1);
pathGraph.addEdge(1, 2);
pathGraph.addEdge(2, 3);

pathGraph.printGraph();

Output:

Node 0: 1 
Node 1: 0 2
Node 2: 1 3
Node 3: 2

This represents a straight line of nodes from 0 to 3.

5. Advantages of Using Adjacency Lists

  1. Space Efficiency: Adjacency lists use space proportional to the number of vertices plus the number of edges, O(V + E), which is much more efficient than adjacency matrices for sparse graphs.
  2. Easy Traversal: Accessing all the neighbors of a node is straightforward and efficient, which is useful in many graph algorithms like DFS and BFS.
  3. Flexibility: Adjacency lists can easily accommodate additional data such as edge weights, making them versatile for various types of graphs.

6. Conclusion

Representing graphs using adjacency lists in Java is both efficient and easy to implement, especially when dealing with sparse graphs. The examples provided demonstrate how different types of undirected graphs can be constructed and represented using this method. Whether you are building a social network, a road map, or any other network-based model, understanding how to efficiently represent and manipulate graphs is a crucial skill.

Exploring Graph Representations in Java: Beyond Adjacency Lists

When representing graphs in Java (or any other programming language), adjacency lists are a popular method due to their efficiency in terms of space, especially for sparse graphs. However, there are several other methods to represent graphs, each with its own advantages and disadvantages depending on the specific use case:

1. Adjacency Matrix

Description:

  • An adjacency matrix is a 2D array where the rows and columns represent vertices, and the entries represent the presence or absence of edges.
  • For an undirected graph, the matrix is symmetric.

Implementation:

  • A graph with V vertices is represented as a V x V matrix. If there is an edge between vertex i and vertex j, then matrix[i][j] = 1 (or the edge weight if it’s a weighted graph); otherwise, matrix[i][j] = 0.

Pros:

  • Quick Edge Lookup: Checking if an edge exists between two vertices is very fast, taking constant time O(1).
  • Simple to Implement: The structure is straightforward and easy to implement.

Cons:

  • Space Inefficiency: For sparse graphs, it requires O(V^2) space, which is wasteful.
  • Slow to Traverse: Traversing all the neighbors of a vertex requires O(V) time, which can be slow if the graph is large and sparse.

Example:

0 1 0 0 1
1 0 1 0 0
0 1 0 1 0
0 0 1 0 1
1 0 0 1 0

2. Edge List

Description:

  • An edge list is simply a list (or array) of all the edges in the graph. Each edge is represented as a pair (or tuple) of vertices.

Implementation:

  • For an undirected graph, each edge between vertices u and v would be represented as (u, v) in the list.

Pros:

  • Space Efficiency: It uses O(E) space, where E is the number of edges, which is very efficient for sparse graphs.
  • Good for Edge Iteration: It’s easy to iterate over all the edges in the graph.

Cons:

  • Slow Edge Lookup: Checking if a specific edge exists takes O(E) time, which is inefficient.
  • No Direct Access to Neighbors: To find all neighbors of a vertex, you must traverse the entire edge list.

Example:

  • An edge list for a simple graph: [(0, 1), (0, 4), (1, 2), (2, 3), (3, 4)]

3. Incidence Matrix

Description:

  • An incidence matrix is a 2D array where rows represent vertices and columns represent edges. Each entry in the matrix indicates whether a vertex is incident to an edge.

Implementation:

  • For an undirected graph, if vertex i is connected to an edge j, then matrix[i][j] = 1; otherwise, it’s 0.

Pros:

  • Simple to Understand: Like the adjacency matrix, it is straightforward and provides a different perspective on graph representation.
  • Edge Representation: It clearly shows the relationship between vertices and edges.

Cons:

  • Space Inefficiency: Like the adjacency matrix, it can consume a lot of space, O(VE).
  • Complexity: Not as commonly used as adjacency lists or matrices, and may not be as intuitive for some operations.

Example:

  • For a graph with 5 vertices and 5 edges, the incidence matrix might look like:
1 1 0 0 0
1 0 1 0 0
0 1 1 1 0
0 0 1 0 1
0 0 0 1 1

4. Adjacency Set

Description:

  • Similar to an adjacency list, but instead of lists, sets are used to store neighbors.

Implementation:

  • This can be implemented using a List<Set<Integer>>, where each set contains the neighbors of a vertex.

Pros:

  • Fast Edge Lookup: Checking for the existence of an edge between two vertices is faster than with lists, typically O(1) in average-case scenarios.
  • Space Efficiency: More space-efficient than adjacency matrices, especially for sparse graphs.

Cons:

  • Higher Memory Overhead: Uses more memory than adjacency lists because of the overhead of storing sets.
  • Slightly Slower for Iteration: Iterating over the neighbors is slower than with a list.

Summary of Alternatives:

  • Adjacency Matrix: Best for dense graphs where quick edge lookups are needed.
  • Edge List: Best for graphs where edge iteration is frequent and space is a concern.
  • Incidence Matrix: Useful when you need a direct mapping between vertices and edges, though it’s less common.
  • Adjacency Set: A variation of adjacency lists, offering fast edge lookups at the cost of higher memory usage.

Each of these methods has its own trade-offs, and the choice depends on the specific requirements of the graph algorithm you are implementing, such as the need for efficient space usage, quick edge lookups, or easy traversal.

--

--