1、前言
一直很好奇为什么JDK没有实现图这种数据结构, 而每次当我们需要去使用的图的时候都要重新写一遍领接矩阵、领接表之类 然后再去写算法实现, 实在异常麻烦。
于是便简单对图这种数据结构进行一些封装实现(仅供学习)。具体代码见 github
2、图是如何存储
图的存储主要有两种实现,分别是领接矩阵
和 领接表
。
2.1、领接矩阵
实际就是用一个二维数组
去存储图中节点与节点的关系。 行下标和列下标分别代表一个节点序号,数组的值表示一条边。 比如有下图二维数组 int[][] graph
, 则 graph[3][5]
表示 节点3
和节点5
之间存在一条边。 通过这样的方式我们就可以很方便的存储图中的所有边和节点。
2.2、领接表
实际就是用一个链表数组
或者哈希表
的方式去存储。 比如用链表数组 LinkedList<Integer>[] graph
存储, 则 graph[0]
表示与节点0相邻的所有节点. 比如用哈希表, Map<Integer, LinkedList<Integer>> graph
, 则的此时的 graph.get(0)
表示与节点0相邻的所有节点.
3、图库体系
主要实现了4种图的结构, 分别是
领接矩阵-无权图
: AdjacencyMatrixGraph领接矩阵-有权图
: AdjacencyMatrixWeightGraph领接表-无权图
: AdjacencyListsGraph领接表-有权图
: AdjacencyListsWeightGraph
类图关系如下
4、功能
并且在这四种数据结构之上实现了以下基础图论算法
:
- 图的深度优先遍历
- 图的广度优先遍历
- 计算图连通分量个数
- 最小生成树算法
- Prim算法
- Kruskal算法
- 路径算法
- 有权图单源最短路径算法Dijkstra
- 无权图单源最短路径算法
- 寻找从 开始节点 到 结束节点的一条路径
- 寻找从 开始节点 到 结束节点的所有路径
5、快速开始
5.1、创建图
/** 有以下图
* 0
* / \
* 2 3
* / / \
* v | 5 -> 1
* 6 v ^
* 4 /
*
* 8--> 9
* 12
*/
// 创建有向无权-领结表图
static Graph<String> noWeightDirectListedGraph = new AdjacencyListsGraph<>(true);
// 创建无向无权-邻接矩阵图
static Graph<String> noWeightMatrixGraph = new AdjacencyMatrixGraph<>(50,false);
static {
noWeightDirectListedGraph.addEdge("x0","x2");
noWeightDirectListedGraph.addEdge("x2","x6");
noWeightDirectListedGraph.addEdge("x0","x3");
noWeightDirectListedGraph.addEdge("x3","x4");
noWeightDirectListedGraph.addEdge("x3","x5");
noWeightDirectListedGraph.addEdge("x4","x5");
noWeightDirectListedGraph.addEdge("x5","x1");
noWeightDirectListedGraph.addEdge("x8","x9");
noWeightDirectListedGraph.addEdge("x12","x12");
noWeightMatrixGraph.addEdge("x0","x2");
noWeightMatrixGraph.addEdge("x2","x6");
noWeightMatrixGraph.addEdge("x0","x3");
noWeightMatrixGraph.addEdge("x3","x4");
noWeightMatrixGraph.addEdge("x3","x5");
noWeightMatrixGraph.addEdge("x4","x5");
noWeightMatrixGraph.addEdge("x8","x9");
noWeightMatrixGraph.addEdge("x12","x12");
}
// 创建无向有权-领接矩阵图
static WeightedGraph<String, Integer> weightListedGraph = new AdjacencyMatrixWeightGraph<>(20,false);
/*
0 - 2
\
3 - 4 - 6 a2-a0(10)
\ / \ a3-a5(10)
1- 5 7 a3-a4(20)
a0-a3(30)
a1-a5(40)
a6-a4(98)
a7-a4(100)
*/
static {
weightListedGraph.addEdge("a0","a2",10);
weightListedGraph.addEdge("a0","a3",10);
weightListedGraph.addEdge("a3","a4",20);
weightListedGraph.addEdge("a3","a5",10);
weightListedGraph.addEdge("a5","a1",40);
weightListedGraph.addEdge("a4","a5",50);
weightListedGraph.addEdge("a4","a5",60);
weightListedGraph.addEdge("a4","a5",36);
weightListedGraph.addEdge("a6","a4",98);
weightListedGraph.addEdge("a4","a7",100);
}
复制代码
5.2、图深度、广度遍历
@Test
public void testTraverse(){
// 深度遍历 dfs
noWeightDirectListedGraph.dfsTraverse(e -> System.out.print(e + "->"));
System.out.println("\n-------");
// 广度遍历 bfs
noWeightMatrixGraph.bfsTraverse(e -> System.out.print(e + "->"));
}
复制代码
5.3、计算联通分量
int count = noWeightDirectListedGraph.componentCount();
int count1 = noWeightMatrixGraph.componentCount();
System.out.println(count);
System.out.println(count1);
复制代码
5.4、最小生成树算法
@Test
public void testGetMinimumSpanningTree(){
MstTree<String, Integer> minimumSpanningTree = weightListedGraph.getMinimumSpanningTree();
// Prim算法
MstTree<String, Integer> minimumSpanningTreePrim = weightListedGraph.getMinimumSpanningTreePrim();
// Kruskal算法
MstTree<String, Integer> minimumSpanningTreeKruskal = weightListedGraph.getMinimumSpanningTreeKruskal();
List<Edge<String,Integer>> edgeList = minimumSpanningTree.getEdgeList();
for (Edge<String,Integer> e : edgeList){
System.out.println(e.toString());
}
}
复制代码
5.5、无权图求单源最短路径算法
// 找到所有从开始节点 到 其他节点的最短路径
String startNoe = "x0";
Map<String, List<String>> tmp = noWeightDirectListedGraph.findShortPathFromStartNode(startNoe);
for (Map.Entry<String, List<String>> entry : tmp.entrySet()) {
String path = entry.getValue().stream().collect(Collectors.joining("->"));
System.out.println("从" + startNoe + "到节点" + entry.getKey() + "的最短路径为: " + path);
}
复制代码
5.6、路径算法
System.out.println("=================2、找到所有从开始节点 到 结束节点的一条路径 ====");
// 寻找从 x0 到x1 的一条路径。
List<String> onePath = noWeightDirectListedGraph.findOnePath("x0", "x1");
System.out.println("寻找从x0到x1 的一条路径为: " + onePath.stream().collect(Collectors.joining("->")));
System.out.println("=================3、找到所有从开始节点 到 结束节点的所有路径 ====");
List<List<String>> allPath = noWeightDirectListedGraph.findAllPath("x0", "x1");
for (List<String> list : allPath) {
System.out.println("从x0 到x1的路径为: " + list.stream().collect(Collectors.joining("->")));
}
复制代码
5.7、有权图单源最短路径算法Dijkstra
@Test
public void testShortPathTree(){
ShortPathTree<String, Integer> tmp = weightListedGraph.getShortPathTree("a0", Integer.class);
for (Map.Entry<String, List<Edge<String, Integer>>> entry : tmp.getFormSourceToEndPathEdgeMap().entrySet()) {
String path = entry.getValue().stream().map(Edge::toString).collect(Collectors.joining("->"));
System.out.println("从a0到节点" + entry.getKey() + "的最短路径为: " + path);
}
System.out.println();
}
复制代码