JGraphTの概要
1. 概要
ほとんどの場合、グラフベースのアルゴリズムを実装するときは、いくつかのユーティリティ関数も実装する必要があります。
JGraphTはオープンソースのJavaクラスライブラリであり、さまざまな種類のグラフだけでなく、最も頻繁に発生するグラフの問題を解決するための多くの便利なアルゴリズムも提供します。
この記事では、さまざまなタイプのグラフを作成する方法と、提供されているユーティリティを使用することの便利さについて説明します。
2. メーベン依存
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
また、有向/無向グラフを作成できます。
この例では、有向グラフを作成し、それを使用して他のユーティリティ関数とアルゴリズムを示します。
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. 完全グラフ
同様に、完全なグラフを生成することもできます。
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. マルチグラフ
単純なグラフの他に、APIはマルチグラフ(2つの頂点間に複数のパスを持つグラフ)も提供します。
さらに、任意のグラフに重み付き/重みなしまたはユーザー定義のエッジを含めることができます。
重み付きエッジを持つマルチグラフを作成しましょう。
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. 最短経路を見つける
org.jgrapht.alg.shortestpathパッケージで、ダイクストラ、ベルマンフォード、アスター、フロイドウォーシャルなどのさまざまなアルゴリズムの実装を提供します。
ダイクストラのアルゴリズムを使用して最短経路を見つけましょう。
@Test
public void whenGetDijkstraShortestPath_thenGetNotNullPath() {
DijkstraShortestPath dijkstraShortestPath
= new DijkstraShortestPath(directedGraph);
List shortestPath = dijkstraShortestPath
.getPath("v1","v4").getVertexList();
assertNotNull(shortestPath);
}
同様に、Bellman-Fordアルゴリズムを使用して最短パスを取得するには:
@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}は強く接続されたサブグラフと見なすことができます。
上の画像に示されている有向グラフの4つのサブグラフをリストできます:{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のすべての頂点とエッジを含む回路です。 それを持つグラフはオイラーグラフです。
グラフを見てみましょう。
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);
});
}
今、私たちは、グラフの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. ハミルトン閉路
各頂点に1回だけアクセスする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、最初にMaven Centralからのjgrapht-ext拡張依存関係を追加しましょう。
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ファイルに書き込みます。
最終的な画像は、ブラウザまたはお気に入りのレンダラーで確認できます。
詳細については、official documentationをご覧ください。
6. 結論
JGraphTは、ほぼすべてのタイプのグラフとさまざまなグラフアルゴリズムを提供します。 いくつかの一般的なAPIの使用方法について説明しました。 ただし、official pageでいつでもライブラリを探索できます。
これらすべての例とコードスニペットの実装は、over on Githubにあります。