1. 概述
大多数时候,当我们实现基于图的算法时,我们还需要实现一些实用函数。
JGraphT是一个开源Java类库,它不仅为我们提供了各种类型的图,还提供了许多有用的算法来解决最常见的图问题。
在本文中,我们将了解如何创建不同类型的图以及使用所提供的实用程序。
2. Maven依赖
首先向我们的Maven项目添加依赖:
<dependency>
<groupId>org.jgrapht</groupId>
<artifactId>jgrapht-core</artifactId>
<version>1.0.1</version>
</dependency>
最新版本可以在Maven Central找到。
3. 创建图
JGraphT支持各种类型的图。
3.1 简单图
首先,让我们创建一个具有String类型顶点的简单图:
Graph<String, DefaultEdge> g
= new SimpleGraph<>(DefaultEdge.class);
g.addVertex("v1");
g.addVertex("v2");
g.addEdge(v1, v2);
3.2 有向图/无向图
它还允许我们创建有向/无向图。
在我们的示例中,我们将创建一个有向图并使用它来演示其他实用函数和算法:
DirectedGraph<String, DefaultEdge> 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<String, DefaultEdge> completeGenerator
= new CompleteGraphGenerator<>(size);
VertexFactory<String> vFactory = new VertexFactory<String>() {
private int id = 0;
public String createVertex() {
return "v" + id++;
}
};
completeGenerator.generateGraph(completeGraph, vFactory, null);
}
3.4 多图
除了简单图之外,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详细信息请参见此处。
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包中提供了各种算法的实现,例如Dijkstra、Bellman-Ford、Astar和FloydWarshall。
让我们使用Dijkstra算法寻找最短路径:
@Test
void whenGetDijkstraShortestPath_thenGetNotNullPath() {
DijkstraShortestPath dijkstraShortestPath = new DijkstraShortestPath(directedGraph);
List<String> shortestPath = dijkstraShortestPath
.getPath("v1","v4").getVertexList();
assertNotNull(shortestPath);
}
类似地,使用Bellman-Ford算法获取最短路径:
@Test
void whenGetBellmanFordShortestPath_thenGetNotNullPath() {
BellmanFordShortestPath bellmanFordShortestPath = new BellmanFordShortestPath(directedGraph);
List<String> shortestPath = bellmanFordShortestPath
.getPath("v1", "v4")
.getVertexList();
assertNotNull(shortestPath);
}
4.3 寻找强连通子图
在开始实现之前,我们先简单了解一下强连通子图的含义;只有当子图的每对顶点之间都有一条路径时,才称其为强连通的。
在我们的例子中,如果我们可以遍历到任何顶点而不管当前顶点是什么,则{v1,v2,v3,v4}可以被视为强连通子图。
我们可以为上图列出四个这样的子图:
{v9},{v8},{v5,v6,v7},{v1,v2,v3,v4}
列出所有强连通子图的实现:
@Test
void whenGetStronglyConnectedSubgraphs_thenPathExists() {
StrongConnectivityAlgorithm<String, DefaultEdge> scAlg
= new KosarajuStrongConnectivityInspector<>(directedGraph);
List<DirectedSubgraph<String, DefaultEdge>> stronglyConnectedSubgraphs
= scAlg.stronglyConnectedSubgraphs();
List<String> stronglyConnectedVertices
= new ArrayList<>(stronglyConnectedSubgraphs.get(3)
.vertexSet());
String randomVertex1 = stronglyConnectedVertices.get(0);
String randomVertex2 = stronglyConnectedVertices.get(3);
AllDirectedPaths<String, DefaultEdge> allDirectedPaths
= new AllDirectedPaths<>(directedGraph);
List<GraphPath<String, DefaultEdge>> possiblePathList
= allDirectedPaths.getAllPaths(
randomVertex1, randomVertex2, false,
stronglyConnectedVertices.size());
assertTrue(possiblePathList.size() > 0);
}
4.4 欧拉回路
图G中的欧拉回路是包含G的所有顶点和边的回路,包含该回路的图称为欧拉图。
我们来看看图表:
public void createGraphWithEulerianCircuit() {
SimpleWeightedGraph<String, DefaultEdge> 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
void givenGraph_whenCheckEluerianCycle_thenGetResult() {
HierholzerEulerianCycle eulerianCycle
= new HierholzerEulerianCycle<>();
assertTrue(eulerianCycle.isEulerian(simpleGraph));
}
@Test
void whenGetEulerianCycle_thenGetGraphPath() {
HierholzerEulerianCycle eulerianCycle
= new HierholzerEulerianCycle<>();
GraphPath path = eulerianCycle.getEulerianCycle(simpleGraph);
assertTrue(path.getEdgeList()
.containsAll(simpleGraph.edgeSet()));
}
4.5 哈密顿电路
恰好访问每个顶点一次的GraphPath称为哈密顿路径。
哈密顿环(或哈密顿电路)是一条哈密顿路径,使得(在图中)从路径的最后一个顶点到第一个顶点有一条边。
我们可以使用HamiltonianCycle.getApproximateOptimalForCompleteGraph()方法找到完全图的最佳哈密顿环。
此方法将返回近似的最小旅行商行程(哈密顿循环),最优解是NP完全的,因此这是一个在多项式时间内运行的不错的近似值:
void whenGetHamiltonianCyclePath_thenGetVerticeSequence() {
List<String> verticeList = HamiltonianCycle
.getApproximateOptimalForCompleteGraph(completeGraph);
assertEquals(verticeList.size(), completeGraph.vertexSet().size());
}
4.6 循环检测器
我们还可以检查图中是否存在任何循环,目前,CycleDetector仅支持有向图:
@Test
void whenCheckCycles_thenDetectCycles() {
CycleDetector<String, DefaultEdge> cycleDetector = new CycleDetector<String, DefaultEdge>(directedGraph);
assertTrue(cycleDetector.detectCycles());
Set<String> cycleVertices = cycleDetector.findCycles();
assertTrue(cycleVertices.size() > 0);
}
5. 图形可视化
JGraphT允许我们生成图的可视化并将其保存为图像,首先让我们从Maven Central添加jgrapht-ext扩展依赖:
<dependency>
<groupId>org.jgrapht</groupId>
<artifactId>jgrapht-ext</artifactId>
<version>1.0.1</version>
</dependency>
接下来,让我们创建一个具有3个顶点和3条边的简单有向图:
@BeforeEach
public void createGraph() {
File imgFile = new File("src/test/resources/graph.png");
imgFile.createNewFile();
DefaultDirectedGraph<String, DefaultEdge> g = new DefaultDirectedGraph<String, DefaultEdge>(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
void givenAdaptedGraph_whenWriteBufferedImage_thenFileShouldExist() throws IOException {
JGraphXAdapter<String, DefaultEdge> graphAdapter = new JGraphXAdapter<String, DefaultEdge>(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文件。
我们可以在浏览器或我们最喜欢的渲染器中看到最终的图像:
可以在官方文档中找到更多详细信息。
6. 总结
JGraphT提供了几乎所有类型的图和各种图算法,我们介绍了一些常用API的使用方法,也可以在官方页面上探索该库。
Post Directory
