Java Graph Tutorial – How To Implement Graph Data Structure

This Comprehensive Java Graph Tutorial Explains Graph Data Structure in detail. It includes how to Create, Implement, Represent & Traverse Graphs in Java:

A graph data structure mainly represents a network connecting various points. These points are termed as vertices and the links connecting these vertices are called ‘Edges’. So a graph g is defined as a set of vertices V and edges E that connect these vertices.

Graphs are mostly used to represent various networks like computer networks, social networks, etc. They can also be used to represent various dependencies in software or architectures. These dependency graphs are very useful in analyzing the software and also at times debugging it.

=> Check ALL Java Tutorials Here.

Graph Data Structure

Java Graph Data Structure

Given below is a graph having five vertices {A,B,C,D,E} and edges given by {{AB},{AC},{AD},{BD},{CE},{ED}}. As the edges do not show any directions, this graph is known as ‘undirected graph’.

Undirected graph
Undirected Graph

Apart from the undirected graph shown above, there are several variants of the graph in Java.

Let's discuss these variants in detail.

Different Variants Of Graph

The following are some of the variants of the graph.

#1) Directed Graph

A directed graph or digraph is a graph data structure in which the edges have a specific direction. They originate from one vertex and culminate into another vertex.

The following diagram shows the example of directed graph.

directed graph
Directed Graph

In the above diagram, there is an edge from vertex A to vertex B. But note that A to B is not the same as B to A like in undirected graph unless there is an edge specified from B to A.

A directed graph is cyclic if there is at least one path that has its first and last vertex as same. In the above diagram, a path A->B->C->D->E->A forms a directed cycle or cyclic graph.

Conversely, a directed acyclic graph is a graph in which there is no directed cycle i.e. there is no path that forms a cycle.

#2) Weighted Graph

In a weighted graph, a weight is associated with each edge of the graph. The weight normally indicates the distance between the two vertices. The following diagram shows the weighted graph. As no directions are shown this is the undirected graph.

weighted graph
Weighted Graph

Note that a weighted graph can be directed or undirected.

How To Create A Graph?

Java does not provide a full-fledged implementation of the graph data structure. However, we can represent the graph programmatically using Collections in Java. We can also implement a graph using dynamic arrays like vectors.

Usually, we implement graphs in Java using HashMap collection. HashMap elements are in the form of key-value pairs. We can represent the graph adjacency list in a HashMap.

A most common way to create a graph is by using one of the representations of graphs like adjacency matrix or adjacency list. We will discuss these representations next and then implement the graph in Java using the adjacency list for which we will use ArrayList.

Graph Representation In Java

Graph representation means the approach or technique using which graph data is stored in the computer's memory.

We have two main representations of graphs as shown below.

Adjacency Matrix

Adjacency Matrix is a linear representation of graphs. This matrix stores the mapping of vertices and edges of the graph. In the adjacency matrix, vertices of the graph represent rows and columns. This means if the graph has N vertices, then the adjacency matrix will have size NxN.

If V is a set of vertices of the graph then intersection Mij in the adjacency list = 1 means there is an edge existing between vertices i and j.

To better understand this concept clearly, let us prepare an adjacency Matrix for an undirected graph.

Adjacency Matrix

As seen from the above diagram, we see that for vertex A, the intersections AB and AE are set to 1 as there is an edge from A to B and A to E. Similarly intersection BA is set to 1, as this is an undirected graph and AB = BA. Similarly, we have set all the other intersections for which there is an edge to 1.

In case the graph is directed, the intersection Mij will be set to 1 only if there is a clear edge directed from Vi to Vj.

This is shown in the following illustration.

Adjacency Matrix - Directed graph

As we can see from the above diagram, there is an edge from A to B. So intersection AB is set to 1 but intersection BA is set to 0. This is because there is no edge directed from B to A.

Consider vertices E and D. We see that there are edges from E to D as well as D to E. Hence we have set both these intersections to 1 in adjacency Matrix.

Now we move on to weighted graphs. As we know for the weighted graph, an integer also known as weight is associated with each edge. We represent this weight in the adjacency Matrix for the edge that exists. This weight is specified whenever there is an edge from one vertex to another instead of ‘1’.

This representation is shown below.

Adjacency Matrix - Directed Weighted Graph

Adjacency List

Instead of representing a graph as an adjacency matrix which is sequential in nature, we can also use linked representation. This linked representation is known as the adjacency list. An adjacency list is nothing but a linked list and each node in the list represents a vertex.

The presence of an edge between two vertices is denoted by a pointer from the first vertex to the second. This adjacency list is maintained for each vertex in the graph.

When we have traversed all the adjacent nodes for a particular node, we store NULL in the next pointer field of the last node of the adjacency list.

Now we will use the above graphs that we used to represent the adjacency matrix to demonstrate the adjacency list.

demonstrate adjacency list.

The above figure shows the adjacency list for the undirected graph. We see that each vertex or node has its adjacency list.

In the case of the undirected graph, the total lengths of adjacency lists are usually twice the number of edges. In the above graph, the total number of edges is 6 and the total or sum of the length of all the adjacency list is 12.

Now let's prepare an adjacency list for the directed graph.

total length of the adjacency

As seen from the above figure, in the directed graph the total length of the adjacency lists of the graph is equal to the number of edges in the graph. In the above graph, there are 9 edges and sum of the lengths of adjacency lists for this graph = 9.

Now let us consider the following weighted directed graph. Note that each edge of the weighted graph has a weight associated with it. So when we represent this graph with the adjacency list, we have to add a new field to each list node that will denote the weight of the edge.

The adjacency list for the weighted graph is shown below.

the weighted graph

The above diagram shows the weighted graph and its adjacency list. Note that there is a new space in the adjacency list that denotes the weight of each node.

Graph Implementation In Java

The following program shows the implementation of a graph in Java. Here we have used the adjacency list to represent the graph.

import java.util.*;

//class to store edges of the weighted graph
class Edge {
	int src, dest, weight;
	Edge(int src, int dest, int weight) {
			this.src = src;
			this.dest = dest;
			this.weight = weight;
		}
}
// Graph class
class Graph {
	// node of adjacency list 
	static class Node {
		int value, weight;
		Node(int value, int weight)  {
			this.value = value;
			this.weight = weight;
		}
	};

// define adjacency list

List<List<Node>> adj_list = new ArrayList<>();

	//Graph Constructor
	public Graph(List<Edge> edges)
	{
		// adjacency list memory allocation
		for (int i = 0; i < edges.size(); i++)
			adj_list.add(i, new ArrayList<>());

		// add edges to the graph
		for (Edge e : edges)
		{
			// allocate new node in adjacency List from src to dest
			adj_list.get(e.src).add(new Node(e.dest, e.weight));
		}
	}
// print adjacency list for the graph
	public static void printGraph(Graph graph)  {
		int src_vertex = 0;
		int list_size = graph.adj_list.size();

	    System.out.println("The contents of the graph:");
		while (src_vertex < list_size) {
			//traverse through the adjacency list and print the edges
			for (Node edge : graph.adj_list.get(src_vertex)) {
				System.out.print("Vertex:" + src_vertex + " ==> " + edge.value + 
								" (" + edge.weight + ")\t");
			}

			System.out.println();
			src_vertex++;
		}
	}
}
class Main{
	public static void main (String[] args)	{
		// define edges of the graph 
		List<Edge> edges = Arrays.asList(new Edge(0, 1, 2),new Edge(0, 2, 4),
                   new Edge(1, 2, 4),new Edge(2, 0, 5), new Edge(2, 1, 4),
				   new Edge(3, 2, 3), new Edge(4, 5, 1),new Edge(5, 4, 3));

		// call graph class Constructor to construct a graph
		Graph graph = new Graph(edges);

		// print the graph as an adjacency list
		Graph.printGraph(graph);
	}
}

Output:

output

Graph Traversal Java

To perform any meaningful action like searching for the presence of any data, we need to traverse the graph such that each vertex and the edge of the graph is visited at least once. This is done using graph algorithms that are nothing but a set of instructions that help us to traverse the graph.

There are two algorithms supported to traverse the graph in Java.

  1. Depth-first traversal
  2. Breadth-first traversal

Depth-first Traversal

Depth-first search (DFS) is a technique that is used to traverse a tree or a graph. DFS technique starts with a root node and then traverses the adjacent nodes of the root node by going deeper into the graph. In the DFS technique, the nodes are traversed depth-wise until there are no more children to explore.

Once we reach the leaf node (no more child nodes), the DFS backtracks and starts with other nodes and carries out traversal in a similar manner. DFS technique uses a stack data structure to store the nodes that are being traversed.

Following is the algorithm for the DFS technique.

Algorithm

Step 1: Start with the root node and insert it into the stack

Step 2: Pop the item from the stack and insert into the ‘visited’ list

Step 3: For node marked as ‘visited’ (or in visited list), add the adjacent nodes of this node that are not yet marked visited, to the stack.

Step 4: Repeat steps 2 and 3 until the stack is empty.

Illustration Of DFS Technique

Now we will illustrate the DFS technique using a proper example of a graph.

Given below is an example graph. We maintain stack to store explored nodes and a list to store visited nodes.

DFS technique

We will start with A to begin with, mark it as visited, and add it to the visited list. Then we will consider all the adjacent nodes of A and push these nodes onto the stack as shown below.

a node from the stack

Next, we pop a node from the stack i.e. B and mark it as visited. We then add it to the ‘visited’ list. This is represented below.

adjacent nodes

Now we consider the adjacent nodes of B which are A and C. Out of this A is already visited. So we ignore it. Next, we pop C from the stack. Mark C as visited. The adjacent node of C i.e. E is added to the stack.

next node

Next, we pop the next node E from the stack and mark it as visited. Node E’s adjacent node is C that is already visited. So we ignore it.

D remains in the stack

Now only node D remains in the stack. So we mark it as visited. Its adjacent node is A which is already visited. So we do not add it to the stack.

depth-first traversal

At this point the stack is empty. This means we have completed the depth-first traversal for the given graph.

The visited list gives the final sequence of traversal using the depth-first technique. The final DFS sequence for the above graph is A->B->C->E->D.

DFS Implementation


import java.io.*; 
import java.util.*; 
  
//DFS Technique for undirected graph
class Graph { 
    private int Vertices;   // No. of vertices 
  
    // adjacency list declaration
    private LinkedList<Integer> adj_list[]; 
  
    // graph Constructor: to initialize adjacency lists as per no of vertices 
    Graph(int v) { 
        Vertices = v; 
        adj_list = new LinkedList[v]; 
        for (int i=0; i<v; ++i) 
            adj_list[i] = new LinkedList(); 
    } 
  
    //add an edge to the graph 
    void addEdge(int v, int w) { 
        adj_list[v].add(w);  // Add w to v's list. 
    } 
  
    // helper function for DFS technique
    void DFS_helper(int v,boolean visited[]) { 
        // current node is visited 
        visited[v] = true; 
        System.out.print(v+" "); 
  
        // process all adjacent vertices 
        Iterator<Integer> i = adj_list[v].listIterator(); 
        while (i.hasNext()) 
        { 
            int n = i.next(); 
            if (!visited[n]) 
                DFS_helper(n, visited); 
        } 
    } 
  
   
void DFS(int v) { 
        //initially none of the vertices are visited 
        boolean visited[] = new boolean[Vertices]; 
  
        // call recursive DFS_helper function for DFS technique 
        DFS_helper(v, visited); 
    } 
}
  class Main{
    public static void main(String args[]) 
    { 
        //create a graph object and add edges to it
        Graph g = new Graph(5); 
        g.addEdge(0, 1); 
        g.addEdge(0, 2); 
        g.addEdge(0, 3); 
        g.addEdge(1, 2); 
        g.addEdge(2, 4); 
        //print the DFS Traversal sequence
        System.out.println("Depth First Traversal for given graph"+ 
                           "(with 0 as starting vertex)"); 
        g.DFS(0); 
    } 
}

Output:

output

Applications Of DFS

#1) Detect a cycle in a graph: DFS facilitates to detect a cycle in a graph when we can backtrack to an edge.

#2) Pathfinding: As we have already seen in the DFS illustration, given any two vertices we can find the path between these two vertices.

#3) Minimum spanning tree and shortest path: If we run the DFS technique on the non-weighted graph, it gives us the minimum spanning tree and the shorted path.

#4) Topological sorting: Topological sorting is used when we have to schedule the jobs. We have dependencies among various jobs. We can also use topological sorting for resolving dependencies among linkers, instruction schedulers, data serialization, etc.

Breadth-first Traversal

Breadth-first (BFS) technique uses a queue to store the nodes of the graph. As against the DFS technique, in BFS we traverse the graph breadth-wise. This means we traverse the graph level wise. When we explore all the vertices or nodes at one level we proceed to the next level.

Given below is an algorithm for the breadth-first traversal technique.

Algorithm

Let's see the algorithm for the BFS technique.

Given a graph G for which we need to perform the BFS technique.

  • Step 1: Begin with the root node and insert it into the queue.
  • Step 2: Repeat steps 3 and 4 for all nodes in the graph.
  • Step 3: Remove the root node from the queue, and add it to the Visited list.
  • Step 4: Now add all the adjacent nodes of the root node to the queue and repeat steps 2 to 4 for each node.[END OF LOOP]
  • Step 6: EXIT

Illustration Of BFS

Let us illustrate the BFS technique using an example graph shown below. Note that we have maintained a list named ‘Visited’ and a queue. We use the same graph that we used in the DFS example for clarity purposes.

Illustration of BFS

First, we start with root i.e. node A and add it to the visited list. All the adjacent nodes of the node A i.e. B, C, and D are added to the queue.

Visited list and mark

Next, we remove the node B from the queue. We add it to the Visited list and mark it as visited. Next, we explore the adjacent nodes of B in the queue (C is already in the queue). Another adjacent node A is already visited so we ignore it.

Visited list and mark

Next, we remove node C from the queue and mark it as visited. We add C to the visited list and its adjacent node E is added to the queue.

the queue and mark

Next, we delete D from the queue and mark it as visited. Node D’s adjacent node A is already visited, so we ignore it.

D’s adjacent node A

So now only node E is in the queue. We mark it as visited and add it to the visited list. The adjacent node of E is C which is already visited. So ignore it.

result of BFS traversal

At this point, the queue is empty and the visited list has the sequence we obtained as a result of BFS traversal. The sequence is, A->B->C->D->E.

BFS Implementation

The following Java program shows the implementation of the BFS technique.


import java.io.*; 
import java.util.*; 
//undirected graph represented using adjacency list.  
class Graph { 
    private int Vertices;   // No. of vertices 
    private LinkedList<Integer> adj_list[]; //Adjacency Lists 
  
    // graph Constructor:number of vertices in graph are passed 
    Graph(int v) { 
        Vertices = v; 
        adj_list = new LinkedList[v]; 
        for (int i=0; i<v; ++i)         //create adjacency lists
            adj_list[i] = new LinkedList(); 
    } 
  
    // add an edge to the graph 
    void addEdge(int v,int w) { 
        adj_list[v].add(w); 
    } 
  
    // BFS traversal from the root_node 
    void BFS(int root_node)   { 
        // initially all vertices are not visited 
        boolean visited[] = new boolean[Vertices]; 
  
        // BFS queue 
        LinkedList<Integer> queue = new LinkedList<Integer>(); 
  
        // current node = visited, insert into queue 
        visited[root_node]=true; 
        queue.add(root_node); 
  
        while (queue.size() != 0)  { 
            // deque an entry from queue and process it  
            root_node = queue.poll(); 
            System.out.print(root_node+" "); 
  
            // get all adjacent nodes of current node and process
            Iterator<Integer> i = adj_list[root_node].listIterator(); 
            while (i.hasNext()){ 
                int n = i.next(); 
                if (!visited[n]) { 
                    visited[n] = true; 
                    queue.add(n); 
                } 
            } 
        } 
    } 
  }
  class Main{ 
    public static void main(String args[]) 
    { 
        //create a graph with 5 vertices
        Graph g = new Graph(5); 
        //add edges to the graph  
        g.addEdge(0, 1); 
        g.addEdge(0, 2); 
        g.addEdge(0, 3); 
        g.addEdge(1, 2); 
        g.addEdge(2, 4); 
        //print BFS sequence
        System.out.println("Breadth-first traversal of graph with 0 as starting vertex:"); 
        g.BFS(0); 
    } 
}

Output:

output

Applications Of BFS Traversal

#1) Garbage collection: One of the algorithms used by the garbage collection technique to copy Garbage collection is “Cheney’s algorithm”. This algorithm uses a breadth-first traversal technique.

#2) Broadcasting in networks: Broadcasting of packets from one point to another in a network is done using the BFS technique.

#3) GPS navigation: We can use the BFS technique to find adjacent nodes while navigating using GPS.

#4) Social networking websites: BFS technique is also used in social networking websites to find the network of people surrounding a particular person.

#5) Shortest path and minimum spanning tree in un-weighted graph: In the unweighted graph, the BFS technique can be used to find a minimum spanning tree and the shortest path between the nodes.

Java Graph Library

Java does not make it compulsory for programmers to always implement the graphs in the program. Java provides a lot of ready libraries that can be directly used to make use of graphs in the program. These libraries have all the graph API functionality required to make full use of the graph and its various features.

Given below is a brief introduction to some of the graph libraries in Java.

#1) Google Guava: Google Guava provides a rich library that supports graphs and algorithms including simple graphs, networks, value graphs, etc.

#2) Apache Commons: Apache Commons is an Apache project that provides Graph data structure components and APIs that have algorithms that operate on this graph data structure. These components are reusable.

#3) JGraphT: JGraphT is one of the widely used Java graph libraries. It provides graph data structure functionality containing simple graph, directed graph, weighted graph, etc. as well as algorithms and APIs that work on the graph data structure.

#4) SourceForge JUNG: JUNG stands for “Java Universal Network/Graph” and is a Java framework. JUNG provides an extensible language for analysis, visualization, and modeling of the data that we want to be represented as a graph.

JUNG also provides various algorithms and routines for decomposition, clustering, optimization, etc.

Frequently Asked Questions

Q #1) What is a Graph in Java?

Answer: A graph data structure mainly stores connected data, for example, a network of people or a network of cities. A graph data structure typically consists of nodes or points called vertices. Each vertex is connected to another vertex using links called edges.

Q #2) What are the types of graphs?

Answer: Different types of graphs are listed below.

  1. Line graph: A line graph is used to plot the changes in a particular property relative to time.
  2. Bar graph: Bar graphs compare numeric values of entities like the population in various cities, literacy percentages across the country, etc.

Apart from these main types we also have other types like pictograph, histogram, area graph, scatter plot, etc.

Q #3) What is a connected graph?

Answer: A connected graph is a graph in which every vertex is connected to another vertex. Hence in the connected graph, we can get to every vertex from every other vertex.

Q #4) What are the applications of the graph?

Answer: Graphs are used in a variety of applications. The graph can be used to represent a complex network. Graphs are also used in social networking applications to denote the network of people as well as for applications like finding adjacent people or connections.

Graphs are used to denote the flow of computation in computer science.

Q #5) How do you store a graph?

Answer: There are three ways to store a graph in memory:

#1) We can store Nodes or vertices as objects and edges as pointers.

#2) We can also store graphs as adjacency matrix whose rows and columns are the same as the number of vertices. The intersection of each row and column denotes the presence or absence of an edge. In the non-weighted graph, the presence of an edge is denoted by 1 while in the weighted graph it is replaced by the weight of the edge.

#3) The last approach to storing a graph is by using an adjacency list of edges between graph vertices or nodes. Each node or vertex has its adjacency list.

Conclusion

In this tutorial, we have discussed graphs in Java in detail. We explored the various types of graphs, graph implementation, and traversal techniques. Graphs can be put to use in finding the shortest path between nodes.

In our upcoming tutorials, we will continue to explore graphs by discussing a few ways of finding the shortest path.

=> Watch Out The Simple Java Training Series Here.