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的相邻顶点 |
---|---|---|---|---|
边的列表 | 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(); } } }
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算法(基于队列)
相关文章推荐
- 书评:《算法之美( Algorithms to Live By )》
- 动易2006序列号破解算法公布
- 渗透技术一瞥(图)
- 图片引发的溢出危机(图)
- Ruby实现的矩阵连乘算法
- C#插入法排序算法实例分析
- 超大数据量存储常用数据库分表分库算法总结
- C#数据结构与算法揭秘二
- C#冒泡法排序算法实例分析
- 算法练习之从String.indexOf的模拟实现开始
- C#算法之关于大牛生小牛的问题
- C#实现的算24点游戏算法实例分析
- c语言实现的带通配符匹配算法
- 浅析STL中的常用算法
- C++实现图的邻接矩阵存储和广度、深度优先遍历实例分析
- 算法之排列算法与组合算法详解
- C++实现一维向量旋转算法
- Ruby实现的合并排序算法
- C#折半插入排序算法实现方法
- C++实现图的邻接表存储和广度优先遍历实例分析