一、图的实现方式概述
1、图的简单实现方法——邻接矩阵
表示图的一种简单的方法是使用一个一维数组和一个二维数组,称为领接矩阵(adjacent matrix)表示法。
对于每条边(u,v),置A[u,v]等于true;否则,数组的元素就是false。如果边有一个权,那么可以置A[u][v]等于该权,而使用一个很大或者很小的权来标记不存在的边。虽然这样表示非常简单,但是,它的空间需求则为θ(|V|2),如果图的边不是很多,那么这种表示的代价就太大了。若图是稠密(dense)的:|E|=θ(|V|2),则领接矩阵是合适的表示方法。但大多数情况下并非如此。无向图用邻接矩阵表示会浪费一半的空间,稀疏的有向图用邻接矩阵表示会浪费大部分空间,稠密的有向图适合用邻接矩阵表示。
2、图的优化实现方法——邻接表
如果图是稀疏的(sparse),那么更好的解决方法是使用邻接表(adjacency list)表示。邻接表是一个二维容器,第一维是一个数组,存储所有顶点,第二维是链表,存储所有与这个点领接的点集。此时的空间需求为O(|E|+|V|),它相对于图的大小而言是线性的。
邻接表是表示图的标准方法。无向图可以以类似的方法表示,但每条边将会出现在两个表中,造成空间的双倍冗余。
实现邻接表的方法有很多,基本的选择有两个:一、使用一个映射,在这个映射下,关键字是顶点,值是那些邻接表。二、关键字是顶点,值是一个包含链的类Vertex。
图的邻接矩阵实现比较简单,这里我们只展示图的邻接表实现方式。
二、图的邻接表实现
图的邻接表实现总共有3个类,它们分别是:
- 图的顶点的类:Vertex.java
- 图的边类:Edge.java
- 图类:Graph.java
此外,还有一个测试类Test,以方便验证图的构建是否成功,下面是完整的实现代码。
顶点类Vertex:
package dataStructure.graph.graph_ve;
/**
* 图的节点类
*/
public class Vertex {
/**
* 节点存储的内容
*/
String verName;
/**
* 顶点的边链
*/
Edge edgeLink;
}
边类Edge
package dataStructure.graph.graph_ve;
/**
* 图的边类
*/
public class Edge {
/**
* 边的尾部节点名称
*/
String tailName;
/**
* 边的权值
*/
int weight;
/**
* 头节点的其他边
*/
Edge broEdge;
}
图类Graph:
package dataStructure.graph.graph_ve;
import java.util.Scanner;
/**
* 图类,在构造方法中完成图的构造
*/
public class Graph {
/**
* 图的节点个数
*/
int verNum;
/**
* 图的边的条数
*/
int edgeNum;
/**
* 图的邻接表中存储节点的数组
*/
Vertex[] verArray;
/**
* Graph类的构造方法,依次读取节点、边等信息,完成图的构建。
*/
public Graph() {
Scanner scan = new Scanner(System.in);
System.out.println("请输入节点个数和边的个数:");
verNum = scan.nextInt();
edgeNum = scan.nextInt();
verArray = new Vertex[verNum];
System.out.println("请依次输入节点的名称:");
for (int i=0;i<verNum;i++){
Vertex vertex = new Vertex();
vertex.verName = scan.next();
vertex.edgeLink = null;
verArray[i] = vertex;
}
System.out.println("请按‘头节点 权值 尾节点 回车’的形式依次输入边的信息");
for (int i=0;i<edgeNum;i++){
String preName = scan.next();
int weight = scan.nextInt();
String folName = scan.next();
Vertex preV = getVertex(preName);
Vertex folV = getVertex(folName);
if (preV == null || folV == null){
System.out.println("输入错误,输入了不存在的顶点!请重新输入");
i--;
continue;
}
Edge edge = new Edge();
edge.tailName = folName;
edge.weight = weight;
//将边加入到节点的链表中去
edge.broEdge = preV.edgeLink;
preV.edgeLink = edge;
// 如果加上下面这段,则创建的是无向图,不加则是有向图
// edge.tailName = preName;
// edge.broEdge = folV.edgeLink;
// folV.edgeLink = edge;
}
}
/**
* 根据节点名称获取该节点
* @param verName 节点的名称
* @return 节点或null
*/
public Vertex getVertex(String verName){
for (int i=0;i<verNum;i++){
if (verArray[i].verName.equals(verName))
return verArray[i];
}
return null;
}
}
测试类Test:
package dataStructure.graph.graph_ve;
public class Test {
public static void main(String[] args) {
Graph graph = new Graph();
System.out.println("该图的邻接表为:");
outputGraph(graph);
}
/**
* 输出图的邻接表的方法。
* @param graph 要输出的图
*/
public static void outputGraph(Graph graph){
for (int i=0;i<graph.verNum;i++){
Vertex vertex = graph.verArray[i];
System.out.print(vertex.verName);
Edge current = vertex.edgeLink;
while (current != null){
System.out.print("--"+current.weight+"-->"+current.tailName);
current = current.broEdge;
}
System.out.println();
}
}
}
测试结果:
/* 测试结果:
请输入节点个数和边的个数:
4 5
请依次输入节点的名称:
V0 V1 V2 V3
请按‘头节点 权值 尾节点 回车’的形式依次输入边的信息
V0 5 V3
V1 3 V0
V2 9 V0
V1 6 V2
V2 7 V1
该图的邻接表为:
V0--5-->V3
V1--6-->V2--3-->V0
V2--7-->V1--9-->V0
V3
*/
上述实例已经同步到Github,遇到问题的同学可以克隆下来直接运行,链接是:https://github.com/Dodozhou/Algorithm/tree/master/src/main/java/dataStructure/graph/graph_ve