Graph的那些事儿

来源:互联网 发布:压力传感器 淘宝 编辑:程序博客网 时间:2024/05/21 13:21

0. 导言

为了让代码更加清晰,有以下一些约定:

  1. Graph的表示使用邻接表,更详细的说明参见 图的表示
  2. 本文代码中使用的图为 有向图,图中的顶点用 int 型来表示
  3. 图的顶点数经初始化后不可变,图的初始化有以下两种形式

    Graph(int verCount);Graph(String filename);

    其中文件内容为:

    100  // 顶点数1000 //边数52 59  // 以下每一行代表一条边24 5754 63....

    图表示文件的生成参见下文中的GenGraph.java程序。

  4. 若想在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.javapackage 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.javapackage 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算法(基于队列)

0 0