Графіки на Java

1. Огляд

У цьому посібнику ми розберемо основні поняття графіка як структури даних .

Ми також дослідимо його реалізацію на Java, а також різні операції, можливі на графіку. Ми також обговоримо бібліотеки Java, що пропонують реалізації графіків.

2. Структура даних графіків

Графік - це структура даних для зберігання підключених даних, як мережа людей, на платформі соціальних мереж.

Графік складається з вершин і ребер. Вершина представляє сутність (наприклад, людей), а ребро - відносини між сутностями (наприклад, дружбу людини).

Давайте визначимо простий графік, щоб краще це зрозуміти:

Тут ми визначили простий графік із п’ятьма вершинами та шістьма ребрами. Кола - це вершини, що представляють людей, а лінії, що з'єднують дві вершини, - ребра, що представляють друзів на Інтернет-порталі.

Існує кілька варіацій цього простого графіка залежно від властивостей ребер. Давайте коротко розглянемо їх у наступних розділах.

Однак ми зупинимось лише на простому графіку, представленому тут для прикладів Java у цьому підручнику.

2.1. Направлений графік

Графік, який ми визначили до цього часу, має ребра без будь-якого напрямку. Якщо ці ребра мають у них напрямок , отриманий графік називається спрямованим графіком.

Прикладом цього може бути представлення того, хто надсилає запит на дружбу у дружбу на Інтернет-порталі:

Тут ми бачимо, що ребра мають фіксований напрямок. Краї також можуть бути двонаправленими.

2.2. Зважений графік

Знову ж таки, наш простий графік має ребра, які є неупередженими або незваженими. Якщо замість цього ребра мають відносну вагу , цей графік відомий як зважений графік.

Прикладом практичного застосування цього може бути подання того, наскільки відносно стара дружба на Інтернет-порталі:

Тут ми бачимо, що краї мають ваги, пов’язані з ними. Це надає відносне значення цим краям.

3. Графічні подання

Графік може бути представлений у різних формах, таких як матриця суміжності та список суміжностей. Кожен з них має свої плюси і мінуси в різній структурі.

Ми представимо ці графічні представлення в цьому розділі.

3.1. Матриця суміжності

Матриця суміжності - це квадратна матриця з розмірами, еквівалентними кількості вершин на графіку.

Елементи матриці зазвичай мають значення '0' або '1'. Значення "1" означає суміжність між вершинами рядка і стовпця, а значення "0" - в іншому випадку.

Давайте подивимося, як виглядає матриця суміжності для нашого простого графіку з попереднього розділу:

Це представлення досить просте у впровадженні та ефективне для запитів . Однак це менш ефективно щодо зайнятого простору .

3.2. Список суміжності

Список суміжності - це не що інше, як масив списків . Розмір масиву еквівалентний кількості вершин на графіку.

Список з певним індексом масиву представляє сусідні вершини вершини, представлені цим індексом масиву.

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

Це подання порівняно важко створити і менш ефективне для запитів . Однак він пропонує кращу космічну ефективність .

Ми будемо використовувати список суміжностей, щоб представити графік у цьому підручнику.

4. Графіки на Java

У Java немає стандартної реалізації структури даних графіків.

Однак ми можемо реалізувати графік, використовуючи Java Collections.

Почнемо з визначення вершини:

class Vertex { String label; Vertex(String label) { this.label = label; } // equals and hashCode }

У наведеному вище визначенні вершини просто мітка, але вона може представляти будь-яку можливу сутність, наприклад, особу чи місто.

Також зауважте, що ми повинні замінити методи equals () та hashCode (), оскільки вони необхідні для роботи з колекціями Java.

Як ми вже обговорювали раніше, графік - це не що інше, як набір вершин і ребер, які можна представити як матрицю суміжності, так і як список суміжностей.

Давайте подивимося, як ми можемо визначити це за допомогою списку суміжності тут:

class Graph { private Map
    
      adjVertices; // standard constructor, getters, setters }
    

Як ми бачимо тут, клас Graph використовує Map from Java Collections для визначення списку суміжності.

У структурі даних графіка можливо кілька операцій, таких як створення, оновлення або пошук у графіку .

Ми розглянемо деякі найпоширеніші операції та побачимо, як ми можемо реалізувати їх на Java.

5. Graph Mutation Operations

To start with, we'll define some methods to mutate the graph data structure.

Let's define methods to add and remove vertices:

void addVertex(String label) { adjVertices.putIfAbsent(new Vertex(label), new ArrayList()); } void removeVertex(String label) { Vertex v = new Vertex(label); adjVertices.values().stream().forEach(e -> e.remove(v)); adjVertices.remove(new Vertex(label)); }

These methods simply add and remove elements from the vertices Set.

Now, let's also define a method to add an edge:

void addEdge(String label1, String label2) { Vertex v1 = new Vertex(label1); Vertex v2 = new Vertex(label2); adjVertices.get(v1).add(v2); adjVertices.get(v2).add(v1); }

This method creates a new Edge and updates the adjacent vertices Map.

In a similar way, we'll define the removeEdge() method:

void removeEdge(String label1, String label2) { Vertex v1 = new Vertex(label1); Vertex v2 = new Vertex(label2); List eV1 = adjVertices.get(v1); List eV2 = adjVertices.get(v2); if (eV1 != null) eV1.remove(v2); if (eV2 != null) eV2.remove(v1); }

Next, let's see how we can create the simple graph we have drawn earlier using the methods we've defined so far:

Graph createGraph() { Graph graph = new Graph(); graph.addVertex("Bob"); graph.addVertex("Alice"); graph.addVertex("Mark"); graph.addVertex("Rob"); graph.addVertex("Maria"); graph.addEdge("Bob", "Alice"); graph.addEdge("Bob", "Rob"); graph.addEdge("Alice", "Mark"); graph.addEdge("Rob", "Mark"); graph.addEdge("Alice", "Maria"); graph.addEdge("Rob", "Maria"); return graph; }

We'll finally define a method to get the adjacent vertices of a particular vertex:

List getAdjVertices(String label) { return adjVertices.get(new Vertex(label)); }

6. Traversing a Graph

Now that we have graph data structure and functions to create and update it defined, we can define some additional functions for traversing the graph. We need to traverse a graph to perform any meaningful action, like search within the graph.

There are two possible ways to traverse a graph, depth-first traversal and breadth-first traversal.

6.1. Depth-First Traversal

A depth-first traversal starts at an arbitrary root vertex and explores vertices as deeper as possible along each branch before exploring vertices at the same level.

Let's define a method to perform the depth-first traversal:

Set depthFirstTraversal(Graph graph, String root) { Set visited = new LinkedHashSet(); Stack stack = new Stack(); stack.push(root); while (!stack.isEmpty()) { String vertex = stack.pop(); if (!visited.contains(vertex)) { visited.add(vertex); for (Vertex v : graph.getAdjVertices(vertex)) { stack.push(v.label); } } } return visited; }

Here, we're using a Stack to store the vertices that need to be traversed.

Let's run this on the graph we created in the previous subsection:

assertEquals("[Bob, Rob, Maria, Alice, Mark]", depthFirstTraversal(graph, "Bob").toString());

Please note that we're using vertex “Bob” as our root for traversal here, but this can be any other vertex.

6.2. Breadth-First Traversal

Comparatively, a breadth-first traversal starts at an arbitrary root vertex and explores all neighboring vertices at the same level before going deeper in the graph.

Now let's define a method to perform the breadth-first traversal:

Set breadthFirstTraversal(Graph graph, String root) { Set visited = new LinkedHashSet(); Queue queue = new LinkedList(); queue.add(root); visited.add(root); while (!queue.isEmpty()) { String vertex = queue.poll(); for (Vertex v : graph.getAdjVertices(vertex)) { if (!visited.contains(v.label)) { visited.add(v.label); queue.add(v.label); } } } return visited; }

Note that a breadth-first traversal makes use of Queue to store the vertices which need to be traversed.

Let's again run this traversal on the same graph:

assertEquals( "[Bob, Alice, Rob, Mark, Maria]", breadthFirstTraversal(graph, "Bob").toString());

Again, the root vertex which is “Bob” here can as well be any other vertex.

7. Java Libraries for Graphs

It's not necessary to always implement the graph from scratch in Java. There are several open source and mature libraries available which offers graph implementations.

In the next few subsections, we'll go through some of these libraries.

7.1. JGraphT

JGraphT is one of the most popular libraries in Java for the graph data structure. It allows the creation of a simple graph, directed graph, weighted graph, amongst others.

Additionally, it offers many possible algorithms on the graph data structure. One of our previous tutorials covers JGraphT in much more detail.

7.2. Google Guava

Google Guava is a set of Java libraries that offer a range of functions including graph data structure and its algorithms.

It supports creating simple Graph, ValueGraph, and Network. These can be defined as Mutable or Immutable.

7.3. Apache Commons

Apache Commons is an Apache project that offers reusable Java components. This includes Commons Graph which offers a toolkit to create and manage graph data structure. This also provides common graph algorithms to operate on the data structure.

7.4. Sourceforge JUNG

Java Universal Network/Graph (JUNG) is a Java framework that provides extensible language for modeling, analysis, and visualization of any data that can be represented as a graph.

JUNG supports a number of algorithms which includes routines like clustering, decomposition, and optimization.

Ці бібліотеки забезпечують ряд реалізацій на основі структури даних графіків. Існують також більш потужні фреймворки, засновані на графіках , такі як Apache Giraph, який в даний час використовується у Facebook для аналізу графіка, сформованого їхніми користувачами, та Apache TinkerPop, який зазвичай використовується поверх баз даних графіків.

8. Висновок

У цій статті ми обговорили графік як структуру даних разом із його поданнями. Ми визначили дуже простий графік у Java, використовуючи Java Collections, а також визначили загальні обходи для графіка.

Ми також коротко поговорили про різні бібліотеки, доступні на Java за межами платформи Java, яка забезпечує реалізації графіків.

Як завжди, код для прикладів доступний на GitHub.