Adjacency matrix representation of graphs (Java)

Posted by BLeez on Sun, 30 Jan 2022 00:36:37 +0100

1, Basic knowledge understanding

(I have some colloquial explanations + Baidu subject explanations. Please consult the literature for professional explanations. I compiled the introduction graph theory of Discrete Mathematics (Fifth Edition) through Qu Wanling, Geng Suyun and Zhang Linang. This book is a rare good book for introductory computer majors.)

Graph: describes the structure of a set of objects (elements) (defined by two or three tuples).

Vertex: each object (element) in the graph is called a vertex.

Edge: used for the relationship between two vertices. (Arc): edge with direction; Tail: initial point; Head: terminal point)

Binary (V,E): V is called the vertices set, e is called the edges set, V(G) and E(G).

Triples (V,E,I): I is called an association function. I maps each element in E to v × V (similar to the representation of adjacency matrix).

Directed graph: every edge in the graph has a direction.

Undirected graph: omitted.

Simple graph: undirected graph if there is only one edge between any two vertices (in a directed graph, there is only one edge in each direction between two vertices); The edge set contains no rings.

Weight: the weight carried by the edge in the graph.

Degree: the degree of a vertex refers to the number of edges associated with the vertex. The degree of vertex v is recorded as d(v).

Loop: if two vertices of an edge are the same, the edge is called a loop.

Path: a path from u to v refers to a sequence v0, E1, v1, E2, v2 EK, VK, where the vertex of ei is vi and vi - 1, and k is called the length of the path. If its starting and ending vertices are the same, the path is "closed", otherwise, it is called "open". A path is called a simple path. If all vertices in the path are not equal except that the start and end vertices can coincide.

In degree and out degree: for a directed graph, the degree of a vertex can be subdivided into in degree and out degree. The penetration of a vertex refers to the number of edges ending at each edge associated with it; Out degree is a relative concept, which refers to the number of edges starting from the vertex.

Track: if the vertices in path P(u,v) are different, the path is called a track from u to v.

A closed track is called a Circuit, and a closed track is called a Cycle.

Connected component: a polar connected subgraph of undirected graph G is called a connected component (or connected branch) of G. A connected graph has only one connected component, that is, itself; A non connected undirected graph has multiple connected components.

Strongly connected graph: in a directed graph G=(V,E), if there are paths from x to y and from y to x for any two different vertices x and Y in V, then G is a strongly connected graph. Accordingly, there is the concept of strongly connected components. A strongly connected graph has only one strongly connected component, that is, itself; Non strongly connected digraphs have multiple strongly connected components.

Unidirectional connected graph: Let G = be a directed graph. If u - > v means that graph G contains at most one simple path from u to v, graph G is a simply connected graph.

Weakly connected graph: replace all directed edges of a directed graph with undirected edges, and the resulting graph is called the base graph of the original graph. If the base graph of a directed graph is connected, the directed graph is weakly connected.

Primary path: all vertices in the path are different from each other. The primary pathway must be a simple pathway, but the opposite is not true.

. . .

(there are other terms such as bridge, self ring edge and cut point, which are not involved in the experiment in this paper, so they will not be introduced, which will be supplemented later)

2, Storage structure of graph

Array (adjacency matrix) stores the representation (directed or undirected) (this paper will use adjacency matrix to realize graph structure)

Adjacency table storage representation

Cross linked list storage representation of directed graph

Adjacency multiple table storage representation of undirected graph

3, Java code implementation

Completion status:

  1. The adjacency matrix representation is used, but it is not a traditional two-dimensional array, but a two-dimensional List, which is convenient for the addition and deletion of elements in the figure;
  2. A set of code implements four types of graphs: directed unauthorized / authorized graph and undirected unauthorized / authorized graph, but different types cannot be converted to each other (it is not impossible, but there is no theoretical basis for conversion, which is of little significance);
  3. It includes depth / breadth first traversal, ring finding algorithm, etc. (but it does not include any algorithm related to weight, because the calculation of weight requires a comparator, and I have no systematic fusion scheme for the time being);
  4. Excellent algorithm efficiency (the algorithm is broken by artificial space-time complexity. If someone is willing to carefully review the algorithm I wrote, he may understand my pains, Wuwuwuwu ~ ~);
  5. The whole set of code is full of details. It can be said that every more complex method has been carefully optimized by me;
  6. My algorithmic style - "the world's martial arts, only fast can not be broken!", In order to pursue efficient time efficiency, I never consider the waste of space. The problem that hardware can solve is none of our software!
  7. It will be continuously improved and updated in the later stage;
  8. You can go whoring in vain, but I hope the students who go whoring in vain can have more products and endless learning when they have time!
  9. It's useless to say more. Thousands of lines of code will lead you to the world of graph theory. Let's learn and make progress together!

Code implementation:

Vertex.java

package graph;

/**
 * This class is not enabled (prepare for multigraphs with parallel edges)
 *
 * @param <E> Types of information carried by vertices
 */
public class Vertex<E> {

    private E value;

    public Vertex(E value) {
        this.value = value;
    }

    @Override
    public String toString() {
        return "graph.Vertex{" +
                "value=" + value +
                '}';
    }
}

Edge.java

package graph;

import com.sun.istack.internal.NotNull;

/**
 * Arc / Edge
 * Edge: represents the relationship between two vertices
 * Arcs: directional edges
 *
 * @param <T> Weight type
 * @param <V> Information type
 */
public class Edge<T, V> {
    /**
     * Tail / initial point
     * (In a directed graph, it is used to distinguish the direction of the edge and to record the coordinates of the edge corresponding to the vertex in the adjacency matrix; In an undirected graph, there is no direction)
     */
    @NotNull
    private final int tailIndex;

    /**
     * Head / end point
     * (In a directed graph, it is used to distinguish the direction of the edge and to record the coordinates of the edge corresponding to the vertex in the adjacency matrix; In an undirected graph, there is no direction)
     */
    @NotNull
    private final int headIndex;

    /**
     * For the unauthorized graph, use true or false to indicate whether it is adjacent or not;
     * For the weighted graph, it is the weight.
     */
    private Object weight;

    /**
     * info Indicates the information carried by the arc / edge
     */
    private V info;

    public Edge(@NotNull int tailIndex, @NotNull int headIndex, Object weight, V info) {
        this.tailIndex = tailIndex;
        this.headIndex = headIndex;
        this.weight = weight;
        this.info = info;
    }

    public int getTailIndex() {
        return tailIndex;
    }

    public int getHeadIndex() {
        return headIndex;
    }

    @SuppressWarnings("unchecked")
    public T getWeight() {
        return (T) weight;
    }

    public void setWeight(Object weight) {
        this.weight = weight;
    }

    public V getInfo() {
        return info;
    }

    public void setInfo(V info) {
        this.info = info;
    }

    @Override
    public String toString() {
        return "Edge{" +
                "tailIndex=" + tailIndex +
                ", headIndex=" + headIndex +
                ", weight=" + weight +
                ", info=" + info +
                '}';
    }
}

GraphKind.java

package graph;

/**
 * Types of Graphs
 * (Weighted graphs (often referred to as nets)
 */
public enum GraphKind {
    /**
     * Directed graph
     */
    DG,
    /**
     * Directed net
     */
    DN,
    /**
     * Undirected graph
     */
    UDG,
    /**
     * Undirected network
     */
    UDN
}

Graph.java

package graph;

import java.util.List;
import java.util.Set;

/**
 * Data structure diagram
 *
 * @param <E> Vertex Type 
 * @param <T> Arc / edge weight type
 * @param <V> Arc / edge information type
 */
public interface Graph<E, T, V> {

    int getVertexNum();

    int getEdgeNum();

    GraphKind getKind();

    int findVertex(E vertex);

    int findLastVertex(E vertex);

    Set<Integer> findVertexIndexes(E vertex);

    E getVertex(int index);

    E setVertex(int index, E vertex);

    boolean addVertex(E vertex);

    boolean addVertexes(List<E> vertexes);

    void addVertex(int index, E vertex);

    E deleteVertex(int index);

    int deleteFirstVertex(E vertex);

    List<E> deleteVertexes(int[] vertexIndexes);

    Edge<T, V> addEdgeByIndex(int index1, int index2);

    Edge<T, V> addEdgeByIndex(int index1, int index2, T weight, V info);

    Set<Edge<T, V>> addEdgesByIndexes(Set<int[]> indexes);

    Edge<T, V> deleteEdge(int index1, int index2);

    Edge<T, V> updateEdge(int index1, int index2, T weight, V info);

    Edge<T, V> updateEdgeWeight(int index1, int index2, T weight);

    Edge<T, V> updateEdgeInfo(int index1, int index2, V info);

    boolean hasEdge(int index1, int index2);

    Edge<T, V> getEdge(int index1, int index2);

    Edge<T, V> getFirstAdjacentEdge(int index);

    Set<Edge<T, V>> getAdjacentEdges(int index);

    Set<Edge<T, V>> getInEdges(int index);

    Edge<T, V> getFirstInEdge(int index);

    Set<Edge<T, V>> getOutEdges(int index);

    Edge<T, V> getFirstOutEdge(int index);

    int getInDegree(int index);

    int getOutDegree(int index);

    int getDegree(int index);

    E getFirstInAdjacentVertex(int index);

    int getFirstInAdjacentVertexIndex(int index);

    List<E> getInAdjacentVertexes(int index);

    Set<Integer> getInAdjacentVertexIndexes(int index);

    E getFirstOutAdjacentVertex(int index);

    int getFirstOutAdjacentVertexIndex(int index);

    List<E> getOutAdjacentVertexes(int index);

    Set<Integer> getOutAdjacentVertexIndexes(int index);

    E getFirstAdjacentVertex(int index);

    int getFirstAdjacentVertexIndex(int index);

    List<E> getAdjacentVertexes(int index);

    Set<Integer> getAdjacentVertexIndexes(int index);

    List<List<Integer>> DFSTraverse();

    List<List<Integer>> BFSTraverse();

    boolean isDirectedGraph();

    boolean isCompletedGraph();

    boolean isConnectedGraph();

    boolean isEmptyGraph();

    boolean isNetwork();

    Object[] getVertexes();

    Set<Edge<T, V>> getEdges();

    List<Integer> getFirstPath(int index1, int index2);

    List<List<Integer>> getPaths(int index1, int index2);

    boolean hasPath(int index1, int index2);

    List<Integer> getFirstCycle(int index);

    List<List<Integer>> getCycles(int index);

    boolean hasCycle(int index);

    boolean hasCycle();

    List<List<Integer>> getCycles();

    Edge<T, V> getLoop(int index);

    boolean hasLoop(int index);

    boolean hasLoop();

    boolean isConnected(int index1, int index2);

}

GraphByAdjacentMatrix.java

package graph.graphImpl;


import com.sun.istack.internal.NotNull;
import graph.Edge;
import graph.Graph;
import graph.GraphKind;

import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * Thousand lines of code, take you to the world of graph theory
 * Figure - implementation class
 * characteristic:
 * 1,It has self ring edge and no parallel edge;
 * 2,Weighted;
 * 3,Have direction;
 * 4,All algorithms consider the directionality of the directed graph, but do not involve any algorithm related to the weight (the weight needs to be compared, and the adjacency matrix may not be suitable for the implementation of some topology algorithms)
 *
 * The algorithm has broken its heart by manipulating the complexity of time and space. If someone is willing to taste the algorithm I wrote, he may understand my pains, whine~~~
 *
 */
public class GraphByAdjacentMatrix<E, T, V> implements Graph<E, T, V> {
    /**
     * adjacency matrix 
     */
    private final List<List<Edge<T, V>>> adjacencyMatrix;
    /**
     * Type mark of drawing
     */
    private final GraphKind graphKind;
    /**
     * Vertex vector
     */
    private final List<E> vertexes;
    /**
     * Current vertex number and arc number of graph
     */
    private int vertexNum, edgeNum;
    /**
     * Auxiliary traversal space
     */
    private boolean[] visited;
    private Queue<Integer> queue;
    private int traversableNum;
    private List<List<Integer>> cycles;
    private List<List<Integer>> paths;

    public GraphByAdjacentMatrix(@NotNull List<E> vertexes, @NotNull List<Edge<T, V>> edges, @NotNull GraphKind graphKind) {
        this.vertexes = vertexes;
        this.vertexNum = vertexes.size();
        this.adjacencyMatrix = createAdjacentMatrix(edges, vertexes.size(), graphKind);
        this.graphKind = graphKind;
    }

    public GraphByAdjacentMatrix(@NotNull GraphKind graphKind) {
        vertexes = new ArrayList<>();
        this.adjacencyMatrix = createAdjacentMatrix(null, 0, graphKind);
        this.graphKind = graphKind;
    }

    public GraphByAdjacentMatrix(@NotNull List<E> vertexes, @NotNull GraphKind graphKind) {
        this.vertexes = vertexes;
        this.vertexNum = vertexes.size();
        this.adjacencyMatrix = createAdjacentMatrix(null, 0, graphKind);
        this.graphKind = graphKind;
    }

    @Override
    public String toString() {
        return "GraphByAdjacentMatrix{\n" +
                "vertexes=" + vertexes +
                ",\nAdjacentMatrix=" + adjacentMatrixToString(adjacencyMatrix) +
                ",\nVertexNum=" + vertexNum +
                ",\nEdgeNum=" + edgeNum +
                ",\ngraphKind=" + graphKind +
                '}';
    }

    /**
     * Implement the printing method of adjacency matrix to assist the Graph class to implement the toString method
     *
     * @param AdjacentMatrix adjacency matrix 
     * @return toString() of adjacency matrix
     */
    private String adjacentMatrixToString(List<List<Edge<T, V>>> AdjacentMatrix) {
        StringBuilder stringBuilder = new StringBuilder().append("[\n");
        for (List<Edge<T, V>> edges : AdjacentMatrix)
            stringBuilder.append(edges).append("\n");
        return stringBuilder.append("]").toString();
    }

    /**
     * Number of stable self increasing arcs / edges
     *
     * @param tailIndex Starting point of Arc / edge
     * @param headIndex End point of Arc / edge
     */
    private void growEdgeNum(int tailIndex, int headIndex) {
        rangeCheck(tailIndex);
        rangeCheck(headIndex);
        if (adjacencyMatrix.get(tailIndex).get(headIndex) == null)
            ++edgeNum;
    }

    /**
     * The adjacency matrix is generated according to the specified graph type graphKind, vertex number order and arc / edge set
     *
     * @param edges     Arc / edge set
     * @param order     Number of vertices
     * @param graphKind Generated graph type
     * @return adjacency matrix 
     */
    private List<List<Edge<T, V>>> createAdjacentMatrix(List<Edge<T, V>> edges, int order, GraphKind graphKind) {
        /*
          The adjacency matrix is initialized according to the incoming vertex number order
         */
        List<List<Edge<T, V>>> adjacencyMatrix = new ArrayList<>();
        for (int i = 0; i < order; i++) {
            List<Edge<T, V>> vector = new ArrayList<>();
            for (int j = 0; j < order; j++)
                vector.add(null);
            adjacencyMatrix.add(vector);
        }
        /*
         Generate adjacency matrix
         */
        if (edges != null)
            switch (graphKind) {
                case DG:
                    edges.forEach(edge -> {
                        int tailIndex = edge.getTailIndex();
                        int headIndex = edge.getHeadIndex();
                        edge.setWeight(true);
                        growEdgeNum(tailIndex, headIndex);
                        adjacencyMatrix.get(tailIndex).set(headIndex, edge);
                    });
                    break;
                case DN:
                    edges.forEach(edge -> {
                        int tailIndex = edge.getTailIndex();
                        int headIndex = edge.getHeadIndex();
                        growEdgeNum(tailIndex, headIndex);
                        adjacencyMatrix.get(tailIndex).set(headIndex, edge);
                    });
                    break;
                case UDG:
                    edges.forEach(edge -> {
                        int tailIndex = edge.getTailIndex();
                        int headIndex = edge.getHeadIndex();
                        edge.setWeight(true);
                        growEdgeNum(tailIndex, headIndex);
                        adjacencyMatrix.get(tailIndex).set(headIndex, edge);
                        adjacencyMatrix.get(headIndex).set(tailIndex, edge);
                    });
                    break;
                case UDN:
                    edges.forEach(edge -> {
                        int tailIndex = edge.getTailIndex();
                        int headIndex = edge.getHeadIndex();
                        growEdgeNum(tailIndex, headIndex);
                        adjacencyMatrix.get(tailIndex).set(headIndex, edge);
                        adjacencyMatrix.get(headIndex).set(tailIndex, edge);
                    });
                    break;
                default:
                    throw new RuntimeException("Unknown graph type!");
            }
        return adjacencyMatrix;
    }

    /**
     * Judge whether the vertex index value is out of bounds;
     * Throw runtime exception out of bounds
     *
     * @param index Vertex index value
     */
    private void rangeCheck(int index) {
        if (index < 0 || index > vertexNum - 1)
            throw new IndexOutOfBoundsException("Vertex subscript must<" + (vertexNum - 1) + "also>=0!");
    }

    @Override
    public int getVertexNum() {
        return vertexNum;
    }

    @Override
    public int getEdgeNum() {
        return edgeNum;
    }

    @Override
    public GraphKind getKind() {
        return graphKind;
    }

    /**
     * Gets the index value that the first vertex is vertex
     *
     * @param vertex vertex
     * @return Index value
     */
    @Override
    public int findVertex(E vertex) {
        return vertexes.indexOf(vertex);
    }

    /**
     * Gets the index value of vertex whose last vertex is vertex
     *
     * @param vertex vertex
     * @return Index value
     */
    @Override
    public int findLastVertex(E vertex) {
        return vertexes.lastIndexOf(vertex);
    }

    @Override
    public Set<Integer> findVertexIndexes(E vertex) {
        Set<Integer> indexes = new HashSet<>();
        for (int i = 0; i < vertexes.size(); i++)
            if (vertexes.get(i).equals(vertex))
                indexes.add(i);
        return indexes;
    }

    @Override
    public E getVertex(int index) {
        return vertexes.get(index);
    }

    @Override
    public E setVertex(int index, E vertex) {
        return vertexes.set(index, vertex);
    }

    @Override
    public boolean addVertex(E vertex) {
        vertexes.add(vertex);
        List<Edge<T, V>> vector = new ArrayList<>();
        //Cycle old VertexNum times
        adjacencyMatrix.forEach(edges -> {
            edges.add(null);
            //Reuse loops to reduce time complexity
            vector.add(null);
        });
        vector.add(null);
        adjacencyMatrix.add(vector);
        ++vertexNum;
        return true;
    }

    @Override
    public boolean addVertexes(List<E> vertexes) {
        if (vertexes != null) {
            vertexes.forEach(this::addVertex);
            return true;
        }
        return false;
    }

    /**
     * The time complexity of this method is very high and time-consuming, so it is not recommended to use it;
     * In fact, there is no need to specify the position to insert vertices, which may be meaningless to the graph data structure
     *
     * @param index  Specifies the index value of the insertion location
     * @param vertex Vertex to be inserted
     */
    @Override
    public void addVertex(int index, E vertex) {
        vertexes.add(vertex);
        List<Edge<T, V>> vector = new ArrayList<>();
        //Cycle old VertexNum times
        adjacencyMatrix.forEach(edges -> {
            edges.add(index, null);
            //Reuse loops to reduce time complexity
            vector.add(null);
        });
        vector.add(null);
        adjacencyMatrix.add(index, vector);
        ++vertexNum;
    }

    /**
     * Deletes the vertex at the position corresponding to the specified index value
     *
     * @param index Delete the vertex corresponding to the index value
     * @return Deleted vertices
     */
    @Override
    public E deleteVertex(int index) {
        rangeCheck(index);
        E vertex = vertexes.remove(index);
        --vertexNum;
        int ReducedEdgeNum = 0;
        for (int i = 0; i < vertexNum; i++) {
            if (adjacencyMatrix.get(i).remove(index) != null)
                ++ReducedEdgeNum;
            if (adjacencyMatrix.get(index).get(i) != null)
                ++ReducedEdgeNum;
        }
        adjacencyMatrix.remove(index);
        edgeNum -= ReducedEdgeNum;
        return vertex;
    }

    @Override
    public int deleteFirstVertex(E vertex) {
        int index = findVertex(vertex);
        if (index != -1)
            deleteVertex(index);
        return index;
    }

    @Override
    public List<E> deleteVertexes(int[] vertexIndexes) {
        List<E> vertexes = new ArrayList<>();
        if (vertexIndexes == null)
            return vertexes;
        for (int vertexIndex : vertexIndexes)
            vertexes.add(deleteVertex(vertexIndex));
        return vertexes;
    }

    /**
     * Add an edge by index value
     *
     * @return Old edge
     */
    @Override
    public Edge<T, V> addEdgeByIndex(int index1, int index2) {
        return addEdgeByIndex(index1, index2, null, null);
    }

    /**
     * Add an edge by index value
     *
     * @return Old edge
     */
    @Override
    public Edge<T, V> addEdgeByIndex(int index1, int index2, T weight, V info) {
        growEdgeNum(index1, index2);
        Edge<T, V> edge;
        if (isNetwork())
            edge = new Edge<>(index1, index2, weight, info);
        else
            edge = new Edge<>(index1, index2, true, info);
        if (!isDirectedGraph())
            adjacencyMatrix.get(index2).set(index1, edge);
        return adjacencyMatrix.get(index1).set(index2, edge);
    }

    @Override
    public Set<Edge<T, V>> addEdgesByIndexes(Set<int[]> indexes) {
        Set<Edge<T, V>> edges = new HashSet<>();
        if (isNetwork() && isDirectedGraph())
            indexes.forEach(e -> {
                if (e == null || e.length != 2)
                    throw new RuntimeException("Generate arc/The index value of an edge with only two vertices");
                else {
                    growEdgeNum(e[0], e[1]);
                    edges.add(adjacencyMatrix.get(e[0]).set(e[1], new Edge<>(e[0], e[1], null, null)));
                }
            });
        else if (!isNetwork() && isDirectedGraph())
            indexes.forEach(e -> {
                if (e == null || e.length != 2)
                    throw new RuntimeException("Generate arc/The index value of an edge with only two vertices");
                else {
                    growEdgeNum(e[0], e[1]);
                    edges.add(adjacencyMatrix.get(e[0]).set(e[1], new Edge<>(e[0], e[1], true, null)));
                }
            });
        else if (isNetwork() && !isDirectedGraph())
            indexes.forEach(e -> {
                if (e == null || e.length != 2)
                    throw new RuntimeException("Generate arc/The index value of an edge with only two vertices");
                else {
                    growEdgeNum(e[0], e[1]);
                    Edge<T, V> edge = new Edge<>(e[0], e[1], null, null);
                    edges.add(adjacencyMatrix.get(e[0]).set(e[1], edge));
                    adjacencyMatrix.get(e[1]).set(e[0], edge);
                }
            });
        else
            indexes.forEach(e -> {
                if (e == null || e.length != 2)
                    throw new RuntimeException("Generate arc/The index value of an edge with only two vertices");
                else {
                    growEdgeNum(e[0], e[1]);
                    Edge<T, V> edge = new Edge<>(e[0], e[1], true, null);
                    edges.add(adjacencyMatrix.get(e[0]).set(e[1], edge));
                    adjacencyMatrix.get(e[1]).set(e[0], edge);
                }
            });
        return edges;
    }

    @Override
    public Edge<T, V> deleteEdge(int index1, int index2) {
        if (hasEdge(index1, index2)) {
            if (!isDirectedGraph())
                adjacencyMatrix.get(index2).set(index1, null);
            --vertexNum;
            return adjacencyMatrix.get(index1).set(index2, null);
        }
        return null;
    }

    @Override
    public Edge<T, V> updateEdge(int index1, int index2, T weight, V info) {
        Edge<T, V> edge = getEdge(index1, index2);
        if (edge != null) {
            if (isNetwork())
                edge.setWeight(weight);
            edge.setInfo(info);
            return edge;
        } else
            throw new RuntimeException("<" + index1 + "," + index2 + ">" + "Not an arc/Side!");
    }

    @Override
    public Edge<T, V> updateEdgeWeight(int index1, int index2, T weight) {
        Edge<T, V> edge = getEdge(index1, index2);
        if (edge != null) {
            if (isNetwork())
                edge.setWeight(weight);
            return edge;
        } else
            throw new RuntimeException("<" + index1 + "," + index2 + ">" + "Not an arc/Side!");
    }

    @Override
    public Edge<T, V> updateEdgeInfo(int index1, int index2, V info) {
        Edge<T, V> edge = getEdge(index1, index2);
        if (edge != null) {
            edge.setInfo(info);
            return edge;
        } else
            throw new RuntimeException("<" + index1 + "," + index2 + ">" + "Not an arc/Side!");
    }

    @Override
    public boolean hasEdge(int index1, int index2) {
        return getEdge(index1, index2) != null;
    }

    @Override
    public Edge<T, V> getEdge(int index1, int index2) {
        rangeCheck(index1);
        rangeCheck(index2);
        return adjacencyMatrix.get(index1).get(index2);
    }

    @Override
    public Set<Edge<T, V>> getInEdges(int index) {
        rangeCheck(index);
        Set<Edge<T, V>> edges = new HashSet<>();
        adjacencyMatrix.forEach(vector -> {
            Edge<T, V> edge = vector.get(index);
            if (edge != null)
                edges.add(edge);
        });
        return edges;
    }

    @Override
    public Edge<T, V> getFirstInEdge(int index) {
        rangeCheck(index);
        for (List<Edge<T, V>> vector : adjacencyMatrix) {
            Edge<T, V> edge = vector.get(index);
            if (edge != null)
                return edge;
        }
        return null;
    }

    @Override
    public Set<Edge<T, V>> getOutEdges(int index) {
        rangeCheck(index);
        Set<Edge<T, V>> edges = new HashSet<>();
        adjacencyMatrix.get(index).forEach(edge -> {
            if (edge != null)
                edges.add(edge);
        });
        return edges;
    }

    @Override
    public Edge<T, V> getFirstOutEdge(int index) {
        rangeCheck(index);
        for (Edge<T, V> edge : adjacencyMatrix.get(index))
            if (edge != null)
                return edge;
        return null;
    }

    @Override
    public Edge<T, V> getFirstAdjacentEdge(int index) {
        Edge<T, V> firstOutEdge = getFirstOutEdge(index);
        if (firstOutEdge != null)
            return firstOutEdge;
        return getFirstInEdge(index);
    }

    @Override
    public Set<Edge<T, V>> getAdjacentEdges(int index) {
        Set<Edge<T, V>> edges = getInEdges(index);
        /*
          Optimization time complexity
         */
        if (isDirectedGraph())
            adjacencyMatrix.get(index).forEach(edge -> {
                if (edge != null)
                    edges.add(edge);
            });
        return edges;
    }

    @Override
    public int getInDegree(int index) {
        rangeCheck(index);
        return (int) adjacencyMatrix.stream().filter(vector -> vector.get(index) != null).count();
    }

    @Override
    public int getOutDegree(int index) {
        rangeCheck(index);
        return (int) adjacencyMatrix.get(index).stream().filter(Objects::nonNull).count();
    }

    @Override
    public int getDegree(int index) {
        int degree = getOutDegree(index);
        if (isDirectedGraph()) {
            degree += (int) adjacencyMatrix.stream().filter(vector -> vector.get(index) != null).count();
            if (hasEdge(index, index))
                return degree - 1;
            return degree;
        }
        return degree;
    }

    @Override
    public int getFirstInAdjacentVertexIndex(int index) {
        Edge<T, V> firstInEdge = getFirstInEdge(index);
        if (firstInEdge == null)
            return -1;
        return firstInEdge.getTailIndex();
    }

    /**
     * The return value cannot be used to measure the existence of vertices, because vertices can be stored in null
     */
    @Override
    public E getFirstInAdjacentVertex(int index) {
        int firstInAdjacentVertexIndex = getFirstInAdjacentVertexIndex(index);
        if (firstInAdjacentVertexIndex == -1)
            return null;
        return vertexes.get(firstInAdjacentVertexIndex);
    }

    @Override
    public List<E> getInAdjacentVertexes(int index) {
        if (isDirectedGraph()) {
            rangeCheck(index);
            List<E> vertexes = new ArrayList<>();
            for (int i = 0; i < this.vertexes.size(); i++)
                if (index != i && adjacencyMatrix.get(i).get(index) != null)
                    vertexes.add(this.vertexes.get(i));
            return vertexes;
        }
        return getAdjacentVertexes(index);
    }

    @Override
    public Set<Integer> getInAdjacentVertexIndexes(int index) {
        if (isDirectedGraph()) {
            rangeCheck(index);
            Set<Integer> vertexIndexes = new HashSet<>();
            for (int i = 0; i < this.vertexes.size(); i++)
                if (index != i && adjacencyMatrix.get(i).get(index) != null)
                    vertexIndexes.add(i);
            return vertexIndexes;
        }
        return getAdjacentVertexIndexes(index);
    }

    @Override
    public int getFirstOutAdjacentVertexIndex(int index) {
        Edge<T, V> firstOutEdge = getFirstOutEdge(index);
        if (firstOutEdge == null)
            return -1;
        return firstOutEdge.getHeadIndex();
    }

    @Override
    public E getFirstOutAdjacentVertex(int index) {
        int firstOutAdjacentVertexIndex = getFirstOutAdjacentVertexIndex(index);
        if (firstOutAdjacentVertexIndex == -1)
            return null;
        return vertexes.get(firstOutAdjacentVertexIndex);
    }

    @Override
    public List<E> getOutAdjacentVertexes(int index) {
        if (isDirectedGraph()) {
            rangeCheck(index);
            List<E> vertexes = new ArrayList<>();
            for (int i = 0; i < this.vertexes.size(); i++)
                if (index != i && adjacencyMatrix.get(index).get(i) != null)
                    vertexes.add(this.vertexes.get(i));
            return vertexes;
        }
        return getAdjacentVertexes(index);
    }

    @Override
    public Set<Integer> getOutAdjacentVertexIndexes(int index) {
        if (isDirectedGraph()) {
            rangeCheck(index);
            Set<Integer> vertexIndexes = new HashSet<>();
            for (int i = 0; i < this.vertexes.size(); i++)
                if (index != i && adjacencyMatrix.get(index).get(i) != null)
                    vertexIndexes.add(i);
            return vertexIndexes;
        }
        return getAdjacentVertexIndexes(index);
    }

    @Override
    public E getFirstAdjacentVertex(int index) {
        return getFirstOutAdjacentVertex(index);
    }

    @Override
    public int getFirstAdjacentVertexIndex(int index) {
        return getFirstOutAdjacentVertexIndex(index);
    }

    @Override
    public List<E> getAdjacentVertexes(int index) {
        rangeCheck(index);
        List<E> vertexes = new ArrayList<>();
        for (int i = 0; i < this.vertexes.size(); i++) {
            if (index == i)
                continue;
            if (adjacencyMatrix.get(index).get(i) != null)
                vertexes.add(this.vertexes.get(i));
            if (isDirectedGraph() && adjacencyMatrix.get(i).get(index) != null)
                vertexes.add(this.vertexes.get(i));
        }
        return vertexes;
    }

    @Override
    public Set<Integer> getAdjacentVertexIndexes(int index) {
        rangeCheck(index);
        Set<Integer> vertexIndexes = new HashSet<>();
        for (int i = 0; i < this.vertexes.size(); i++) {
            if (index == i)
                continue;
            if (adjacencyMatrix.get(index).get(i) != null)
                vertexIndexes.add(i);
            if (isDirectedGraph() && adjacencyMatrix.get(i).get(index) != null)
                vertexIndexes.add(i);
        }
        return vertexIndexes;
    }

    /**
     * It has directionality in a directed graph. For vertices without edges, the traversal is not in the same connected component
     */
    @Override
    public List<List<Integer>> DFSTraverse() {
        List<List<Integer>> oneTraversal = new ArrayList<>();
        if (vertexNum == 0)
            return oneTraversal;
        visited = new boolean[vertexNum];
        for (int i = 0; i < vertexNum; i++)
            if (!visited[i])
                oneTraversal.add(DFS(new ArrayList<>(), i));

        return oneTraversal;
    }

    private List<Integer> DFS(List<Integer> path, int v) {
        visited[v] = true;
        path.add(v);
        getOutAdjacentVertexIndexes(v).forEach(index -> {
            if (!visited[index])
                DFS(path, index);
        });
        return path;
    }

    /**
     * It has directionality in a directed graph. For vertices without edges, the traversal is not in the same connected component
     */
    @Override
    public List<List<Integer>> BFSTraverse() {
        List<List<Integer>> oneTraversal = new ArrayList<>();
        if (vertexNum == 0)
            return oneTraversal;
        visited = new boolean[vertexNum];
        queue = new LinkedList<>();
        for (int i = 0; i < vertexNum; i++)
            if (!visited[i])
                oneTraversal.add(BFS(new ArrayList<>(), i));

        return oneTraversal;
    }

    private List<Integer> BFS(List<Integer> path, int v) {
        visited[v] = true;
        path.add(v);
        queue.offer(v);
        while (!queue.isEmpty()) {
            getOutAdjacentVertexIndexes(queue.poll()).forEach(index -> {
                if (!visited[index]) {
                    visited[index] = true;
                    path.add(index);
                    queue.offer(index);
                }
            });
        }
        return path;
    }

    @Override
    public boolean isDirectedGraph() {
        return graphKind == GraphKind.DG || graphKind == GraphKind.DN;
    }

    @Override
    public boolean isCompletedGraph() {
        if ((isDirectedGraph() && edgeNum == vertexNum * (vertexNum - 1)) || (edgeNum == vertexNum * (vertexNum - 1) / 2)) {
            for (int i = 0; i < vertexNum; i++)
                if (adjacencyMatrix.get(i).get(i) != null)
                    return false;
            return true;
        }
        return false;
    }

    /**
     * All vertices in an undirected graph are reachable;
     * There are paths between all vertices in a directed graph
     * (The graph has only one connected component)
     *
     * @return true Indicates that the graph is connected
     */
    @Override
    public boolean isConnectedGraph() {
        if (edgeNum == 0) {
            return false;
        }
        visited = new boolean[vertexNum];
        traversableNum = 0;
        return isConnectedGraphByDFS(0) == vertexNum;
    }

    @Override
    public boolean isEmptyGraph() {
        return vertexNum == 0;
    }

    private int isConnectedGraphByDFS(int v) {
        visited[v] = true;
        ++traversableNum;
        getOutAdjacentVertexIndexes(v).forEach(index -> {
            if (!visited[index])
                isConnectedGraphByDFS(index);
        });
        return traversableNum;
    }

    @Override
    public boolean isNetwork() {
        return graphKind == GraphKind.DN || graphKind == GraphKind.UDN;
    }

    @Override
    public Object[] getVertexes() {
        return vertexes.toArray();
    }

    @Override
    public Set<Edge<T, V>> getEdges() {
        Set<Edge<T, V>> edges = new HashSet<>();
        for (int i = 0; i < vertexNum; i++)
            for (int j = i; j < vertexNum; j++) {
                Edge<T, V> edge = adjacencyMatrix.get(i).get(j);
                if (edge != null)
                    edges.add(edge);
            }
        if (isDirectedGraph()) {
            for (int i = 0; i < vertexNum; i++)
                for (int j = 0; j < i; j++) {
                    Edge<T, V> edge = adjacencyMatrix.get(i).get(j);
                    if (edge != null)
                        edges.add(edge);
                }

        }
        return edges;
    }

    /**
     * The single source path problem has directionality in a directed graph,
     *
     * @param index1 Vertex 1 index value
     * @param index2 Vertex 2 index value
     * @return A collection of vertex index values that the path passes through
     */
    @Override
    public List<Integer> getFirstPath(int index1, int index2) {
        rangeCheck(index1);
        rangeCheck(index2);
        LinkedList<Integer> path = new LinkedList<>();
        if (edgeNum == 0)
            return path;
        if (index1 == index2) {
            if (adjacencyMatrix.get(index1).get(index2) != null) {
                path.add(index1);
                path.add(index2);
            }
            return path;
        }
        visited = new boolean[vertexNum];
        getFirstPathByDFS(path, index1, index2);
        return path;
    }

    private boolean getFirstPathByDFS(LinkedList<Integer> path, int v, int index2) {
        visited[v] = true;
        path.offerLast(v);
        if (v == index2)
            return true;
        for (Integer index : getOutAdjacentVertexIndexes(v)) {
            if (!visited[index]) {
                if (getFirstPathByDFS(path, index, index2))
                    return true;
            }
        }
        path.pollLast();
        return false;
    }

    @Override
    public List<List<Integer>> getPaths(int index1, int index2) {
        rangeCheck(index1);
        rangeCheck(index2);
        paths = new ArrayList<>();
        if (edgeNum == 0)
            return paths;
        if (index1 == index2)
            if (adjacencyMatrix.get(index1).get(index2) != null)
                paths.add(Stream.of(index1, index2).collect(Collectors.toList()));
        visited = new boolean[vertexNum];
        getPathsByDFS(new LinkedList<>(), index2, index1, index1);
        return paths;
    }


    @SuppressWarnings("unchecked")
    private void getPathsByDFS(LinkedList<Integer> path, int index2, int v, int pre) {
        visited[v] = true;
        path.offerLast(v);
        if (v == index2 && pre != index2)
            paths.add((List<Integer>) path.clone());
        for (Integer index : getOutAdjacentVertexIndexes(v)) {
            if (index == pre)
                continue;
            if (!visited[index])
                getPathsByDFS(path, index2, index, v);
        }
        path.pollLast();
        visited[v] = false;
    }


    private boolean hasPathByDFS(int v, int index2) {
        visited[v] = true;
        if (v == index2)
            return true;
        for (Integer index : getOutAdjacentVertexIndexes(v))
            if (!visited[index]) {
                if (hasPathByDFS(index, index2))
                    return true;
            }

        return false;
    }

    /**
     * This method has directionality in directed graph
     *
     * @param index1 Index value 1
     * @param index2 Index value 2
     * @return true Indicates that there is a path between two points
     */
    @Override
    public boolean hasPath(int index1, int index2) {
        rangeCheck(index1);
        rangeCheck(index2);
        if (index1 == index2)
            if (adjacencyMatrix.get(index1).get(index2) != null)
                return true;
        visited = new boolean[vertexNum];
        return hasPathByDFS(index1, index2);
    }

    /**
     * This is the last method I wrote. I don't want to optimize it. I vomited. That's it...
     */
    @Override
    public List<Integer> getFirstCycle(int index) {
        List<List<Integer>> cycles = getCycles(index);
        if (cycles.size() > 0) {
            return cycles.get(0);
        }
        return new ArrayList<>();
    }

    /**
     * It has directivity in a directed graph
     *
     * @param index Vertex index value
     * @return true Indicates that there is a loop / ring at this vertex
     */
    @Override
    public boolean hasCycle(int index) {
        if (edgeNum == 0)
            return false;
        if (hasLoop(index))
            return true;
        visited = new boolean[vertexNum];
        return hasCycleByDFS(index, index, index);
    }

    private boolean hasCycleByDFS(int v, int pre) {
        visited[v] = true;
        for (Integer index : getOutAdjacentVertexIndexes(v)) {
            if (index == pre)
                continue;
            if (visited[index] || adjacencyMatrix.get(index).get(index) != null)
                return true;
            if (hasCycleByDFS(index, v))
                return true;
        }
        return false;
    }

    private boolean hasCycleByDFS(int root, int v, int pre) {
        visited[v] = true;
        for (Integer index : getOutAdjacentVertexIndexes(v)) {
            if (index == pre)
                continue;
            if (visited[index] && index == root)
                return true;
            if (hasCycleByDFS(root, index, v))
                return true;
        }
        return false;
    }

    /**
     * It has directivity in a directed graph
     *
     * @return true Indicates that there are rings in the graph
     */
    @Override
    public boolean hasCycle() {
        if (edgeNum == 0)
            return false;
        visited = new boolean[vertexNum];
        return hasCycleByDFS(0, 0);
    }

    @Override
    public List<List<Integer>> getCycles(int index) {
        rangeCheck(index);
        cycles = new ArrayList<>();
        if (edgeNum == 0)
            return cycles;
        visited = new boolean[vertexNum];
        getCyclesByDFS(new LinkedList<>(), index, index, index);
        Edge<T, V> edge = adjacencyMatrix.get(index).get(index);
        if (edge != null)
            cycles.add(Stream.of(index).collect(Collectors.toList()));
        return cycles;
    }

    @Override
    public List<List<Integer>> getCycles() {
        cycles = new ArrayList<>();
        if (edgeNum == 0)
            return cycles;
        visited = new boolean[vertexNum];
        for (int i = 0; i < vertexNum; i++) {
            getCyclesByDFS(new LinkedList<>(), i, i, i);
            Edge<T, V> edge = adjacencyMatrix.get(i).get(i);
            if (edge != null)
                cycles.add(Stream.of(i).collect(Collectors.toList()));
        }
        return cycles;
    }


    @SuppressWarnings("unchecked")
    private void getCyclesByDFS(LinkedList<Integer> cycle, int root, int v, int pre) {
        visited[v] = true;
        cycle.offerLast(v);
        for (Integer index : getOutAdjacentVertexIndexes(v)) {
            if (index == pre)
                continue;
            if (visited[index]) {
                if (index == root)
                    cycles.add((List<Integer>) cycle.clone());
                continue;
            }
            getCyclesByDFS(cycle, root, index, v);
        }
        cycle.pollLast();
        visited[v] = false;
    }

    @Override
    public Edge<T, V> getLoop(int index) {
        rangeCheck(index);
        return adjacencyMatrix.get(index).get(index);
    }

    @Override
    public boolean hasLoop(int index) {
        return getLoop(index) != null;
    }

    @Override
    public boolean hasLoop() {
        for (int i = 0; i < vertexNum; i++)
            if (adjacencyMatrix.get(i).get(i) != null)
                return true;
        return false;
    }

    private void isConnectedByDFS(int[] visited, int v, int flag) {
        visited[v] = flag;
        getOutAdjacentVertexIndexes(v).forEach(index -> {
            if (visited[index] == 0)
                isConnectedByDFS(visited, index, flag);
        });
    }


    /**
     * Judge whether the vertices corresponding to the index value are connected
     * index1==index2: Check whether the index value has a self ring edge
     * No directivity in acyclic graphs
     * Directivity in a ring graph
     *
     * @param index1 Index value 1
     * @param index2 Index value 2
     * @return true Indicates that two points are connected
     */
    @Override
    public boolean isConnected(int index1, int index2) {
        rangeCheck(index1);
        rangeCheck(index2);
        if (index1 == index2)
            return adjacencyMatrix.get(index1).get(index1) != null;
        int[] visited = new int[vertexNum];
        for (int i = index1, flag = 1; i < vertexNum; i++)
            if (visited[i] == 0) {
                isConnectedByDFS(visited, i, flag);
                flag++;
            }
        return visited[index1] == visited[index2];
    }
}

Test demo:

package graph.graphImpl;

import graph.GraphKind;

import java.util.stream.Collectors;


public class test {
    public static void main(String[] args) {
        GraphByAdjacentMatrix<String, String, String> graph = new GraphByAdjacentMatrix<>(GraphKind.DG);
        for (int i = 1; i < 10; i++) {
            graph.addVertex("V" + i);
        }
        graph.addEdgeByIndex(0, 1);
        graph.addEdgeByIndex(0, 2);
        graph.addEdgeByIndex(1, 3);
        graph.addEdgeByIndex(1, 4);
        graph.addEdgeByIndex(1, 8);
        graph.addEdgeByIndex(2, 7);
        graph.addEdgeByIndex(5, 2);
        graph.addEdgeByIndex(5, 7);
        graph.addEdgeByIndex(6, 3);
        graph.addEdgeByIndex(6, 8);
        System.out.println(graph);
        graph.BFSTraverse().forEach(path -> {
            System.out.println(path.stream().map(graph::getVertex).collect(Collectors.toList()));
        });

    }
}

Test results:

Topics: Java Algorithm data structure Graph Theory