Your constructor might as well be public; it doesn't matter if a user can create a separate Graph
instance after all.
private int V; // Number of vertices
private int E; // Number of edges
Why not directly call them numberOfVertices
and numberOfEdges
or even just vertices
and edges
? Throughout the program you act if the variables are part of a math equation. However, that doesn't make your code any more readable.
Note that on most IDE's you can easily refactor after typing in the code as well. The current names make the variables look like generic types or constants. Not good.
public int vertices() {
I'd call that getVertices
; you directly retrieve a field value without any other side effects, so this is consider a getter by most.
public void addEdge(int v, int w) {
Now we have two variables called v
, that's not a good idea, even if they just vary in case.
About the functionality of the program:
- There doesn't seem to be an option in case both
v
and w
are present; in that case an edge needs to be added (it's separate from the other ones as no vertex needs to be added).
- This program assumes that if one of the vertices already exists (the first branch in the
if
statement) that the edge cannot add a vertex (at least the count doesn't go up). This seems incorrect.
- It also assumes that you can add parallel edges. So this is a multi-graph. In that case name your class that way.
- In the case none of the vertices exist then you only add one to
V
which cannot be correct.
- There doesn't seem any special handling for
v == w
: so called loops. I don't know if that's correct or not.
If you only want singular edges between vertices then you should explicitly skip if a v
can be found with a w
in the list. In that case you might be better off using a SortedSet
such as a TreeSet
.
If you allow your vertices and edges to be counted then you you would not have the trouble of keeping more state than necessary and getting out of sync. For instance, you could simply only count those edges to values higher than the key in the map (or divide by 2?). Counting the vertices should be easy.
For instance, I would come up with something similar to the following:
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
/**
* Implements a multi-graph without loops. A loop is an edge from one vertex to the same vertex.
*/
public class MultiGraph {
private HashMap<Integer, List<Integer>> adjacentVerticesByVertex;
/**
* Constructs an empty graph.
*/
public MultiGraph() {
adjacentVerticesByVertex = new HashMap<>();
}
public int getNumberOfVertices() {
return this.adjacentVerticesByVertex.size();
}
public int getNumberOfEdges() {
int edgesBothWays = 0;
for (var adjForVertice : adjacentVerticesByVertex.values()) {
edgesBothWays += adjForVertice.size();
}
return edgesBothWays / 2;
}
public void addEdge(int vertexA, int vertexB) {
if (vertexA == vertexB) {
throw new IllegalArgumentException("Loops are not allowed");
}
addVertexAsAdjacent(vertexA, vertexB);
addVertexAsAdjacent(vertexB, vertexA);
}
private void addVertexAsAdjacent(int vertexA, int vertexB) {
var verticesForVertexA =
adjacentVerticesByVertex.getOrDefault(vertexA, new LinkedList<Integer>());
verticesForVertexA.add(vertexB);
if (verticesForVertexA.size() == 1) {
adjacentVerticesByVertex.put(vertexA, verticesForVertexA);
}
}
@Override
public String toString() {
var sb = new StringBuilder();
sb.append(String.format("Vertices: %d, Edges: %d, Adjacent vertices:",
getNumberOfVertices(), getNumberOfEdges()));
for (int key : adjacentVerticesByVertex.keySet()) {
sb.append(String.format(" (V: %d ", key));
var nodesList = adjacentVerticesByVertex.get(key);
sb.append(Arrays.toString(nodesList.toArray()));
sb.append(")");
}
return sb.toString();
}
public static void main(String[] args) {
var g = new MultiGraph();
g.addEdge(0, 1);
g.addEdge(0, 2);
g.addEdge(0, 2);
g.addEdge(10, 11);
System.out.println(g);
}
}
The disadvantage of counting all the edges this way is of course that you have to iterate over all vertices and check the size. Furthermore, as it is, it stores each edge twice - this way it is easier to go back and forth between two vertices with an edge, but it also means more memory usage.
But as you can see V is now gone, and just getting the number of keys in the set is undoubtedly just one call, so we are all the better for it: try and minimize the state within objects falls firmly within KISS principles.