0. 导言
为了让代码更加清晰,有以下一些约定:
- Graph的表示使用邻接表,更详细的说明参见 图的表示
- 本文代码中使用的图为 有向图,图中的顶点用 int 型来表示
图的顶点数经初始化后不可变,图的初始化有以下两种形式
Graph(int verCount); Graph(String filename);
其中文件内容为:
100 // 顶点数 1000 //边数 52 59 // 以下每一行代表一条边 24 57 54 63 ....
图表示文件的生成参见下文中的
GenGraph.java
程序。- 若想在Graph类中添加
addVertex
方法,可考虑使用符号表,即使用string类型作为邻接表的索引。
1. 图的表示
Graph的表示可以使用:
- 边的列表(list of edges):定义一个Edge类表示每一条边
- 邻接矩阵(adjacency matrix): 用一个V * V的矩阵来表示图
- 邻接表(adjacency lists): 使用一个以顶点为索引的列表数组
- 邻接集(adjacency sets): 使用一个以顶点为索引的set集合
典型Graph实现的性能复杂度:
数据结构 | 所需空间 | 添加一条边 | 检查两个顶点是否相邻 | 遍历顶点v的相邻顶点 |
---|---|---|---|---|
边的列表 | E | 1 | E | E |
邻接矩阵 | V2 | 1 | 1 | V |
邻接表 | E + V | 1 | degree(v) | degree(v) |
邻接集 | E + V | logV | logV | logV + degree(v) |
package graph;
import java.io.*;
import java.util.LinkedList;
public class Digraph {
private final int verCount; // number of vertices;
private int edgeCount; // number of edges;
private LinkedList<Integer>[] adj; // adjacency lists;
public Digraph(int verCount) {
this.verCount = verCount;
this.edgeCount = 0;
adj = (LinkedList<Integer>[]) new LinkedList[verCount];
for (int v = 0; v < verCount; ++v)
adj[v] = new LinkedList<Integer>();
}
public Digraph(String filename) throws IOException {
BufferedReader in = new BufferedReader(
new FileReader(new File(filename).getAbsoluteFile()));
// Get the vertex count
verCount = Integer.parseInt(in.readLine());
// Get the edge count
int edgeNum = Integer.parseInt(in.readLine());
// Initial the graph
adj = (LinkedList<Integer>[]) new LinkedList[verCount];
for (int v = 0; v < verCount; ++v)
adj[v] = new LinkedList<Integer>();
// Add the edges to graph
for (int i = 0; i < edgeNum; ++i) {
String[] currEdge = in.readLine().split(" ");
if (currEdge.length > 0)
addEdge(Integer.parseInt(currEdge[0]),
Integer.parseInt(currEdge[1]));
}
in.close();
}
public int getVerCount() { return verCount; }
public int getedgeCount() { return edgeCount; }
public void addEdge(int v, int w) {
adj[v].add(w);
edgeCount++;
}
public Iterable<Integer> adj(int v) {
return adj[v];
}
public Digraph reverse() {
Digraph dg = new Digraph(verCount);
for (int v = 0; v < verCount; ++v)
for (int w : adj(v))
dg.addEdge(v, w);
return dg;
}
public static void main(String[] args) throws IOException {
Digraph ug = new Digraph("graph.txt");
System.out.println(ug.reverse().getedgeCount());
// print the adjacent vertex of vertex 0
for (int w : ug.adj(0))
System.out.print(w + " ");
}
}
GenGraph.java:
// GenGraph.java
package graph;
import java.util.*;
import utils.*;
public class GenGraph {
private String outfile;
private Random rand = new Random(System.currentTimeMillis());
private int verCount;
private int edgeCount;
public GenGraph(String outfile, int verCount, int edgeCount) {
this.outfile = outfile;
this.verCount = verCount;
this.edgeCount = edgeCount;
}
public void run() {
StringBuilder sb = new StringBuilder();
sb.append(verCount + "\n");
sb.append(edgeCount + "\n");
for (int i = 0; i < edgeCount; ) {
int left = rand.nextInt(verCount);
int right = rand.nextInt(verCount);
if (left == right)
continue;
else if (left < right)
sb.append(left + " " + right + "\n");
else
sb.append(right + " " + left + "\n");
++i;
}
TextFile.write(outfile, sb.toString());
}
public static void main(String[] args) {
GenGraph gen = new GenGraph("graph.txt", 100, 1000);
gen.run();
}
}
// TextFile.java
package utils;
import java.io.*;
import java.util.ArrayList;
import java.util.Arrays;
public class TextFile extends ArrayList<String> {
// Read a file as a single string:
public static String read(String fileName) {
StringBuilder sb = new StringBuilder();
try {
BufferedReader in = new BufferedReader(new FileReader(
new File(fileName).getAbsoluteFile()));
try {
String s;
while ((s = in.readLine()) != null) {
sb.append(s);
sb.append("\n");
}
} finally {
in.close();
}
} catch (IOException e) {
throw new RuntimeException(e);
}
return sb.toString();
}
// Write a single file in one method call:
public static void write(String fileName, String text) {
try {
PrintWriter out = new PrintWriter(
new File(fileName).getAbsoluteFile());
try {
out.print(text);
} finally {
out.close();
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
// Read a file, split by any regular expression
public TextFile(String fileName, String splitter) {
super(Arrays.asList(read(fileName).split(splitter)));
// Regular expression split() often leaves an
// empty String at the first position.
if (get(0).equals("")) remove(0);
}
// Normally read by lines
public TextFile(String fileName) {
this(fileName, "\n");
}
public void write(String fileName) {
try {
PrintWriter out = new PrintWriter(
new File(fileName).getAbsoluteFile());
try {
for (String item : this)
out.println(item);
} finally {
out.close();
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
2. 图的应用
2.1 单点和多点的连通性
- 单点可达性: 给定一幅有向图和一个起点s,回答”是否存在一条从s到达给定顶点v的有向路径?”等类似问题。
- 多点可达性:给定一幅有向图和顶点的集合,回答”是否存在一条从集合中的任意顶点到达给定顶点v的有向路径?”等类似问题。
package graph;
import java.io.*;
import java.util.*;
public class DirectedDFS {
private boolean[] marked;
// find vertices in graph that are reachable from s
public DirectedDFS(Digraph graph, int s) {
marked = new boolean[graph.getVerCount()];
dfs(graph, s);
}
// find vertices in graph that are reachable from sources
public DirectedDFS(Digraph graph, Iterable<Integer> sources) {
marked = new boolean[graph.getVerCount()];
for (int s : sources)
if (!marked[s]) dfs(graph, s);
}
private void dfs(Digraph graph, int v) {
marked[v] = true;
for (int w : graph.adj(v))
if (!marked[w]) dfs(graph, w);
}
public boolean marked(int v) {
return marked[v];
}
public static void main(String[] args) throws IOException {
Digraph graph = new Digraph(args[0]);
List<Integer> sources = new LinkedList<Integer>();
for (int i = 1; i < args.length; ++i)
sources.add(Integer.parseInt(args[i]));
DirectedDFS reachable = new DirectedDFS(graph, sources);
for (int v = 0; v < graph.getVerCount(); ++v)
if (reachable.marked(v) == true)
System.out.print(v + " ");
}
}
E.g.
java DirectedDFS graph.txt 1
java DirectedDFS graph.txt 1 2 6
2.2 单点有向路径
- 单点有向路径: 给定一副有向图和一个起点s, 回答”从s到给定目的顶点v是否存在一条有向路径?如果有,请找出这条路径。”等类似问题。
package graph;
import java.util.*;
import java.io.*;
public class DepthFirstPaths {
private boolean[] marked;
public int[] edgeTo; // last vertex on known path to this vertex
private final int s; // source
// find paths in graph from source s
public DepthFirstPaths(Digraph graph, int s) {
marked = new boolean[graph.getVerCount()];
edgeTo = new int[graph.getVerCount()];
this.s = s;
dfs(graph, s);
}
private void dfs(Digraph graph, int v) {
marked[v] = true;
for (int w : graph.adj(v))
if (!marked[w]) {
edgeTo[w] = v;
dfs(graph, w);
}
}
public boolean hasPathTo(int v) {
return marked[v];
}
public Iterable<Integer> pathTo(int v) {
if (!hasPathTo(v)) return null;
Deque<Integer> path = new ArrayDeque<Integer>();
for (int x = v; x != s; x = edgeTo[x])
path.push(x);
path.push(s);
return path;
}
public static void main(String[] args) throws IOException {
Digraph graph = new Digraph(args[0]);
int s = Integer.parseInt(args[1]);
DepthFirstPaths search = new DepthFirstPaths(graph, s);
for (int x : search.pathTo(66))
System.out.print(x + " ");
for (int v = 0; v < graph.getVerCount(); ++v) {
System.out.println("Path " + s + " ---> " + v + " :");
if (search.hasPathTo(v))
for (int x : search.pathTo(v)) {
if (x == s)
System.out.print(s);
else
System.out.print("-" + x);
}
System.out.println();
}
}
}