您的位置:首页 > 其它

Graph的那些事儿

2016-05-30 17:09 253 查看

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的相邻顶点
边的列表E1EE
邻接矩阵V211V
邻接表E + V1degree(v)degree(v)
邻接集E + VlogVlogVlogV + 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();
}
}
}


2.3 单点最短有向路径

2.4 有向环检测

2.5 深度优先的顶点排序

2.6 优先级限制下的调度问题

2.7 拓扑排序

2.8 强连通性

2.9 顶点对的可达性

3. 最小生成树

4. 最短路径

4.1 Dijkstra算法(即时版本)

4.2 拓扑排序

4.3 Bellman-Ford算法(基于队列)

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  Graph 算法