Javaのグラフ

Javaのグラフ

1. 概要

このチュートリアルでは、we’ll understand the basic concepts of a graph as a data structure

また、Javaでの実装と、グラフで可能なさまざまな操作についても説明します。 また、グラフ実装を提供するJavaライブラリについても説明します。

2. グラフのデータ構造

グラフは、ソーシャルメディアプラットフォーム上の人々のネットワークのようなdata structure for storing connected dataです。

グラフは、頂点とエッジで構成されます。 A vertex represents the entity(たとえば、人)とan edge represents the relationship between entities(たとえば、人の友情)。

これをよりよく理解するために、簡単なグラフを定義しましょう。

image image

ここでは、5つの頂点と6つのエッジを持つ単純なグラフを定義しました。 円は人を表す頂点であり、2つの頂点を結ぶ線はオンラインポータル上の友人を表すエッジです。

エッジのプロパティに応じて、この単純なグラフにはいくつかのバリエーションがあります。 次のセクションで簡単に説明しましょう。

ただし、このチュートリアルのJavaの例については、ここに示す単純なグラフのみに焦点を当てます。

2.1. 有向グラフ

これまでに定義したグラフには、方向のないエッジがあります。 これらのedges feature a direction in themの場合、結果のグラフは有向グラフと呼ばれます。

この例として、オンラインポータルのフレンドシップでフレンドリクエストを送信した人を表すことができます。

image image

ここでは、エッジの方向が固定されていることがわかります。 エッジも双方向にすることができます。

2.2. 加重グラフ

繰り返しますが、単純なグラフには、偏りのない、または重みのないエッジがあります。 代わりにこれらのedges carry relative weightの場合、このグラフは加重グラフと呼ばれます。

これの実用的なアプリケーションの例は、オンラインポータルの友情がどれほど古いかを表すことです。

image image

ここでは、エッジに重みが関連付けられていることがわかります。 これは、これらのエッジに相対的な意味を提供します。

3. Graph Representations

グラフは、隣接行列や隣接リストなどのさまざまな形式で表すことができます。 それぞれに長所と短所があります。

このセクションでは、これらのグラフ表現を紹介します。

3.1. 隣接行列

隣接行列はグラフのa square matrix with dimensions equivalent to the number of verticesです。

マトリックスの要素は通常、値「0」または「1」を持ちます。 「1」の値は、行と列の頂点間の隣接を示し、それ以外の場合は「0」の値を示します。

前のセクションの単純なグラフの隣接行列がどのように見えるかを見てみましょう。

image image

この表現もかなりeasier to implement and efficient to queryです。 ただし、それはless efficient with respect to space occupiedです。

3.2. 隣接リスト

隣接リストはan array of listsに他なりません。 配列のサイズは、グラフの頂点の数に相当します。

配列の特定のインデックスにあるリストは、その配列インデックスによって表される頂点の隣接する頂点を表します。

前のセクションの単純なグラフの隣接リストがどのように見えるかを見てみましょう。

image image

この表現はcomparatively difficult to create and less efficient to queryです。 ただし、better space efficiencyを提供します。

このチュートリアルでは、隣接リストを使用してグラフを表します。

4. Javaのグラフ

Javaには、グラフデータ構造のデフォルトの実装がありません。

ただし、Javaコレクションを使用してグラフを実装できます。

頂点を定義することから始めましょう:

class Vertex {
    String label;
    Vertex(String label) {
        this.label = label;
    }

    // equals and hashCode
}

上記の頂点の定義はラベルのみを特徴としていますが、これはPersonCity.などの可能なエンティティを表すことができます

また、we have to override the equals() and hashCode() methods as these are necessary to work with Java Collections.に注意してください

前に説明したように、グラフは、隣接行列または隣接リストのいずれかとして表現できる頂点とエッジのコレクションにすぎません。

ここで隣接リストを使用してこれを定義する方法を見てみましょう。

class Graph {
    private Map> adjVertices;

    // standard constructor, getters, setters
}

ここに表示されているように、クラスGraphはJavaコレクションのMapを使用して隣接リストを定義しています。

creating, updating or searching through the graphなど、グラフデータ構造で可能な操作はいくつかあります。

より一般的な操作のいくつかを実行し、それらをJavaで実装する方法を確認します。

5. グラフの突然変異操作

まず、グラフのデータ構造を変更するためのいくつかのメソッドを定義します。

頂点を追加および削除するメソッドを定義しましょう。

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));
}

これらのメソッドは、vertices Setから要素を追加および削除するだけです。

次に、エッジを追加するメソッドも定義しましょう。

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);
}

このメソッドは、新しいEdgeを作成し、隣接する頂点Map.を更新します。

同様の方法で、removeEdge()メソッドを定義します。

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);
}

次に、これまでに定義した方法を使用して、以前に描画した単純なグラフを作成する方法を見てみましょう。

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;
}

最後に、特定の頂点の隣接する頂点を取得する方法を定義します。

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

6. グラフのトラバース

グラフのデータ構造と、それを作成および更新する関数が定義されたので、グラフを走査するためのいくつかの追加関数を定義できます。 グラフ内を検索するなど、意味のあるアクションを実行するには、グラフを走査する必要があります。

two possible ways to traverse a graph, depth-first traversal and breadth-first traversalがあります。

6.1. 深さ優先走査

深さ優先走査は、任意のルート頂点とexplores vertices as deeper as possible along each branch before exploring vertices at the same levelから始まります。

深さ優先探索を実行する方法を定義しましょう。

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;
}

ここでは、we’re using a Stack to store the vertices that need to be traversedです。

前のサブセクションで作成したグラフでこれを実行してみましょう。

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

ここでは、トラバーサルのルートとして頂点「Bob」を使用していることに注意してください。ただし、これは他のどの頂点でもかまいません。

6.2. 幅優先探索

比較すると、幅優先走査は、グラフ内の任意のルート頂点とexplores all neighboring vertices at the same level before going deeperから始まります。

次に、幅優先走査を実行するメソッドを定義しましょう。

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;
}

幅優先走査makes use of Queue to store the vertices which need to be traversedに注意してください。

同じグラフでこのトラバーサルをもう一度実行してみましょう。

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

繰り返しますが、ここで「ボブ」であるルート頂点は、他の頂点でもあります。

7. グラフ用のJavaライブラリ

Javaで常にグラフを最初から実装する必要はありません。 グラフ実装を提供するいくつかのオープンソースおよび成熟したライブラリが利用可能です。

次のいくつかのサブセクションでは、これらのライブラリのいくつかについて説明します。

7.1. JGraphT

JGraphTは、グラフデータ構造用のJavaで最も人気のあるライブラリの1つです。 これにより、単純なグラフ、有向グラフ、加重グラフなどを作成できます。

さらに、グラフのデータ構造で可能な多くのアルゴリズムを提供します。 以前のチュートリアルの1つは、JGraphT in much more detailをカバーしています。

7.2. Google Guava

Google Guavaは、グラフデータ構造とそのアルゴリズムを含むさまざまな関数を提供するJavaライブラリのセットです。

単純なGraphValueGraph、およびNetworkの作成をサポートします。 これらは、MutableまたはImmutableとして定義できます。

7.3. Apache Commons

Apache Commonsは、再利用可能なJavaコンポーネントを提供するApacheプロジェクトです。 これには、グラフデータ構造を作成および管理するためのツールキットを提供するCommons Graphが含まれます。 これは、データ構造を操作する一般的なグラフアルゴリズムも提供します。

7.4. Sourceforge JUNG

Java Universal Network/Graph (JUNG)は、グラフとして表現できるデータのモデリング、分析、および視覚化のための拡張可能な言語を提供するJavaフレームワークです。

JUNGは、クラスタリング、分解、最適化などのルーチンを含む多くのアルゴリズムをサポートしています。

 

これらのライブラリは、グラフのデータ構造に基づいて多くの実装を提供します。 また、Facebookでユーザーが作成したグラフを分析するために現在使用されているApache Giraphなどのmore powerful frameworks based on graphsや、グラフデータベース上で一般的に使用されているApache TinkerPopもあります。

8. 結論

この記事では、グラフをその表現とともにデータ構造として説明しました。 Javaコレクションを使用してJavaで非常に単純なグラフを定義し、グラフの共通トラバーサルも定義しました。

また、グラフ実装を提供するJavaプラットフォーム以外のJavaで使用可能なさまざまなライブラリについても簡単に説明しました。

いつものように、例のコードはover on GitHubで利用できます。