«1. Обзор

В этом кратком руководстве мы узнаем, как мы можем обнаружить цикл в заданном ориентированном графе.

2. Представление графа

В этом уроке мы будем придерживаться представления графа списка смежности.

Во-первых, давайте начнем с определения вершины в Java:

public class Vertex {

    private String label;
    private boolean beingVisited;
    private boolean visited;
    private List<Vertex> adjacencyList;

    public Vertex(String label) {
        this.label = label;
        this.adjacencyList = new ArrayList<>();
    }

    public void addNeighbor(Vertex adjacent) {
        this.adjacencyList.add(adjacent);
    }
    //getters and setters
}

Здесь adjacencyList вершины v содержит список всех вершин, смежных с v. Метод addNeighbor() добавляет соседнюю вершину к соседству list of v.

Мы также определили два логических параметра, посещенных и посещенных, которые показывают, посещается ли узел в настоящее время или уже был посещен.

Граф можно рассматривать как группу вершин или узлов, соединенных ребрами.

Итак, давайте теперь быстро представим граф в Java:

public class Graph {

    private List<Vertex> vertices;

    public Graph() {
        this.vertices = new ArrayList<>();
    }

    public void addVertex(Vertex vertex) {
        this.vertices.add(vertex);
    }

    public void addEdge(Vertex from, Vertex to) {
        from.addNeighbor(to);
    }

   // ...
}

Мы будем использовать методы addVertex() и addEdge() для добавления новых вершин и ребер в наш граф.

3. Обнаружение циклов

Чтобы обнаружить цикл в ориентированном графе, мы будем использовать вариант обхода в глубину:

    Выберите непосещенную вершину v и отметьте ее состояние как посещенное Для каждой соседней вершины u из v, проверьте: если u уже находится в состоянии BeingVisited, это явно означает, что существует обратная граница и, следовательно, обнаружен цикл. флаг вершины v’s bevisited установлен на false, а флаг ее посещения — на true

Обратите внимание, что все вершины нашего графа изначально находятся в состоянии unvisited, так как их флаги beingVisited и посещенные инициализированы значением false.

Давайте теперь посмотрим на наше Java-решение:

public boolean hasCycle(Vertex sourceVertex) {
    sourceVertex.setBeingVisited(true);

    for (Vertex neighbor : sourceVertex.getAdjacencyList()) {
        if (neighbor.isBeingVisited()) {
            // backward edge exists
            return true;
        } else if (!neighbor.isVisited() && hasCycle(neighbor)) {
            return true;
        }
    }

    sourceVertex.setBeingVisited(false);
    sourceVertex.setVisited(true);
    return false;
}

Мы можем использовать любую вершину графа в качестве исходной или начальной вершины.

Для несвязанного графа нам придется добавить дополнительный метод-оболочку:

public boolean hasCycle() {
    for (Vertex vertex : vertices) {
        if (!vertex.isVisited() && hasCycle(vertex)) {
            return true;
        }
    }
    return false;
}

Это гарантирует, что мы посещаем каждый компонент несвязанного графа для обнаружения цикла.

4. Тестирование реализации

Давайте рассмотрим приведенный ниже циклический ориентированный граф:

Мы можем быстро написать JUnit для проверки нашего метода hasCycle() для этого графа:

@Test
public void givenGraph_whenCycleExists_thenReturnTrue() {

    Vertex vertexA = new Vertex("A");
    Vertex vertexB = new Vertex("B");
    Vertex vertexC = new Vertex("C")
    Vertex vertexD = new Vertex("D");

    Graph graph = new Graph();
    graph.addVertex(vertexA);
    graph.addVertex(vertexB);
    graph.addVertex(vertexC);
    graph.addVertex(vertexD);

    graph.addEdge(vertexA, vertexB);
    graph.addEdge(vertexB, vertexC);
    graph.addEdge(vertexC, vertexA);
    graph.addEdge(vertexD, vertexC);

    assertTrue(graph.hasCycle());

}

Здесь наш hasCycle() метод вернул true, что означает, что наш график является циклическим.

5. Заключение

В этом уроке мы узнали, как проверить, существует ли цикл в заданном ориентированном графе в Java.

Как обычно, реализация кода с примерами доступна на Github.