Understanding Graph Data Structures: Adjacency Matrix and Adjacency List Representations

A graph is a data structure consisting of a set of vertices (nodes) and a set of edges that define the relationships between these vertices. Mathematically, a graph G is represented as G = (V, E), where:

  • V is a finite, non-empty set of vertices.
  • E is a finite set of relationships between vertices. For an undirected graph, an edge is represented as an unordered pair (x, y), meaning the relationship is bidirectional. For a directed graph, an edge is an ordered pair <x, y>, representing a directed connection from vertex x to vertex y.

Key Graph Terminology:

  • Vertex (Node): An individual element within a graph. Vertices can be denoted as vi.

  • Edge: A connection between two vertices. An edge ek connecting vertices vi and vj can be represented as (vi, vj) for undirected graphs or <vi, vj> for directed graphs.

  • Directed vs. Undirected Graphs: In a directed graph, edges have a specific direction (e.g., <x, y> is distinct from <y, x>). In an undirected graph, edges are bidirectional (e.g., (x, y) is the same as (y, x)).

  • Complete Graph: An undirected graph with n vertices is complete if every pair of distinct vertices is connected by a unique edge (n*(n-1)/2 edges total). A directed graph is complete if for every pair of distinct vertices, there's an edge in both directions (n*(n-1) edges total).

  • Degree of a Vertex: The number of edges incident to a vertex. In directed graphs, this is split into:

    • In-degree: The number of edges pointing towards the vertex.
    • Out-degree: The number of edges originating from the vertex.

    The total degree is the sum of in-degree and out-degree. For undirected graphs, the degree is simply the count of connected edges.

  • Path: A sequence of vertices connected by edges.

  • Path Length: For unweighted graphs, it's the number of edges in the path. For weighted graphs, it's the sum of the weights of the edges in the path.

  • Simple Path: A path where all vertices are distinct.

  • Cycle (Circuit): A path that starts and ends at the same vertex.

  • Subgraph: A graph formed by a subset of vertices and a subset of edges from a larger graph.

  • Connected Graph (Undirected): A graph where there is a path between every pair of vertices.

  • Strongly Connected Graph (Directed): A directed graph where for every pair of vertices vi and vj, there exists a path from vi to vj AND a path from vj to vi.

  • Spanning Tree: A subgraph that connects all vertices together, without any cycles, and with the minimum possible number of edges (n-1 edges for n vertices).

Graph Storage Structures

Graphs can be stored using two primary methods: the adjacency matrix and the adjacency list. Both methods focus on representing the vertices and their connections.

Adjacency Matrix Representation

An adjacency matrix uses a 2D array to represent the graph. The array's dimensions are determined by the number of vertices. Each cell matrix[i][j] indicates the relationship (or absence thereof) between vertex i and vertex j.

  • For unweighted graphs, a value of 1 typically signifies an edge, and 0 signifies no edge.
  • For weighted graphs, the cell stores the weight of the edge if one exists; otherwise, it might store infinity or a sentinel value indicating no connection.
  • In an undirected graph, the adjacency matrix is symmetric (matrix[i][j] == matrix[j][i]). The sum of elements in row i (or column i) corresponds to the degree of vertex i.
  • In a directed graph, the matrix is not necessarily symmetric. The sum of elements in row i represents the out-degree of vertex i, and the sum of elements in column i represents the in-degree.

Advantages: Quick checking of connectivity between two vertices.
Disadvantages: Can be space-inefficient for sparse graphs (many vertices, few edges) due to storing many zeros. Finding paths can be complex.

Implemantation Details (Adjacency Matrix)

A class can be implemented to manage the adjacency matrix. Key components include:

  • An array to store vertex identifiers.
  • A 2D array (matrix) to store edge information (weights or connection status).
  • A boolean flag to indicate if the graph is directed.

The constructor initializes the matrix, often filling it with a value representing infinity (e.g., Integer.MAX_VALUE) to signify no initial connections.


import java.util.Arrays;

public class AdjacencyMatrixGraph {
   private char[] vertices;
   private int[][] adjacencyMatrix;
   private boolean isDirected;
   private static final int INFINITY = Integer.MAX_VALUE;

   public AdjacencyMatrixGraph(int numVertices, boolean directed) {
       if (numVertices <= 0) {
           throw new IllegalArgumentException("Number of vertices must be positive.");
       }
       this.vertices = new char[numVertices];
       this.adjacencyMatrix = new int[numVertices][numVertices];
       this.isDirected = directed;

       // Initialize matrix with INFINITY
       for (int i = 0; i < numVertices; i++) {
           Arrays.fill(this.adjacencyMatrix[i], INFINITY);
           // A vertex is not connected to itself unless explicitly added
           this.adjacencyMatrix[i][i] = 0; // Self-loops often represented by 0 weight
       }
   }

   // Method to populate the vertex array
   public void setVertices(char[] vertexLabels) {
       if (vertexLabels.length != vertices.length) {
           throw new IllegalArgumentException("Number of vertex labels must match the graph size.");
       }
       System.arraycopy(vertexLabels, 0, this.vertices, 0, vertices.length);
   }

   // Get the index of a vertex by its label
   public int getVertexIndex(char label) {
       for (int i = 0; i < vertices.length; i++) {
           if (vertices[i] == label) {
               return i;
           }
       }
       return -1; // Vertex not found
   }

   // Add an edge between two vertices with a given weight
   public void addEdge(char sourceLabel, char destinationLabel, int weight) {
       int sourceIndex = getVertexIndex(sourceLabel);
       int destinationIndex = getVertexIndex(destinationLabel);

       if (sourceIndex == -1 || destinationIndex == -1) {
           throw new IllegalArgumentException("One or both vertices not found.");
       }
       if (weight < 0) {
            throw new IllegalArgumentException("Edge weight cannot be negative for this implementation detail.");
       }


       adjacencyMatrix[sourceIndex][destinationIndex] = weight;
       if (!isDirected) {
           // For undirected graphs, add the edge in both directions
           adjacencyMatrix[destinationIndex][sourceIndex] = weight;
       }
   }

   // Calculate the degree of a vertex
   public int getDegree(char label) {
       int index = getVertexIndex(label);
       if (index == -1) {
           throw new IllegalArgumentException("Vertex not found.");
       }

       int degree = 0;
       // Sum outgoing edges (row sum)
       for (int j = 0; j < vertices.length; j++) {
           if (adjacencyMatrix[index][j] != INFINITY && adjacencyMatrix[index][j] != 0) { // Exclude self-loops if not counted
               degree++;
           }
       }

       if (isDirected) {
           // Sum incoming edges (column sum), excluding the current vertex
           for (int i = 0; i < vertices.length; i++) {
                if (i != index && adjacencyMatrix[i][index] != INFINITY && adjacencyMatrix[i][index] != 0) {
                   degree++;
               }
           }
       }
       // Note: This calculation might need adjustment based on how self-loops are treated.
       // For standard definitions, self-loops usually add 2 to the degree in undirected graphs.
       // This implementation counts an edge once per connection.
       return degree;
   }

   // Print the graph representation
   public void displayGraph() {
       System.out.print("   ");
       for (char vertexLabel : vertices) {
           System.out.print(vertexLabel + " ");
       }
       System.out.println();

       for (int i = 0; i < vertices.length; i++) {
           System.out.print(vertices[i] + " ");
           for (int j = 0; j < vertices.length; j++) {
               if (adjacencyMatrix[i][j] == INFINITY) {
                   System.out.print("∞ ");
               } else {
                   System.out.print(adjacencyMatrix[i][j] + " ");
               }
           }
           System.out.println();
       }
   }

    // Example Usage:
    public static void main(String[] args) {
        // Undirected Graph Example
        AdjacencyMatrixGraph undirectedGraph = new AdjacencyMatrixGraph(4, false);
        undirectedGraph.setVertices(new char[]{'A', 'B', 'C', 'D'});
        undirectedGraph.addEdge('A', 'B', 5);
        undirectedGraph.addEdge('A', 'C', 3);
        undirectedGraph.addEdge('B', 'C', 2);
        undirectedGraph.addEdge('C', 'D', 4);

        System.out.println("Undirected Graph (Adjacency Matrix):");
        undirectedGraph.displayGraph();
        System.out.println("Degree of A: " + undirectedGraph.getDegree('A')); // Expected: 2
        System.out.println("Degree of C: " + undirectedGraph.getDegree('C')); // Expected: 3

        System.out.println("\n---------------------------\n");

        // Directed Graph Example
        AdjacencyMatrixGraph directedGraph = new AdjacencyMatrixGraph(4, true);
        directedGraph.setVertices(new char[]{'X', 'Y', 'Z', 'W'});
        directedGraph.addEdge('X', 'Y', 1);
        directedGraph.addEdge('Y', 'Z', 2);
        directedGraph.addEdge('Z', 'X', 3);
        directedGraph.addEdge('X', 'Z', 4); // Another edge from X

        System.out.println("Directed Graph (Adjacency Matrix):");
        directedGraph.displayGraph();
        System.out.println("Degree of X: " + directedGraph.getDegree('X')); // Expected: 3 (1 out to Y, 1 out to Z, 1 in from Z)
        System.out.println("Degree of Y: " + directedGraph.getDegree('Y')); // Expected: 2 (1 in from X, 1 out to Z)
    }
}
   

Adjacency List Representation

An adjacency list uses an array where each element points to a linked list (or similar structure) of nodes. Each node in the linked list represents an edge originating from the vertex at the corresponding array index.

  • Each entry in the main array corresponds to a vertex.
  • The linked list at index i contains nodes representing edges starting from vertex i.
  • Each node in the linked list typically stores the destination vertex, the edge weight, and a pointer to the next node in the list.

For undirected graphs: If there's an edge between vi and vj, vertex vj will appear in the list for vi, and vi will appear in the list for vj.

For directed graphs: If there's an edge from vi to vj, vj appears only in the list for vi. The number of nodes in the list for vertex vi directly gives its out-degree.

Advantages: More space-efficient for sparse graphs. Traversal algorithms like Breadth-First Search (BFS) and Depth-First Search (DFS) are often more efficient.

Disadvantages: Checking connectivity between two arbitrary vertices can take longer (proportional to the degree of the source vertex).

Implemantation Details (Adjacency List)

A common implementation uses an ArrayList where each element is either null or the head of a linked list. A static inner class can define the structure of a node in the linked list.


import java.util.ArrayList;
import java.util.List;

public class AdjacencyListGraph {

   // Represents an edge in the adjacency list
   static class EdgeNode {
       int destinationIndex;
       int weight;
       EdgeNode next;

       public EdgeNode(int destinationIndex, int weight) {
           this.destinationIndex = destinationIndex;
           this.weight = weight;
           this.next = null;
       }
   }

   private char[] vertexLabels;
   private List<edgenode> adjacencyLists; // List of linked lists, one for each vertex
   private boolean isDirected;
   private static final int UNCONNECTED = -1; // Placeholder for no connection in degree calculation

   public AdjacencyListGraph(int numVertices, boolean directed) {
       if (numVertices <= 0) {
           throw new IllegalArgumentException("Number of vertices must be positive.");
       }
       this.vertexLabels = new char[numVertices];
       this.adjacencyLists = new ArrayList<>(numVertices);
       this.isDirected = directed;

       // Initialize each list in the adjacencyLists
       for (int i = 0; i < numVertices; i++) {
           adjacencyLists.add(null); // Initially, each vertex has an empty adjacency list
       }
   }

   // Method to populate the vertex label array
   public void setVertexLabels(char[] labels) {
       if (labels.length != vertexLabels.length) {
           throw new IllegalArgumentException("Number of labels must match the graph size.");
       }
       System.arraycopy(labels, 0, this.vertexLabels, 0, vertexLabels.length);
   }

   // Get the index of a vertex by its label
   public int getVertexIndex(char label) {
       for (int i = 0; i < vertexLabels.length; i++) {
           if (vertexLabels[i] == label) {
               return i;
           }
       }
       return -1; // Vertex not found
   }

   // Helper method to add an edge to the list (used internally)
   private void addEdgeToList(int sourceIndex, int destinationIndex, int weight) {
       // Prevent adding duplicate edges between the same pair of vertices in the same direction
       EdgeNode current = adjacencyLists.get(sourceIndex);
       while (current != null) {
           if (current.destinationIndex == destinationIndex) {
               // Edge already exists, could update weight or ignore
               // For simplicity, we ignore adding duplicates here.
               return;
           }
           current = current.next;
       }

       // Add new edge using head insertion
       EdgeNode newNode = new EdgeNode(destinationIndex, weight);
       newNode.next = adjacencyLists.get(sourceIndex);
       adjacencyLists.set(sourceIndex, newNode);
   }

   // Add an edge between two vertices
   public void addEdge(char sourceLabel, char destinationLabel, int weight) {
       int sourceIndex = getVertexIndex(sourceLabel);
       int destinationIndex = getVertexIndex(destinationLabel);

       if (sourceIndex == -1 || destinationIndex == -1) {
           throw new IllegalArgumentException("One or both vertices not found.");
       }
        if (weight < 0) {
            throw new IllegalArgumentException("Edge weight cannot be negative for this implementation detail.");
       }


       addEdgeToList(sourceIndex, destinationIndex, weight);

       if (!isDirected) {
           // For undirected graphs, add the reverse edge as well
           addEdgeToList(destinationIndex, sourceIndex, weight);
       }
   }

   // Calculate the degree of a vertex
   public int getDegree(char label) {
       int index = getVertexIndex(label);
       if (index == -1) {
           throw new IllegalArgumentException("Vertex not found.");
       }

       int degree = 0;
       // Count outgoing edges (edges originating from this vertex)
       EdgeNode current = adjacencyLists.get(index);
       while (current != null) {
           degree++;
           current = current.next;
       }

       if (isDirected) {
           // For directed graphs, we also need to count incoming edges (in-degree)
           // This requires iterating through all other vertices' adjacency lists
           for (int i = 0; i < vertexLabels.length; i++) {
               // Skip the current vertex's own list
               if (i == index) continue;

               EdgeNode incomingEdgeChecker = adjacencyLists.get(i);
               while (incomingEdgeChecker != null) {
                   if (incomingEdgeChecker.destinationIndex == index) {
                       degree++; // Found an incoming edge
                   }
                   incomingEdgeChecker = incomingEdgeChecker.next;
               }
           }
       }
       // Note: This definition of degree counts each edge once. For undirected graphs,
       // this correctly represents the degree. For directed graphs, it sums in-degree and out-degree.
       return degree;
   }


   // Print the graph representation
   public void displayGraph() {
       for (int i = 0; i < vertexLabels.length; i++) {
           System.out.print(vertexLabels[i] + " -> ");
           EdgeNode current = adjacencyLists.get(i);
           if (current == null) {
               System.out.print("null");
           }
           while (current != null) {
               System.out.print("(" + current.destinationIndex + ":" + current.weight + ") -> ");
               current = current.next;
           }
           System.out.println("null");
       }
   }

    // Example Usage:
    public static void main(String[] args) {
        // Undirected Graph Example
        AdjacencyListGraph undirectedGraph = new AdjacencyListGraph(4, false);
        undirectedGraph.setVertexLabels(new char[]{'A', 'B', 'C', 'D'});
        undirectedGraph.addEdge('A', 'B', 5);
        undirectedGraph.addEdge('A', 'C', 3);
        undirectedGraph.addEdge('B', 'C', 2);
        undirectedGraph.addEdge('C', 'D', 4);

        System.out.println("Undirected Graph (Adjacency List):");
        undirectedGraph.displayGraph();
        // Note: getDegree calculation for adjacency list needs careful implementation
        // to correctly sum in-degree and out-degree for directed graphs,
        // and handle undirected graph representation where edges appear twice.
        // The provided getDegree sums list length + checks incoming for directed.
        System.out.println("Degree of A: " + undirectedGraph.getDegree('A')); // Expected: 2 (A-B, A-C)
        System.out.println("Degree of C: " + undirectedGraph.getDegree('C')); // Expected: 3 (C-A, C-B, C-D)

        System.out.println("\n---------------------------\n");

        // Directed Graph Example
        AdjacencyListGraph directedGraph = new AdjacencyListGraph(4, true);
        directedGraph.setVertexLabels(new char[]{'X', 'Y', 'Z', 'W'});
        directedGraph.addEdge('X', 'Y', 1); // X -> Y
        directedGraph.addEdge('Y', 'Z', 2); // Y -> Z
        directedGraph.addEdge('Z', 'X', 3); // Z -> X
        directedGraph.addEdge('X', 'Z', 4); // X -> Z

        System.out.println("Directed Graph (Adjacency List):");
        directedGraph.displayGraph();
        // For directed graph, getDegree sums out-degree (list length) and in-degree (searched from others)
        System.out.println("Degree of X: " + directedGraph.getDegree('X')); // Expected: 3 (Out: X->Y, X->Z. In: Z->X)
        System.out.println("Degree of Y: " + directedGraph.getDegree('Y')); // Expected: 2 (In: X->Y. Out: Y->Z)
    }
}
   </edgenode>

Tags: graph theory Data Structures adjacency matrix Adjacency List algorithms

Posted on Wed, 02 Sep 2026 16:46:28 +0000 by GoodCoffee