1 数据类型
要处理各种图的算法,我们首先看一份定义了图的基本操作的API,如下表1-1所示:
public class | Graph | |
---|---|---|
Graph(int V) | 创建一个含有V个顶点但不含有边的图 | |
Graph(In in) | 从标准输入流in读入一幅图 | |
int | V() | 顶点数 |
int | E() | 边数 |
void | addEdge(int v, int w) | 向图中添加一条边v-w |
Interable<Integer> | adj(int v) | 和v相邻的顶点集合 |
String | toString() | 对象的字符串表示 |
2 图的表示方法
下一个图处理问题就是用那种方法(数据结构)来表示图并实现上述API,包含以下两个要求:
- 它必须为为可能在应用中碰到的各种类型的图预留足够的空间;
- Graph实例方法实现要快。
比较以下三种表示方法:
- 邻接矩阵:使用一个V
X
X
XV的布尔矩阵(二维数组)。当顶点v和顶点w有边相连,设置v行w列和w行v列为true(1),否则false。
- 问题:空间数量级为 V 2 V^2 V2,当顶点有百万个时,显然不满足需求。
- 边的数组:定义一个Edge类,它含有2个int型变量表示它所依附的两个顶点。
- 问题:实现简单,但是要实现adj()需要检查图中所有的边。
- 邻接表数组:使用一个以顶点为索引的列表(链表)数组,其中数组每个元素表示和该顶点相邻的顶点列表。
我们选择邻接表数组实现上述API,无向图及其邻接表数组示意图如下:
3 无向图的实现
源代码如下3-1所示:算法第四版源码
public class Graph {
private static final String NEWLINE = System.getProperty("line.separator");
private final int V;
private int E;
private Bag<Integer>[] adj;
/**
* 初始化含有V个顶点边为0的图
*
* @param V 顶点数
* @throws IllegalArgumentException if {@code V < 0}
*/
public Graph(int V) {
if (V < 0) throw new IllegalArgumentException("Number of vertices must be non-negative");
this.V = V;
this.E = 0;
adj = (Bag<Integer>[]) new Bag[V];
for (int v = 0; v < V; v++) {
adj[v] = new Bag<Integer>();
}
}
/**
* 从标准输入流in读入一个图
*
* @param in the input stream
* @throws IllegalArgumentException if {@code in} is {@code null}
* @throws IllegalArgumentException if the endpoints of any edge are not in prescribed range
* @throws IllegalArgumentException if the number of vertices or edges is negative
* @throws IllegalArgumentException if the input stream is in the wrong format
*/
public Graph(In in) {
if (in == null) throw new IllegalArgumentException("argument is null");
try {
this.V = in.readInt();
if (V < 0) throw new IllegalArgumentException("number of vertices in a Graph must be non-negative");
adj = (Bag<Integer>[]) new Bag[V];
for (int v = 0; v < V; v++) {
adj[v] = new Bag<Integer>();
}
int E = in.readInt();
if (E < 0) throw new IllegalArgumentException("number of edges in a Graph must be non-negative");
for (int i = 0; i < E; i++) {
int v = in.readInt();
int w = in.readInt();
validateVertex(v);
validateVertex(w);
addEdge(v, w);
}
}
catch (NoSuchElementException e) {
throw new IllegalArgumentException("invalid input format in Graph constructor", e);
}
}
/**
* Initializes a new graph that is a deep copy of {@code G}.
*
* @param G the graph to copy
* @throws IllegalArgumentException if {@code G} is {@code null}
*/
public Graph(Graph G) {
this.V = G.V();
this.E = G.E();
if (V < 0) throw new IllegalArgumentException("Number of vertices must be non-negative");
// update adjacency lists
adj = (Bag<Integer>[]) new Bag[V];
for (int v = 0; v < V; v++) {
adj[v] = new Bag<Integer>();
}
for (int v = 0; v < G.V(); v++) {
// reverse so that adjacency list is in same order as original
Stack<Integer> reverse = new Stack<Integer>();
for (int w : G.adj[v]) {
reverse.push(w);
}
for (int w : reverse) {
adj[v].add(w);
}
}
}
/**
* 顶点数
*
* @return the number of vertices in this graph
*/
public int V() {
return V;
}
/**
* 边数
*
* @return the number of edges in this graph
*/
public int E() {
return E;
}
// throw an IllegalArgumentException unless {@code 0 <= v < V}
private void validateVertex(int v) {
if (v < 0 || v >= V)
throw new IllegalArgumentException("vertex " + v + " is not between 0 and " + (V-1));
}
/**
* 向无向图添加一条边v-w
*
* @param v one vertex in the edge
* @param w the other vertex in the edge
* @throws IllegalArgumentException unless both {@code 0 <= v < V} and {@code 0 <= w < V}
*/
public void addEdge(int v, int w) {
validateVertex(v);
validateVertex(w);
E++;
adj[v].add(w);
adj[w].add(v);
}
/**
* 和顶点v相邻的所有顶点
*
* @param v the vertex
* @return the vertices adjacent to vertex {@code v}, as an iterable
* @throws IllegalArgumentException unless {@code 0 <= v < V}
*/
public Iterable<Integer> adj(int v) {
validateVertex(v);
return adj[v];
}
/**
* 顶点v的度数
*
* @param v the vertex
* @return the degree of vertex {@code v}
* @throws IllegalArgumentException unless {@code 0 <= v < V}
*/
public int degree(int v) {
validateVertex(v);
return adj[v].size();
}
/**
* 图的字符串表示
*
* @return the number of vertices <em>V</em>, followed by the number of edges <em>E</em>,
* followed by the <em>V</em> adjacency lists
*/
public String toString() {
StringBuilder s = new StringBuilder();
s.append(V + " vertices, " + E + " edges " + NEWLINE);
for (int v = 0; v < V; v++) {
s.append(v + ": ");
for (int w : adj[v]) {
s.append(w + " ");
}
s.append(NEWLINE);
}
return s.toString();
}
}
非稠密图的标准表示就是邻接表。我们用一个以顶点为索引的链表数组表示,链表使用Bag包数据类型实现。每个元素存储该顶点相邻的顶点列表。实现特点如下:
- 使用空间和V+E成正比
- 添加一条边所需时间为常数
- 遍历顶点v的所有相邻顶点和所需的时间和v的度数成正比
4 图处理算法的设计模式
因为我们会讨论大量关于图处理的算法,所以设计的首要目标是将图的表示和实现分离开来。我们会为每个任务(算法)创建一个相应的类,典型的用例程序会构造一幅图,作为构造函数的参数传递给实现了某个算法的类。
搜索与某个顶点的连通顶点算法Search API示例如下:
public class | search | |
---|---|---|
Search(Graph g, int s) | 找到和启点s联通的所有顶点 | |
boolean | marked(int v) | v和s是连通的吗 |
int | cout() | 与s连通顶点总数 |
后记
如果小伙伴什么问题或者指教,欢迎交流。
❓QQ:806797785
⭐️源代码仓库地址:https://gitee.com/gaogzhen/algorithm
参考链接:
[1][美]Robert Sedgewich,[美]Kevin Wayne著;谢路云译.算法:第4版[M].北京:人民邮电出版社,2012.10