This comprehensive Java Graph tutorial explains the Graph data structure in detail. It includes how to create, implement, represent, and traverse graphs in Java:
The graph data structure mainly represents a network connecting various points. These points are called vertices, and the links connecting these vertices are called "edges". Therefore, graph g is defined as a set of vertices V and edges E connecting these vertices.
Diagrams are mainly used to represent various networks, such as computer networks, social networks, etc. They can also be used to represent various dependencies in software or architecture. These dependency graphs are very useful for analyzing software and sometimes debugging it.
Java graph data structure
The graph given below has five vertices {A, B, C, D, E}, and its edges are given by { {AB}, {AC}, {AD}, {BD}, {CE}, {ED}} Out. Since the edge does not show any direction, this graph is called an "undirected graph".
Undirected graph
In addition to the undirected graph shown above, there are several graph variants in Java.
Let us discuss these variants in detail.
Different variants of the graph
Here are some variations of this diagram.
#1) Directed graph
A directed graph or directed graph is a graph data structure in which the edges have a specific direction. They originate from one vertex and eventually reach another vertex.
The following figure shows an example of a directed graph.
Directed graph
In the above figure, there is an edge from vertex A to vertex B. Note, however, that unless there is an edge from B to A, A to B is like B to A in an undirected graph.
If there is at least one path where the first and last vertices are the same, the directed graph is cyclic. In the above figure, the path A-> B-> C-> D-> E-> A forms a directed ring or cyclic graph.
On the contrary, a directed acyclic graph is a graph in which there is no directed ring, that is, there is no path forming a ring.
#2) Weighted graph
In a weighted graph, the weight is associated with each edge of the graph. The weight usually represents the distance between two vertices. The figure below shows the weighted graph. Since the direction is not shown, this is an undirected graph.
Weighted graph
Note that the weighted graph can be directed or undirected.
How to create graphics?
Java does not provide a complete implementation of graphical data structures. However, we can use Collections in Java to represent graphics programmatically. We can also use dynamic arrays like vectors to implement graphs.
Usually, we use HashMap collections to implement graphics in Java. HashMap elements take the form of key-value pairs. We can represent graph adjacency list in HashMap.
The most common way to create a graph is to use one of the representations of the graph, such as an adjacency matrix or adjacency list. Next, we will discuss these representations and then implement graphs in Java using adjacency lists that will use ArrayList.
Graphical representation in Java
Graphic representation refers to the method or technology of storing graphic data in computer memory.
We have two main graphical representations as shown below.
Adjacency matrix
The adjacency matrix is a linear representation of the graph. This matrix stores the mapping of the vertices and edges of the graph. In the adjacency matrix, the vertices of the graph represent rows and columns. This means that if the graph has N vertices, the size of the adjacency matrix will be NxN.
If V is a set of vertices of the graph, the intersection point M ij = 1 in the adjacency list indicates that there is an edge between vertices i and j.
In order to understand this concept better, let us prepare an adjacency matrix for undirected graphs.
As can be seen from the figure above, for vertex A, the intersections AB and AE are set to 1, because there are edges from A to B and A to E. Similarly, the intersection point BA is set to 1, because this is an undirected graph, AB = BA. Similarly, we set all other intersections with edges to 1.
In the case of a directed graph, only when there is a clear edge from Vi to Vj, the intersection Mij will be set to 1.
As shown below.
As you can see from the figure above, there is an edge from A to B. Therefore, the intersection AB is set to 1, and the intersection BA is set to 0. This is because there is no side from B to A.
Consider vertices E and D. We see that there are edges from E to D and from D to E. Therefore, we set these two intersection points to 1 in the adjacency matrix.
Now we continue with the weighted graph. As we know about weighted graphs, each edge is associated with an integer, also known as weight. We represent this weight for existing edges in the adjacency matrix. As long as there is an edge from one vertex to another instead of "1", assign the weight.
This representation is shown below.
Adjacency list
In addition to representing the graph as an adjacency matrix that is continuous in nature, we can also use link representation. This form of link representation is called an adjacency list. The adjacency list is just a linked list, and each node in the list represents a vertex.
The pointer from the first vertex to the second vertex indicates that there is an edge between the two vertices. Maintain this adjacency list for each vertex in the graph.
When we traverse all neighboring nodes of a particular node, we store NULL in the next pointer field of the last node of the adjacency table.
Now, we will use the graph used to represent the adjacency matrix above to demonstrate the adjacency list.
The figure above shows the adjacency list of an undirected graph. We see that every vertex or node has its adjacency list.
For undirected graphs, the total length of the adjacency list is usually twice the number of sides. In the above figure, the total number of edges is 6, and the sum of the lengths of all adjacency lists is 12.
Now, let us prepare an adjacency list for the directed graph.
As can be seen from the figure above, in a directed graph, the total length of the adjacency list of the graph is equal to the number of edges in the graph. In the above figure, there are 9 edges, and the sum of the lengths of the adjacency list in this figure = 9.
Now let us consider the following weighted directed graph. Note that each edge of the weighted graph has a weight associated with it. Therefore, when we use the adjacency list to represent the graph, we must add a new field to each list node to represent the weight of the edge.
The adjacency list of the weighted graph is shown below.
The figure above shows the weighted graph and its adjacency list. Note that there is a new space in the adjacency list, indicating the weight of each node.
Graph implementation in Java
The following program shows the implementation of Java graphics. Here, we use an adjacency list to represent the graph.
package 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:
Graph traversal Java
In order to perform any meaningful operation (such as searching whether there is any data), we need to traverse the graph so that each vertex and edge of the graph is accessed at least once. This is done by using graphics algorithms, which are just a set of instructions that help us traverse the graph.
Two algorithms are supported to traverse the graph in Java .
- Depth-first traversal
- Breadth first traversal
Depth-first traversal
Depth First Search (DFS) is a technique used to traverse trees or graphs. The DFS technology starts from the root node, and then traverses the adjacent nodes of the root node by traversing the graph more deeply. In DFS technology, nodes are traversed in the depth direction until there are no more children to explore.
Once we reach the leaf node (no more child nodes), DFS will backtrack and start from other nodes and traverse in a similar way. DFS technology uses a stack data structure to store the nodes being traversed.
The following is the algorithm of DFS technology.
algorithm
Step 1: Start from the root node and insert it into the stack
Step 2: Pop the item from the stack and insert the "visited" list
Step 3: For the node marked as "visited" (or visited list), add adjacent nodes of the node that have not been marked as visited to the stack.
Step 4: Repeat steps 2 and 3 until the stack is empty.
Illustration of DFS technology
Now, we will use appropriate graphical examples to illustrate DFS technology.
Given below is an example diagram. We maintain a stack to store explored nodes and a list to store visited nodes.
We will start with A, mark it as visited, and add it to the visited list. Then, we will consider all adjacent nodes of A and push these nodes onto the stack as shown below.
Next, we pop a node from the stack, namely B and mark it as visited. Then, add it to the "Visited" list. As follows.
Now we consider the neighboring nodes of B, namely A and C. Have already visited A among them. Therefore, we ignore it. Next, we pop C from the stack. Mark C as visited. The adjacent node of C, that is, E is added to the stack.
Next, we pop the next node E from the stack and mark it as visited. The neighbor of node E is C, which has been visited. Therefore, we ignore it.
Now, only node D remains in the stack. Therefore, we mark it as visited. Its neighbor node is A, which has already been visited. Therefore, we do not add it to the stack.
At this time, the stack is empty. This means that we have completed the depth-first traversal of the given graph.
The access list uses depth-first technology to give the final order of traversal. The final DFS sequence in the figure above is A-> B-> C-> E-> D.
DFS implementation
package GraphDFS;
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:
Application of DFS
#1) Detect cycles in the graph: When we can go back to the edge, DFS helps to detect cycles in the graph.
#2) Pathfinding: As we have 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 DFS technology on a non-weighted graph, it will provide us with the minimum spanning tree and shortest path.
#4) Topological sorting: When we have to schedule homework, use topological sorting. We have dependencies between various jobs. We can also use topological sorting to resolve the dependencies between linkers, instruction schedulers, data serialization, etc.
Breadth first traversal
Breadth First (BFS) technology uses queues to store the nodes of the graph. Unlike DFS technology, in BFS, we traverse the graph horizontally. This means that we traverse the graph wisely. When we explore all vertices or nodes on one level, we will move to the next level.
Given below is the algorithm of breadth-first traversal technology .
algorithm
Let us look at the algorithm of BFS technology.
Given a graph G, we need to perform BFS technology on it.
- Step 1: Start from 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 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 use the example diagram shown below to illustrate the BFS technology. Please note that we maintain a list called "Visit" and a queue. For clarity, we use the same graph used in the DFS example.
First, we start from the root, node A, and add it to the access list. All adjacent nodes of node A (ie, B, C, and D) are added to the queue.
Next, we remove node B from the queue. We add it to the "visited" list and mark it as "visited". Next, we explore the neighboring nodes of B in the queue (C is already in the queue). Another adjacent node A has already been visited, so we ignore it.
Next, we remove node C from the queue and mark it as visited. We add C to the visit list, and add its neighboring node E to the queue.
Next, we remove D from the queue and mark it as visited. The neighbor node A of node D has been visited, so we ignore it.
Therefore, now only node E is in the queue. We mark it as visited and add it to the visited list. The neighboring node of E is C, which has been visited. Therefore, please ignore it.
At this point, the queue is empty, and the access list has the sequence we obtained through BFS traversal. The order is A-> B-> C-> D-> E.
BFS implementation
The following Java program shows the implementation of BFS technology.
package GraphBFS;
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:
Application of BFS traversal
#1) Garbage collection: One of the algorithms used by garbage collection technology to replicate garbage collection is the "Cheney Algorithm". The algorithm uses breadth-first traversal technology.
# 2) Network Broadcast : using techniques BFS data packets from the network a point to point broadcast.
#3) GPS navigation: When using GPS navigation, we can use BFS technology to find adjacent nodes.
#4) Social networking sites: BFS technology is also used in social networking sites to find the interpersonal network around a specific person.
#5) Shortest path and minimum spanning tree in unweighted graph : In unweighted graph, BFS technology can be used to find the shortest path between the minimum spanning tree and nodes.
Java graphics library
Java does not force programmers to always implement graphics in their programs. Java provides a lot of ready-made libraries, you can use them directly to use the graphs in the program. These libraries have all the graphics API functions required to make full use of graphics and their various functions.
Given below is a brief introduction to some graphics 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, which operate on the graph data structure. These components are reusable.
#3) JGraphT: JGraphT is one of the widely used Java graph libraries. It provides graph data structure functions, including simple graphs, directed graphs, weighted graphs, etc., as well as algorithms and APIs that work on graph data structures.
#4) SourceForge JUNG: JUNG stands for "Java Universal Network/Graphics" and is a Java framework. JUNG provides an extensible language for analyzing, visualizing and modeling the data we want to represent as graphics.
JUNG also provides various algorithms and routines for decomposition, clustering, optimization, etc.
Frequently asked questions
Q#1) What is a Java graph?
Answer: The graph data structure mainly stores the connected data, for example, the network of people or the network of cities. Graph data structures are usually composed of nodes or points called vertices. Each vertex is connected to another vertex using links called edges.
Q#2) What is the type of graph?
Answer: The different types of diagrams are listed below.
- Line chart: A line chart is used to plot the change of a specific attribute with respect to time.
- Bar graph: The bar graph compares the values of entities, such as the population of each city, the national literacy rate, etc.
In addition to these main types, we also have other types, such as pictograms, histograms, area charts, scatter charts, etc.
Q#3) What is a connected graph?
Answer: A connected graph is a graph in which each vertex is connected to another vertex. Therefore, in the connection graph, we can reach every vertex from every other vertex.
Q#4) What is the application of graphs?
Answer: Graphics can be used in a variety of applications. This diagram can be used to represent a complex network. Graphics are also used in social networking applications to represent networks of people and applications such as finding neighbors or contacts.
Graphics are used to represent the calculation process in computer science.
Question #5) How to store graphics?
Answer: There are three ways to store graphics in memory:
#1) We can store nodes or vertices as objects and edges as pointers.
#2) We can also store the graph as an adjacency matrix with the same number of rows and columns as the vertices. The intersection of each row and column indicates the presence or absence of an edge. In a non-weighted graph, the presence of an edge is represented by 1, while in a weighted graph, the presence of an edge is replaced by the weight of the edge.
#3) The last way to store a graph is to use an adjacency list of graph vertices or edges between nodes. Each node or vertex has its adjacency list.
in conclusion
In this tutorial, we discussed graphics in Java in detail. We explored various types of graphs, graph implementation and traversal techniques. You can use a graph to find the shortest path between nodes.