Graph的那些事兒

0. 導言

爲了讓代碼更加清晰,有以下一些約定:

  1. Graph的表示使用鄰接表,更詳細的說明參見 圖的表示
  2. 本文代碼中使用的圖爲 有向圖,圖中的頂點用 int 型來表示
  3. 圖的頂點數經初始化後不可變,圖的初始化有以下兩種形式

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

    其中文件內容爲:

    100  // 頂點數
    1000 //邊數
    52 59  // 以下每一行代表一條邊
    24 57
    54 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.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算法(基於隊列)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章