In our previous post, we stored the graph in Edges List and Vertices List. And we saw that time complexity of performing operations in this representation is very high. So, we need another representation which can perform operations in less time.
Another representation of the graph is a 2D array of size V x V called Adjacency Matrix. Let’s call that matrix adjacencyMatrix. If we have an edge between nodes s and d, then adjacencyMatrix[s][d] is set to 1 or weight, else set to infinity.
Unweighted Undirected Graph
Consider the following graph
The adjacency matrix of above graph is
There is an edge between 1 and 2, so we put 1 in adjacencyMatrix[1][2] and also in adjacencyMatrix[2][1] as this is an undirected graph. We give value 1 here because there is no weight for an edge. There is no edge between 1 and 3, so we put infinity in adjacencyMatrix[1][3].
In Java, we initialize a 2D array adjacencyMatrix[size+1][size+1], where size is the total number of vertices in the graph. For the above graph, the matrix will have a size of 7. We assign Integer.MIN_VALUE which is equivalent to negative infinity to adjacencyMatrix.
(Note: We can assign an index from 0 to 6 to all the vertices, but that creates confusion. Here, we use node names as the array index in this implementation. Since there is no node 0, and node start from 1, we increase the matrix size by 1. If you have string nodes, you can give them indices from zero and use size instead of size+1.)
public class GraphAdjacencyMatrix{ //2-D array to store adjacency matrix private int[][] adjacencyMatrix; //variable to store size of 2-D array/adjacency matrix private int size; //Constructor to initialize the matrix and size public GraphAdjacencyMatrix(int size){ this.size = size; adjacencyMatrix = new int[size][size]; for(int[] single : adjacencyMatrix) Arrays.fill(single, Integer.MIN_VALUE); } }
Add an edge
Now we add edges between two nodes using the following method. Since the graph in fig 1 is undirected, we add an edge from source to destination and destination to source by assigning one to adjacencyMatrix[destination] and adjacencyMatrix[destination].
public void add(int source, int destination){ adjacencyMatrix[destination] = 1; adjacencyMatrix[destination] = 1; }
Find Adjacent Vertices
The findAdjacent method takes an index as input and returns anArrayList of vertices adjacent to the index. To find all adjacent vertices of the index, we need to traverseadjacencyMatrix[vertex] and add those vertices to list whose value is 1.
Since we traverse adjacencyMatrix[vertex] linearly, whose size is V (V being the number of vertices in the graph), the time complexity of this operation is O(V).
public ArrayList<Integer> findAdjacent(int index){ ArrayList<Integer> adjacent = new ArrayList<Integer>(); for(int i=1;i<size;i++){ if(adjacencyMatrix[index][i] == 1) adjacent.add(i); } return adjacent; }
Find whether two nodes are connected
To find whether source and destination are connected, we just check whether adjacencyMatrix[destination] or adjacencyMatrix[destination] is equal to 1. If any of them is equal to 1, source and destination are connected else they are not connected. The time complexity of this operation is O(1) as we only access a position in a 2D matrix.
public boolean isConnected(int source, int destination){ if(adjacencyMatrix[destination] == 1 || adjacencyMatrix[destination] == 1) return true; return false; }
The entire code for a unweighted undirected graph is available here.
Weighted Undirected Graph
If an edge between source and destination has a weight then we assign value of weight to adjacencyMatrix[destination] and adjacencyMatrix[destination].
public void add(int source, int destination, int weight){ adjacencyMatrix[destination] = weight; adjacencyMatrix[destination] = weight; }
To find adjacent nodes of a given node, we add all those nodes to the list whose value is not equal to Integer.MIN_VALUE.
public ArrayList<Integer> findAdjacent(int index){ ArrayList<Integer> adjacent = new ArrayList<Integer>(); for(int i=1;i<size;i++){ if(adjacencyMatrix[index][i] != Integer.MIN_VALUE) adjacent.add(i); } return adjacent; }
Similarly to check whether two nodes are connected or not, we check whether adjacencyMatrix[destination] or adjacencyMatrix[destination] is not equal to Integer.MIN_VALUE.
public boolean isConnected(int source, int destination){ if(adjacencyMatrix[destination] != Integer.MIN_VALUE || adjacencyMatrix[destination] != Integer.MIN_VALUE) return true; return false; }
The whole code for the weighted undirected graph can be found here.
Weighted Directed Graph
In case of directed weighted graph, we assign weight to only adjacencyMatrix[destination] and not to adjacencyMatrix[destination]. Other operations are same as those for the above graphs. The whole code for directed weighted graph is available here.
Problems in this approach
If we have a graph with million nodes, then the space this graph takes is square of million, as adjacency matrix is a 2D array. That’s a lot of space. It is good when we have a large number of vertices and equally large number of edges between them, as then we will have a dense matrix. But If we have a graph with a lot of vertices and very few edges, resulting in a sparse matrix, we store a lot of infinite values which are unnecessary and take a lot of space. The next implementation Adjacency List, which we cover in next post improves upon this.
A large number of vertices and equally large number of edges between them will produce a dense matrix. Here, using adjacency matrix is efficient. But a large number of vertices and very few edges between them will produce a sparse matrix. Here, using adjacency matrix is inefficient as we store a lot of infinite values (taking up large space) which are unnecessary. The next implementation Adjacency List, which we cover in next post improves upon this.