Введение в JGraphT

Введение в JGraphT

1. обзор

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

JGraphT - это библиотека классов Java с открытым исходным кодом, которая не только предоставляет нам различные типы графиков, но и множество полезных алгоритмов для решения наиболее часто встречающихся проблем с графами.

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

2. Maven Dependency

Начнем с добавления зависимости в наш проект Maven:


    org.jgrapht
    jgrapht-core
    1.0.1

Последнюю версию можно найти наMaven Central.

3. Создание графиков

JGraphT поддерживает различные типы графиков.

3.1. Простые графики

Для начала создадим простой граф с вершиной типаString:

Graph g
  = new SimpleGraph<>(DefaultEdge.class);
g.addVertex(“v1”);
g.addVertex(“v2”);
g.addEdge(v1, v2);

3.2. Directed/Undirected Graphs

Это также позволяет нам создавать ориентированные / ненаправленные графы.

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

image

DirectedGraph directedGraph
  = new DefaultDirectedGraph<>(DefaultEdge.class);
directedGraph.addVertex("v1");
directedGraph.addVertex("v2");
directedGraph.addVertex("v3");
directedGraph.addEdge("v1", "v2");
// Add remaining vertices and edges

3.3. Полные графики

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

image

public void createCompleteGraph() {
    completeGraph = new SimpleWeightedGraph<>(DefaultEdge.class);
    CompleteGraphGenerator completeGenerator
      = new CompleteGraphGenerator<>(size);
    VertexFactory vFactory = new VertexFactory() {
        private int id = 0;
        public String createVertex() {
            return "v" + id++;
        }
    };
    completeGenerator.generateGraph(completeGraph, vFactory, null);
}

3.4. Мульти-графы

image

Помимо простых графиков, API также предоставляет нам мультиграфы (графики с несколькими путями между двумя вершинами).

Кроме того, мы можем иметь взвешенные / невзвешенные или определяемые пользователем ребра на любом графике.

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

public void createMultiGraphWithWeightedEdges() {
    multiGraph = new Multigraph<>(DefaultWeightedEdge.class);
    multiGraph.addVertex("v1");
    multiGraph.addVertex("v2");
    DefaultWeightedEdge edge1 = multiGraph.addEdge("v1", "v2");
    multiGraph.setEdgeWeight(edge1, 5);

    DefaultWeightedEdge edge2 = multiGraph.addEdge("v1", "v2");
    multiGraph.setEdgeWeight(edge2, 3);
}

В дополнение к этому мы можем иметь не изменяемые (только для чтения) и прослушиваемые (позволяет внешним слушателям отслеживать изменения) графики, а также подграфы. Также мы всегда можем создать все композиции из этих графиков.

Более подробную информацию об API можно найти вhere.

4. Алгоритмы API

Теперь, когда у нас есть полноценные графические объекты, давайте рассмотрим некоторые общие проблемы и их решения.

4.1. Итерация графа

Мы можем перемещаться по графу, используя различные итераторы, такие какBreadthFirstIterator,DepthFirstIterator,ClosestFirstIterator,RandomWalkIterator в соответствии с требованиями. Нам просто нужно создать экземпляр соответствующих итераторов, передав объекты графа:

DepthFirstIterator depthFirstIterator
  = new DepthFirstIterator<>(directedGraph);
BreadthFirstIterator breadthFirstIterator
  = new BreadthFirstIterator<>(directedGraph);

Как только мы получим объекты итератора, мы можем выполнить итерацию, используя методыhasNext() иnext().

4.2. Поиск кратчайшего пути

Он предоставляет реализации различных алгоритмов, таких как Dijkstra, Bellman-Ford, Astar и FloydWarshall в пакетеorg.jgrapht.alg.shortestpath.

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

@Test
public void whenGetDijkstraShortestPath_thenGetNotNullPath() {
    DijkstraShortestPath dijkstraShortestPath
      = new DijkstraShortestPath(directedGraph);
    List shortestPath = dijkstraShortestPath
      .getPath("v1","v4").getVertexList();

    assertNotNull(shortestPath);
}

Аналогично, чтобы получить кратчайший путь, используя алгоритм Беллмана-Форда:

@Test
public void
  whenGetBellmanFordShortestPath_thenGetNotNullPath() {
    BellmanFordShortestPath bellmanFordShortestPath
      = new BellmanFordShortestPath(directedGraph);
    List shortestPath = bellmanFordShortestPath
      .getPath("v1", "v4")
      .getVertexList();

    assertNotNull(shortestPath);
}

4.3. Поиск сильносвязных подграфов

Прежде чем мы перейдем к реализации, давайте кратко рассмотрим, что означают сильно связанные подграфы. A subgraph is said to be strongly connected only if there is a path between each pair of its vertices.с

В нашем примере \ {v1, v2, v3, v4} можно считать сильно связным подграфом, если мы можем перейти к любой вершине, независимо от того, какой является текущая вершина.

Мы можем перечислить четыре таких подграфа для ориентированного графа, показанного на изображении выше: {v9}, {v8}, \ {v5, v6, v7}, \ {v1, v2, v3, v4}

Реализация перечислить все сильно связанные подграфы:

@Test
public void
  whenGetStronglyConnectedSubgraphs_thenPathExists() {

    StrongConnectivityAlgorithm scAlg
      = new KosarajuStrongConnectivityInspector<>(directedGraph);
    List> stronglyConnectedSubgraphs
      = scAlg.stronglyConnectedSubgraphs();
    List stronglyConnectedVertices
      = new ArrayList<>(stronglyConnectedSubgraphs.get(3)
      .vertexSet());

    String randomVertex1 = stronglyConnectedVertices.get(0);
    String randomVertex2 = stronglyConnectedVertices.get(3);
    AllDirectedPaths allDirectedPaths
      = new AllDirectedPaths<>(directedGraph);

    List> possiblePathList
      = allDirectedPaths.getAllPaths(
        randomVertex1, randomVertex2, false,
          stronglyConnectedVertices.size());

    assertTrue(possiblePathList.size() > 0);
}

4.4. Схема Эйлера

Эйлерова схема в графеG - это схема, которая включает в себя все вершины и ребраG. Граф, который имеет это, является эйлеровым графом.

Посмотрим на график:

image

public void createGraphWithEulerianCircuit() {
    SimpleWeightedGraph simpleGraph
      = new SimpleWeightedGraph<>(DefaultEdge.class);
    IntStream.range(1,5)
      .forEach(i-> simpleGraph.addVertex("v" + i));
    IntStream.range(1,5)
      .forEach(i-> {
        int endVertexNo = (i + 1) > 5 ? 1 : i + 1;
        simpleGraph.addEdge("v" + i,"v" + endVertexNo);
    });
}

Теперь мы можем проверить, содержит ли граф Eulerian Circuit, используя API:

@Test
public void givenGraph_whenCheckEluerianCycle_thenGetResult() {
    HierholzerEulerianCycle eulerianCycle
      = new HierholzerEulerianCycle<>();

    assertTrue(eulerianCycle.isEulerian(simpleGraph));
}
@Test
public void whenGetEulerianCycle_thenGetGraphPath() {
    HierholzerEulerianCycle eulerianCycle
      = new HierholzerEulerianCycle<>();
    GraphPath path = eulerianCycle.getEulerianCycle(simpleGraph);

    assertTrue(path.getEdgeList()
      .containsAll(simpleGraph.edgeSet()));
}

4.5. Гамильтонова схема

GraphPath, который посещает каждую вершину ровно один раз, известен как гамильтонов путь.

Гамильтонов цикл (или гамильтонова схема) - это гамильтонов путь, в котором есть ребро (в графе) от последней вершины до первой вершины пути.

Мы можем найти оптимальный гамильтонов цикл для полного графа с помощью методаHamiltonianCycle.getApproximateOptimalForCompleteGraph().

Этот метод возвращает приблизительный минимальный тур коммивояжера (цикл Гамильтона). Оптимальное решение является NP-полным, поэтому это достойное приближение, которое выполняется за полиномиальное время:

public void
  whenGetHamiltonianCyclePath_thenGetVerticeSequence() {
    List verticeList = HamiltonianCycle
      .getApproximateOptimalForCompleteGraph(completeGraph);

    assertEquals(verticeList.size(), completeGraph.vertexSet().size());
}

4.6. Детектор цикла

Мы также можем проверить, есть ли циклы в графе. В настоящее времяCycleDetector поддерживает только ориентированные графы:

@Test
public void whenCheckCycles_thenDetectCycles() {
    CycleDetector cycleDetector
      = new CycleDetector(directedGraph);

    assertTrue(cycleDetector.detectCycles());
    Set cycleVertices = cycleDetector.findCycles();

    assertTrue(cycleVertices.size() > 0);
}

5. Визуализация графика

JGraphT allows us to generate visualizations of graphs and save them as images, сначала давайте добавим зависимость расширенияjgrapht-ext из Maven Central:


    org.jgrapht
    jgrapht-ext
    1.0.1

Затем давайте создадим простой ориентированный граф с 3 вершинами и 3 ребрами:

@Before
public void createGraph() {

    File imgFile = new File("src/test/resources/graph.png");
    imgFile.createNewFile();

    DefaultDirectedGraph g =
      new DefaultDirectedGraph(DefaultEdge.class);

    String x1 = "x1";
    String x2 = "x2";
    String x3 = "x3";

    g.addVertex(x1);
    g.addVertex(x2);
    g.addVertex(x3);

    g.addEdge(x1, x2);
    g.addEdge(x2, x3);
    g.addEdge(x3, x1);
}

Теперь мы можем визуализировать этот график:

@Test
public void givenAdaptedGraph_whenWriteBufferedImage_thenFileShouldExist() throws IOException {

    JGraphXAdapter graphAdapter =
      new JGraphXAdapter(g);
    mxIGraphLayout layout = new mxCircleLayout(graphAdapter);
    layout.execute(graphAdapter.getDefaultParent());

    BufferedImage image =
      mxCellRenderer.createBufferedImage(graphAdapter, null, 2, Color.WHITE, true, null);
    File imgFile = new File("src/test/resources/graph.png");
    ImageIO.write(image, "PNG", imgFile);

    assertTrue(imgFile.exists());
}

Здесь мы создалиJGraphXAdapter, который получает наш график в качестве аргумента конструктора, и мы применили к немуmxCircleLayout . Это раскладывает визуализацию по кругу.

Кроме того, мы используемmxCellRenderer для созданияBufferedImage, а затем записываем визуализацию в файл png.

Мы можем увидеть финальное изображение в браузере или в нашем любимом рендерере:

image

Мы можем найти более подробную информацию вofficial documentation.

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

JGraphT предоставляет практически все типы графов и различные графические алгоритмы. Мы рассмотрели, как использовать несколько популярных API. Однако вы всегда можете изучить библиотеку наofficial page.

Реализацию всех этих примеров и фрагментов кода можно найти вover on Github.