1无向图

1.1无向图的表示

无向图的API:

实现:

package graphs.undirectedgraph;

import edu.princeton.cs.algs4.In;
import fundamentals.Bag;

public class Graph {
    private final int V;//顶点数目
    private int E;//边的数目
    private Bag<Integer>[] adj;//领接表
    public Graph(int V) {//创建一个含有v个顶点但不含有边的图
        this.V = V;
        this.E = 0;
        adj = (Bag<Integer>[]) new Bag[V];//创建领接表
        for (int v = 0; v < V; v++) {
            adj[v] = new Bag<>();
        }
    }
    public Graph(In in) {//从标准输入流in读入一幅图
        this(in.readInt());//读取V并将图初始化
        int E = in.readInt();//读取E
        for (int i = 0; i < E; i++) {//添加一条边
            int v = in.readInt();//读取一个顶点
            int w = in.readInt();//读取另一个顶点
            addEdge(v, w);//添加一条连接它们的边
        }
    }
    public int V(){//顶点数
        return V;
    }
    public int E(){//边数
        return E;
    }
    public void addEdge(int v,int w){//向图中添加一条边v-w
        adj[v].add(w);//将w添加到v的链表中
        adj[w].add(v);//将v添加到w的链表中
        E++;
    }
    public Iterable<Integer>adj(int v){//和v相邻的所有顶点
        return adj[v];
    }
    public int degree(int v) {//计算v的度数
        return adj[v].size();
    }
    public String toString() {//对象的字符串表示
        StringBuilder s = new StringBuilder();
        s.append(V + " vertices, " + E + " edges\n");
        for (int v = 0; v < V; v++) {
            s.append(v + ": ");
            for (int w : adj[v]) {
                s.append(w + " ");
            }
            s.append("\n");
        }
        return s.toString();
    }
    /*测试数据 13 13 0 5 4 3 0 1 9 12 6 4 5 4 0 2 11 12 9 10 0 6 7 8 9 11 5 3 */
    public static void main(String[] args) {
        In in = new In();
        Graph G = new Graph(in);
        System.out.println(G);
    }
}

1.2深度优先搜索

原理:

要搜索一幅图,只需用一个递归方法来遍历所有顶点。在访问其中一个顶点时:
将它标记为已访问;
递归地访问它的所有没有被标记过得邻居顶点。

实现:

package graphs.undirectedgraph;

import edu.princeton.cs.algs4.In;

public class DepthFirstSearch {
    private boolean[] marked;
    private int count;//与s连通的顶点总数

    public DepthFirstSearch(Graph G, int s){//找到和起点s连通的所有顶点
        marked=new boolean[G.V()];
        dfs(G,s);
    }

    private void dfs(Graph G,int v){
        marked[v]=true;
        count++;
        for(int w:G.adj(v)){
            if(!marked[w]){
                dfs(G,w);
            }
        }
    }

    public boolean marked(int w){//v和s是连通的吗?
        return marked[w];
    }
    public int count(){//与s连通的顶点总数
        return count;
    }
    /*测试数据 13 13 0 5 4 3 0 1 9 12 6 4 5 4 0 2 11 12 9 10 0 6 7 8 9 11 5 3 9 */
    public static void main(String[] args) {
        In in = new In();
        Graph G = new Graph(in);
        int s = in.readInt();
        DepthFirstSearch search = new DepthFirstSearch(G, s);
        for (int v = 0; v < G.V(); v++) {
            if (search.marked(v))
                System.out.print(v + " ");
        }

        System.out.println();
        if (search.count() != G.V()) System.out.println("NOT connected");
        else                         System.out.println("connected");
    }
}

1.3使用深度优先搜索查找图中的路径

原理:


实现:

package graphs.undirectedgraph;

import edu.princeton.cs.algs4.In;
import edu.princeton.cs.algs4.Stack;

public class DepthFirstPaths {
    private boolean[] marked;//这个顶点上调用过dfs()了吗?
    private int[] edgeTo;//从起点到顶点的已知路径上的最后一个顶点
    private final int s;//起点

    public DepthFirstPaths(Graph G, int s){//在G中找出所有起点为s的路径(非最短路径)
        marked=new boolean[G.V()];
        edgeTo=new int[G.V()];
        this.s=s;
        dfs(G,s);
    }
    private void dfs(Graph G,int v){
        marked[v]=true;
        for(int w:G.adj(v)){
            if(!marked[w]){
                edgeTo[w]=v;
                dfs(G,w);
            }
        }
    }
    public boolean hasPathTo(int v){//是否存在s到v的路径
        return marked[v];
    }
    Iterable<Integer> pathTo(int v){//s到v的路径,如果不存在则返回null
        if (!hasPathTo(v)) return null;
        Stack<Integer> path=new Stack<>();
        for(int x=v;x!=s;x=edgeTo[x]){
            path.push(x);
        }
        path.push(s);
        return path;
    }
    /*测试数据 6 8 0 5 2 4 2 3 1 2 0 1 3 4 3 5 0 2 0 */
    public static void main(String[] args) {
        In in = new In();
        Graph G = new Graph(in);
        int s = in.readInt();
        DepthFirstPaths dfs = new DepthFirstPaths(G, s);

        for (int v = 0; v < G.V(); v++) {
            if (dfs.hasPathTo(v)) {
                System.out.printf("%d to %d: ", s, v);
                for (int x : dfs.pathTo(v)) {
                    if (x == s) System.out.print(x);
                    else        System.out.print("-" + x);
                }
                System.out.println();
            }
            else {
                System.out.printf("%d to %d: not connected\n", s, v);
            }

        }
    }
}

1.4广度优先搜索

原理:

广度优先搜索可以解决单点最短路径问题。


实现:

package graphs.undirectedgraph;

import edu.princeton.cs.algs4.In;
import edu.princeton.cs.algs4.Queue;
import edu.princeton.cs.algs4.Stack;

public class BreadthFirstPaths {
    private boolean[] marked;//到达该顶点的最短路径已知吗?
    private int[] edgeTo;//到达该顶点的已知路径上的最后一个顶点
    private final int s;//起点

    public BreadthFirstPaths(Graph G, int s){//在G中找出所有起点为s的路径(最短路径)
        marked=new boolean[G.V()];
        edgeTo=new int[G.V()];
        this.s=s;
        bfs(G,s);
    }
    private void bfs(Graph G,int s){
        Queue<Integer> queue=new Queue<>();
        marked[s]=true;//标记起点
        queue.enqueue(s);//将它加入队列
        while (!queue.isEmpty()){
            int v=queue.dequeue();//从队列中删去下一个顶点
            for(int w:G.adj(v)){
                if(!marked[w]){//对于每个未被标记的相邻顶点
                    edgeTo[w]=v;//保存最短路径的最后一条边
                    marked[w]=true;//标记它,因为最短路径已知
                    queue.enqueue(w);//将它添加到队列中
                }
            }
        }
    }
    public boolean hasPathTo(int v){//是否存在s到v的路径
        return marked[v];
    }
    Iterable<Integer> pathTo(int v){//s到v的最短路径,如果不存在则返回null
        if (!hasPathTo(v)) return null;
        Stack<Integer> path=new Stack<>();
        for(int x=v;x!=s;x=edgeTo[x]){
            path.push(x);
        }
        path.push(s);
        return path;
    }
    /*测试数据 6 8 0 5 2 4 2 3 1 2 0 1 3 4 3 5 0 2 0 */
    public static void main(String[] args) {
        In in = new In();
        Graph G = new Graph(in);
        int s = in.readInt();
        BreadthFirstPaths bfs = new BreadthFirstPaths(G, s);

        for (int v = 0; v < G.V(); v++) {
            if (bfs.hasPathTo(v)) {
                System.out.printf("%d to %d: ", s, v);
                for (int x : bfs.pathTo(v)) {
                    if (x == s) System.out.print(x);
                    else        System.out.print("-" + x);
                }
                System.out.println();
            }
            else {
                System.out.printf("%d to %d: not connected\n", s, v);
            }

        }
    }
}

1.5连通分量

原理:

实现:

package graphs.undirectedgraph;

import edu.princeton.cs.algs4.In;
import edu.princeton.cs.algs4.Queue;

public class CC {
    private boolean[] marked;
    private int[] id;//id[v]表示v属于哪个连通分量
    private int count;//连通分量个数

    public CC(Graph G){//预处理构造函数
        marked=new boolean[G.V()];
        id=new int[G.V()];
        for(int s=0;s<G.V();s++){
            if (!marked[s]){
                dfs(G,s);
                count++;
            }
        }
    }
    private void dfs(Graph G,int v){
        marked[v]=true;
        id[v]=count;
        for(int w:G.adj(v)){
            if (!marked[w])
                dfs(G,w);
        }
    }
    public boolean connected(int v,int w){//v和w连通吗?
        return id[v]==id[w];
    }
    public int id(int v){//v所在的连通分量的标识符(0~count()-1)
        return id[v];
    }
    public int count(){//连通分量数
        return count;
    }
/*测试数据 13 13 0 5 4 3 0 1 9 12 6 4 5 4 0 2 11 12 9 10 0 6 7 8 9 11 5 3 */
    public static void main(String[] args) {
        In in = new In();
        Graph G = new Graph(in);
        CC cc = new CC(G);

        // number of connected components
        int m = cc.count();
        System.out.println(m + " components");

        // compute list of vertices in each connected component
        Queue<Integer>[] components = (Queue<Integer>[]) new Queue[m];
        for (int i = 0; i < m; i++) {
            components[i] = new Queue<>();
        }
        for (int v = 0; v < G.V(); v++) {
            components[cc.id(v)].enqueue(v);
        }

        // print results
        for (int i = 0; i < m; i++) {
            for (int v : components[i]) {
                System.out.print(v + " ");
            }
            System.out.println();
        }
    }
}

2有向图

2.1有向图的表示

有向图的API:

实现:

package graphs.directedgraph;

import edu.princeton.cs.algs4.In;
import fundamentals.Bag;

public class Digraph {
    private final int V;//顶点数目
    private int E;//边的数目
    private Bag<Integer>[] adj;//领接表
    private int[] indegree;// indegree[v] = indegree of vertex v ,入度
    public Digraph(int V) {//创建一个含有v个顶点但不含有边的有向图
        this.V = V;
        this.E = 0;
        indegree = new int[V];
        adj = (Bag<Integer>[]) new Bag[V];//创建领接表
        for (int v = 0; v < V; v++) {
            adj[v] = new Bag<>();
        }
    }
    public Digraph(In in) {//从标准输入流in读入一幅图
        this(in.readInt());//读取V并将图初始化
        int E = in.readInt();//读取E
        for (int i = 0; i < E; i++) {//添加一条边
            int v = in.readInt();//读取一个顶点
            int w = in.readInt();//读取另一个顶点
            addEdge(v, w);//添加一条连接它们的边
        }
    }
    public int V(){//顶点数
        return V;
    }
    public int E(){//边数
        return E;
    }
    public void addEdge(int v,int w){//向有向图中添加一条边v-w
        adj[v].add(w);//将w添加到v的链表中
        indegree[w]++;
        E++;
    }
    public Iterable<Integer>adj(int v){//和v相邻的所有顶点
        return adj[v];
    }
    public int outdegree(int v) {//v的出度
        return adj[v].size();
    }
    public int indegree(int v) {//v的入度
        return indegree[v];
    }
    public Digraph reverse() {
        Digraph reverse = new Digraph(V);
        for (int v = 0; v < V; v++) {
            for (int w : adj(v)) {
                reverse.addEdge(w, v);
            }
        }
        return reverse;
    }
    public String toString() {//对象的字符串表示
        StringBuilder s = new StringBuilder();
        s.append(V + " vertices, " + E + " edges\n");
        for (int v = 0; v < V; v++) {
            s.append(String.format("%d: ", v));
            for (int w : adj[v]) {
                s.append(String.format("%d ", w));
            }
            s.append("\n");
        }
        return s.toString();
    }
    /*测试数据 13 22 4 2 2 3 3 2 6 0 0 1 2 0 11 12 12 9 9 10 9 11 7 9 10 12 11 4 4 3 3 5 6 8 8 6 5 4 0 5 6 4 6 9 7 6 */
    public static void main(String[] args) {
        In in = new In();
        Digraph G = new Digraph(in);
        System.out.println(G);
    }
}

2.2有向图的可达性

package graphs.directedgraph;

import edu.princeton.cs.algs4.In;

public class DirectedDFS {
    private boolean[] marked;

    public DirectedDFS(Digraph G, int s){//在G中找到从s可达的所有顶点
        marked=new boolean[G.V()];
        dfs(G,s);
    }

    public DirectedDFS(Digraph G,Iterable<Integer> sources){//在G中找到从sources中的所有顶点可达的所有顶点
        marked=new boolean[G.V()];
        for (int s:sources){
            if(!marked[s]) dfs(G,s);
        }
    }

    private void dfs(Digraph G,int v){
        marked[v]=true;
        for (int w:G.adj(v)){
            if (!marked[w]) dfs(G,w);
        }
    }

    public boolean marked(int v){//v是可达的吗
        return marked[v];
    }
    /*测试数据 13 22 4 2 2 3 3 2 6 0 0 1 2 0 11 12 12 9 9 10 9 11 7 9 10 12 11 4 4 3 3 5 6 8 8 6 5 4 0 5 6 4 6 9 7 6 2 */
    public static void main(String[] args){
        In in = new In();
        Digraph G=new Digraph(in);
        int s = in.readInt();
        DirectedDFS rechable=new DirectedDFS(G,s);
        for (int v = 0; v < G.V(); v++) {
            if (rechable.marked(v))
                System.out.print(v + " ");
        }

        System.out.println();
    }
}

2.3有向图的寻路

与无向图中的寻路类似。
使用深度优先搜索解决“从s到给定目的顶点v是否存在一条有向路径?如果有。找出这条路径。”问题。

package graphs.directedgraph;

import edu.princeton.cs.algs4.In;
import edu.princeton.cs.algs4.Stack;

public class DepthFirstDirectedPaths {
    private boolean[] marked;//这个顶点上调用过dfs()了吗?
    private int[] edgeTo;//从起点到顶点的已知路径上的最后一个顶点
    private final int s;//起点

    public DepthFirstDirectedPaths(Digraph G, int s){//在G中找出所有起点为s的有向路径(非最短路径)
        marked=new boolean[G.V()];
        edgeTo=new int[G.V()];
        this.s=s;
        dfs(G,s);
    }
    private void dfs(Digraph G,int v){
        marked[v]=true;
        for(int w:G.adj(v)){
            if(!marked[w]){
                edgeTo[w]=v;
                dfs(G,w);
            }
        }
    }
    public boolean hasPathTo(int v){//是否存在s到v的路径
        return marked[v];
    }
    Iterable<Integer> pathTo(int v){//s到v的路径,如果不存在则返回null
        if (!hasPathTo(v)) return null;
        Stack<Integer> path=new Stack<>();
        for(int x=v;x!=s;x=edgeTo[x]){
            path.push(x);
        }
        path.push(s);
        return path;
    }
    /*测试数据 13 22 4 2 2 3 3 2 6 0 0 1 2 0 11 12 12 9 9 10 9 11 7 9 10 12 11 4 4 3 3 5 6 8 8 6 5 4 0 5 6 4 6 9 7 6 3 */
    public static void main(String[] args) {
        In in = new In();
        Digraph G = new Digraph(in);
        int s = in.readInt();
        DepthFirstDirectedPaths dfs = new DepthFirstDirectedPaths(G, s);

        for (int v = 0; v < G.V(); v++) {
            if (dfs.hasPathTo(v)) {
                System.out.printf("%d to %d: ", s, v);
                for (int x : dfs.pathTo(v)) {
                    if (x == s) System.out.print(x);
                    else        System.out.print("-" + x);
                }
                System.out.println();
            }
            else {
                System.out.printf("%d to %d: not connected\n", s, v);
            }

        }
    }
}

使用广度优先搜索解决“从s到给定目的顶点v是否存在一条有向路径?如果有。找出其中最短的那条。”问题。

package graphs.directedgraph;

import edu.princeton.cs.algs4.In;
import edu.princeton.cs.algs4.Queue;
import edu.princeton.cs.algs4.Stack;

public class BreadthFirstDirectedPaths {
    private static final int INFINITY = Integer.MAX_VALUE;
    private boolean[] marked;//到达该顶点的最短路径已知吗?
    private int[] edgeTo;//到达该顶点的已知路径上的最后一个顶点
    private final int s;//起点
    private int[] distTo; // distTo[v] = length of shortest s->v path

    public BreadthFirstDirectedPaths(Digraph G, int s){//在G中找出所有起点为s的有向路径(最短路径)
        marked=new boolean[G.V()];
        distTo = new int[G.V()];
        edgeTo=new int[G.V()];
        for (int v = 0; v < G.V(); v++)
            distTo[v] = INFINITY;
        this.s=s;
        bfs(G,s);
    }
    private void bfs(Digraph G,int s){
        Queue<Integer> queue=new Queue<>();
        marked[s]=true;//标记起点
        distTo[s] = 0;
        queue.enqueue(s);//将它加入队列
        while (!queue.isEmpty()){
            int v=queue.dequeue();//从队列中删去下一个顶点
            for(int w:G.adj(v)){
                if(!marked[w]){//对于每个未被标记的相邻顶点
                    edgeTo[w]=v;//保存最短路径的最后一条边
                    distTo[w] = distTo[v] + 1;
                    marked[w]=true;//标记它,因为最短路径已知
                    queue.enqueue(w);//将它添加到队列中
                }
            }
        }
    }
    public boolean hasPathTo(int v){//是否存在s到v的路径
        return marked[v];
    }

    public int distTo(int v) {
        return distTo[v];
    }

    Iterable<Integer> pathTo(int v){//s到v的最短路径,如果不存在则返回null
        if (!hasPathTo(v)) return null;
        Stack<Integer> path=new Stack<>();
        for(int x=v;x!=s;x=edgeTo[x]){
            path.push(x);
        }
        path.push(s);
        return path;
    }
    /*测试数据 13 22 4 2 2 3 3 2 6 0 0 1 2 0 11 12 12 9 9 10 9 11 7 9 10 12 11 4 4 3 3 5 6 8 8 6 5 4 0 5 6 4 6 9 7 6 3 */
    public static void main(String[] args) {
        In in = new In();
        Digraph G = new Digraph(in);
        int s = in.readInt();
        BreadthFirstDirectedPaths bfs = new BreadthFirstDirectedPaths(G, s);

        for (int v = 0; v < G.V(); v++) {
            if (bfs.hasPathTo(v)) {
                System.out.printf("%d to %d (%d): ", s, v, bfs.distTo(v));
                for (int x : bfs.pathTo(v)) {
                    if (x == s) System.out.print(x);
                    else        System.out.print("->" + x);
                }
                System.out.println();
            }
            else {
                System.out.printf("%d to %d (-): not connected\n", s, v);
            }

        }
    }
}

2.4寻找有向环

原理:


实现:

package graphs.directedgraph;

import edu.princeton.cs.algs4.In;
import edu.princeton.cs.algs4.Stack;

public class DirectedCycle {
    private boolean[] marked;
    private int[] edgeTo;
    private boolean[] onStack;       // onStack[v] = is vertex on the stack?
    private Stack<Integer> cycle;    // directed cycle (or null if no such cycle)

    public DirectedCycle(Digraph G){//寻找有向环的构造函数
        onStack=new boolean[G.V()];
        marked  = new boolean[G.V()];
        edgeTo  = new int[G.V()];
        for (int v = 0; v < G.V(); v++)
            if (!marked[v] && cycle == null) dfs(G, v);
    }

    private void dfs(Digraph G,int v){
        onStack[v]=true;
        marked[v]=true;
        for(int w:G.adj(v)){
            // short circuit if directed cycle found
            if(cycle!=null) return;
            // found new vertex, so recur
            else if(!marked[w]){
                edgeTo[w]=v;
                dfs(G,w);
            }
            // trace back directed cycle
            else if(onStack[w]){
                cycle=new Stack<>();
                for(int x=v;x!=w;x=edgeTo[x]){
                    cycle.push(x);
                }
                cycle.push(w);
                cycle.push(v);
            }
        }
        onStack[v]=false;
    }

    public boolean hasCycle() {//G是否含有有向环
        return cycle != null;
    }

    public Iterable<Integer> cycle() {//有向环中的所有顶点
        return cycle;
    }
    /*测试数据 13 22 4 2 2 3 3 2 6 0 0 1 2 0 11 12 12 9 9 10 9 11 7 9 10 12 11 4 4 3 3 5 6 8 8 6 5 4 0 5 6 4 6 9 7 6 运行结果:Directed cycle: 3 5 4 3 */
    /*测试数据 13 15 2 3 0 6 0 1 2 0 11 12 9 12 9 10 9 11 3 5 8 7 5 4 0 5 6 4 6 9 7 6 运行结果:No directed cycle*/
    public static void main(String[] args) {
        In in = new In();
        Digraph G = new Digraph(in);
        DirectedCycle finder = new DirectedCycle(G);
        if (finder.hasCycle()) {
            System.out.print("Directed cycle: ");
            for (int v : finder.cycle()) {
                System.out.print(v + " ");
            }
            System.out.println();
        }

        else {
            System.out.println("No directed cycle");
        }
        System.out.println();
    }
}

2.5拓扑排序

原理:



实现:

package graphs.directedgraph;


import edu.princeton.cs.algs4.In;
import edu.princeton.cs.algs4.Queue;
import edu.princeton.cs.algs4.Stack;
import graphs.shortestpaths.DirectedEdge;
import graphs.shortestpaths.EdgeWeightedDigraph;

public class DepthFirstOrder {
    private boolean[] marked;
    private Queue<Integer> pre;//所有顶点的前序排列,即dfs的调用顺序
    private Queue<Integer> post;//所有顶点的后序排列,即顶点遍历完成的顺序
    private Stack<Integer> reversePost;//所有顶点的逆后序排列

    public DepthFirstOrder(Digraph G){
        pre=new Queue<>();
        post=new Queue<>();
        reversePost=new Stack<>();
        marked=new boolean[G.V()];
        for(int v=0;v<G.V();v++){
            if(!marked[v]) dfs(G,v);
        }
    }

    public DepthFirstOrder(EdgeWeightedDigraph G) {
        post = new Queue<Integer>();
        pre  = new Queue<Integer>();
        reversePost=new Stack<>();
        marked    = new boolean[G.V()];
        for (int v = 0; v < G.V(); v++)
            if (!marked[v]) dfs(G, v);
    }

    private void dfs(Digraph G,int v){
        pre.enqueue(v);
        marked[v]=true;
        for(int w:G.adj(v)){
            if (!marked[w]) dfs(G,w);
        }
        post.enqueue(v);
        reversePost.push(v);
    }
    private void dfs(EdgeWeightedDigraph G, int v) {
        marked[v] = true;
        pre.enqueue(v);
        for (DirectedEdge e : G.adj(v)) {
            int w = e.to();
            if (!marked[w]) {
                dfs(G, w);
            }
        }
        post.enqueue(v);
        reversePost.push(v);
    }
    public Iterable<Integer> pre() {
        return pre;
    }
    public Iterable<Integer> post() {
        return post;
    }
    public Iterable<Integer> reversePost() {
        return reversePost;
    }
    /* 13 15 2 3 0 6 0 1 2 0 11 12 9 12 9 10 9 11 3 5 8 7 5 4 0 5 6 4 6 9 7 6 */
    public static void main(String[] args) {
        In in = new In();
        Digraph G = new Digraph(in);

        DepthFirstOrder dfs = new DepthFirstOrder(G);
        System.out.print("Preorder: ");
        for (int v : dfs.pre()) {
            System.out.print(v + " ");
        }
        System.out.println();

        System.out.print("Postorder: ");
        for (int v : dfs.post()) {
            System.out.print(v + " ");
        }
        System.out.println();

        System.out.print("Reverse postorder: ");
        for (int v : dfs.reversePost()) {
            System.out.print(v + " ");
        }
        System.out.println();
    }
}

2.6有向图中的强连通性

Kosaraju算法原理:



实现:

package graphs.directedgraph;


import edu.princeton.cs.algs4.In;
import edu.princeton.cs.algs4.Queue;

public class KosarajuSharirSCC {
    private boolean[] marked;     // marked[v] = has vertex v been visited?
    private int[] id;             // id[v] = id of strong component containing v
    private int count;            // number of strongly-connected components

    public KosarajuSharirSCC(Digraph G) {
        // compute reverse postorder of reverse graph
        DepthFirstOrder dfs = new DepthFirstOrder(G.reverse());

        // run DFS on G, using reverse postorder to guide calculation
        marked = new boolean[G.V()];
        id = new int[G.V()];
        for (int v : dfs.reversePost()) {
            if (!marked[v]) {
                dfs(G, v);
                count++;
            }
        }
    }

    // DFS on graph G
    private void dfs(Digraph G, int v) {
        marked[v] = true;
        id[v] = count;
        for (int w : G.adj(v)) {
            if (!marked[w]) dfs(G, w);
        }
    }

    public int count() {
        return count;
    }

    public boolean stronglyConnected(int v, int w) {
        return id[v] == id[w];
    }

    public int id(int v) {
        return id[v];
    }
    /*测试数据 50 147 0 7 0 34 1 14 1 45 1 21 1 22 1 22 1 49 2 19 2 25 2 33 3 4 3 17 3 27 3 36 3 42 4 17 4 17 4 27 5 43 6 13 6 13 6 28 6 28 7 41 7 44 8 19 8 48 9 9 9 11 9 30 9 46 10 0 10 7 10 28 10 28 10 28 10 29 10 29 10 34 10 41 11 21 11 30 12 9 12 11 12 21 12 21 12 26 13 22 13 23 13 47 14 8 14 21 14 48 15 8 15 34 15 49 16 9 17 20 17 24 17 38 18 6 18 28 18 32 18 42 19 15 19 40 20 3 20 35 20 38 20 46 22 6 23 11 23 21 23 22 24 4 24 5 24 38 24 43 25 2 25 34 26 9 26 12 26 16 27 5 27 24 27 32 27 31 27 42 28 22 28 29 28 39 28 44 29 22 29 49 30 23 30 37 31 18 31 32 32 5 32 6 32 13 32 37 32 47 33 2 33 8 33 19 34 2 34 19 34 40 35 9 35 37 35 46 36 20 36 42 37 5 37 9 37 35 37 47 37 47 38 35 38 37 38 38 39 18 39 42 40 15 41 28 41 44 42 31 43 37 43 38 44 39 45 8 45 14 45 14 45 15 45 49 46 16 47 23 47 30 48 12 48 21 48 33 48 33 49 34 49 22 49 49 */
    public static void main(String[] args) {
        In in = new In();
        Digraph G = new Digraph(in);
        KosarajuSharirSCC scc = new KosarajuSharirSCC(G);

        // number of connected components
        int m = scc.count();
        System.out.println(m + " strong components");

        // compute list of vertices in each strong component
        Queue<Integer>[] components = (Queue<Integer>[]) new Queue[m];
        for (int i = 0; i < m; i++) {
            components[i] = new Queue<>();
        }
        for (int v = 0; v < G.V(); v++) {
            components[scc.id(v)].enqueue(v);
        }

        // print results
        for (int i = 0; i < m; i++) {
            for (int v : components[i]) {
                System.out.print(v + " ");
            }
            System.out.println();
        }

    }

}

3最小生成树

3.1原理






3.2加权无向图的表示

加权边的API:

实现:

package graphs.minimumspanningtrees;

//加权边
public class Edge implements Comparable<Edge> { 

    private final int v;//顶点之一
    private final int w;//另一个顶点
    private final double weight;//边的权重

    public Edge(int v, int w, double weight) {
        this.v = v;
        this.w = w;
        this.weight = weight;
    }

    public double weight() {
        return weight;
    }

    public int either() {//边两端的顶点之一
        return v;
    }

    public int other(int vertex) {//另一个顶点
        if      (vertex == v) return w;
        else if (vertex == w) return v;
        else throw new RuntimeException("Inconsistent edge");
    }

    public int compareTo(Edge that) {
        return Double.compare(this.weight, that.weight);
    }

    public String toString() {
        return String.format("%d-%d %.5f", v, w, weight);
    }

    public static void main(String[] args) {
        Edge e = new Edge(12, 34, 5.67);
        System.out.println(e);
    }
}

加权无向图的API:

实现:

package graphs.minimumspanningtrees;

import edu.princeton.cs.algs4.In;
import fundamentals.Bag;

//加权无向图
public class EdgeWeightedGraph {
    private static final String NEWLINE = System.getProperty("line.separator");

    private final int V;//顶点总数
    private int E;//边的总数
    private Bag<Edge>[] adj;//领接表

    public EdgeWeightedGraph(int V) {//创建一幅含有V个顶点的空图
        if (V < 0) throw new IllegalArgumentException("Number of vertices must be nonnegative");
        this.V = V;
        this.E = 0;
        adj = (Bag<Edge>[]) new Bag[V];
        for (int v = 0; v < V; v++) {
            adj[v] = new Bag<>();
        }
    }

    public EdgeWeightedGraph(In in) {//从输入流中读取图
        this(in.readInt());
        int E = in.readInt();
        if (E < 0) throw new IllegalArgumentException("Number of edges must be nonnegative");
        for (int i = 0; i < E; i++) {
            int v = in.readInt();
            int w = in.readInt();
            double weight = in.readDouble();
            Edge e = new Edge(v, w, weight);
            addEdge(e);
        }
    }

    public int V() {
        return V;
    }

    public int E() {
        return E;
    }

    public void addEdge(Edge e) {
        int v = e.either();
        int w = e.other(v);
        adj[v].add(e);
        adj[w].add(e);
        E++;
    }

    public Iterable<Edge> adj(int v) {

        return adj[v];
    }

    public int degree(int v) {
        return adj[v].size();
    }

    public Iterable<Edge> edges() {
        Bag<Edge> list = new Bag<>();
        for (int v = 0; v < V; v++) {
            int selfLoops = 0;
            for (Edge e : adj(v)) {
                if (e.other(v) > v) {
                    list.add(e);
                }
                // add only one copy of each self loop (self loops will be consecutive)
                else if (e.other(v) == v) {
                    if (selfLoops % 2 == 0) list.add(e);
                    selfLoops++;
                }
            }
        }
        return list;
    }

    public String toString() {
        StringBuilder s = new StringBuilder();
        s.append(V + " " + E + NEWLINE);
        for (int v = 0; v < V; v++) {
            s.append(v + ": ");
            for (Edge e : adj[v]) {
                s.append(e + " ");
            }
            s.append(NEWLINE);
        }
        return s.toString();
    }
    /*测试数据 8 16 4 5 0.35 4 7 0.37 5 7 0.28 0 7 0.16 1 5 0.32 0 4 0.38 2 3 0.17 1 7 0.19 0 2 0.26 1 2 0.36 1 3 0.29 2 7 0.34 6 2 0.40 3 6 0.52 6 0 0.58 6 4 0.93 */
    public static void main(String[] args) {
        In in = new In();
        EdgeWeightedGraph G = new EdgeWeightedGraph(in);
        System.out.println(G);
    }

}

3.3最小生成树的Prim算法

原理:

实现:



package graphs.minimumspanningtrees;

import edu.princeton.cs.algs4.In;
import edu.princeton.cs.algs4.MinPQ;
import edu.princeton.cs.algs4.Queue;

//最小生成树的Prim算法的延时实现
public class LazyPrimMST {
    private double weight;       // total weight of MST
    private Queue<Edge> mst;     // edges in the MST
    private boolean[] marked;    // marked[v] = true if v on tree
    private MinPQ<Edge> pq;      // edges with one endpoint in tree

    public LazyPrimMST(EdgeWeightedGraph G) {
        mst = new Queue<>();
        pq = new MinPQ<>();
        marked = new boolean[G.V()];
        for (int v = 0; v < G.V(); v++)     // run Prim from all vertices to
            if (!marked[v]) prim(G, v);     // get a minimum spanning forest
    }

    // run Prim's algorithm
    private void prim(EdgeWeightedGraph G, int s) {
        scan(G, s);
        while (!pq.isEmpty()) {                        // better to stop when mst has V-1 edges
            Edge e = pq.delMin();                      // smallest edge on pq
            int v = e.either(), w = e.other(v);        // two endpoints
            if (marked[v] && marked[w]) continue;      // lazy, both v and w already scanned
            mst.enqueue(e);                            // add e to MST
            weight += e.weight();
            if (!marked[v]) scan(G, v);               // v becomes part of tree
            if (!marked[w]) scan(G, w);               // w becomes part of tree
        }
    }

    // add all edges e incident to v onto pq if the other endpoint has not yet been scanned
    private void scan(EdgeWeightedGraph G, int v) {
        marked[v] = true;
        for (Edge e : G.adj(v))
            if (!marked[e.other(v)]) pq.insert(e);
    }

    public Iterable<Edge> edges() {
        return mst;
    }
    public double weight() {
        return weight;
    }
    /*测试数据 8 16 4 5 0.35 4 7 0.37 5 7 0.28 0 7 0.16 1 5 0.32 0 4 0.38 2 3 0.17 1 7 0.19 0 2 0.26 1 2 0.36 1 3 0.29 2 7 0.34 6 2 0.40 3 6 0.52 6 0 0.58 6 4 0.93 */
    public static void main(String[] args) {
        In in = new In();
        EdgeWeightedGraph G = new EdgeWeightedGraph(in);
        LazyPrimMST mst = new LazyPrimMST(G);
        for (Edge e : mst.edges()) {
            System.out.println(e);
        }
        System.out.printf("%.5f\n", mst.weight());
    }
}

3.4 最小生成树的Kruskal算法

原理:



实现:

package graphs.minimumspanningtrees;

import edu.princeton.cs.algs4.In;
import edu.princeton.cs.algs4.MinPQ;
import edu.princeton.cs.algs4.Queue;
import edu.princeton.cs.algs4.UF;

public class KruskalMST {
    private double weight;                    // weight of MST
    private Queue<Edge> mst = new Queue<>();  // edges in MST

    public KruskalMST(EdgeWeightedGraph G) {
        // more efficient to build heap by passing array of edges
        MinPQ<Edge> pq = new MinPQ<>();
        for (Edge e : G.edges()) {
            pq.insert(e);
        }

        // run greedy algorithm
        UF uf = new UF(G.V());
        while (!pq.isEmpty() && mst.size() < G.V() - 1) {
            Edge e = pq.delMin();
            int v = e.either();
            int w = e.other(v);
            if (!uf.connected(v, w)) { // v-w does not create a cycle
                uf.union(v, w);  // merge v and w components
                mst.enqueue(e);  // add edge e to mst
                weight += e.weight();
            }
        }
    }

    public Iterable<Edge> edges() {
        return mst;
    }

    public double weight() {
        return weight;
    }
    /* 8 16 4 5 0.35 4 7 0.37 5 7 0.28 0 7 0.16 1 5 0.32 0 4 0.38 2 3 0.17 1 7 0.19 0 2 0.26 1 2 0.36 1 3 0.29 2 7 0.34 6 2 0.40 3 6 0.52 6 0 0.58 6 4 0.93 */
    public static void main(String[] args) {
        In in = new In();
        EdgeWeightedGraph G = new EdgeWeightedGraph(in);
        KruskalMST mst = new KruskalMST(G);
        for (Edge e : mst.edges()) {
            System.out.println(e);
        }
        System.out.printf("%.5f\n", mst.weight());
    }

}

4最短路径

4.1加权有向图的表示

加权有向边的API:

实现:

package graphs.shortestpaths;

public class DirectedEdge {//加权有向边
    private final int v;//边的起点
    private final int w;//边的终点
    private final double weight;//边的权重

    public DirectedEdge(int v, int w, double weight) {
        this.v = v;
        this.w = w;
        this.weight = weight;
    }

    public int from() {
        return v;
    }

    public int to() {
        return w;
    }

    public double weight() {
        return weight;
    }

    public String toString() {
        return v + "->" + w + " " + String.format("%5.2f", weight);
    }

    public static void main(String[] args) {
        DirectedEdge e = new DirectedEdge(12, 34, 5.67);
        System.out.println(e);
    }
}

加权有向图的API:

实现:

package graphs.shortestpaths;

import edu.princeton.cs.algs4.In;
import fundamentals.Bag;

//加权有向图
public class EdgeWeightedDigraph {
    private static final String NEWLINE = System.getProperty("line.separator");

    private final int V;                // number of vertices in this digraph
    private int E;                      // number of edges in this digraph
    private Bag<DirectedEdge>[] adj;    // adj[v] = adjacency list for vertex v
    private int[] indegree;             // indegree[v] = indegree of vertex v

    public EdgeWeightedDigraph(int V) {
        if (V < 0) throw new IllegalArgumentException("Number of vertices in a Digraph must be nonnegative");
        this.V = V;
        this.E = 0;
        this.indegree = new int[V];
        adj = (Bag<DirectedEdge>[]) new Bag[V];
        for (int v = 0; v < V; v++)
            adj[v] = new Bag<>();
    }

    public EdgeWeightedDigraph(In in) {
        this(in.readInt());
        int E = in.readInt();
        if (E < 0) throw new IllegalArgumentException("Number of edges must be nonnegative");
        for (int i = 0; i < E; i++) {
            int v = in.readInt();
            int w = in.readInt();
            double weight = in.readDouble();
            addEdge(new DirectedEdge(v, w, weight));
        }
    }

    public int V() {
        return V;
    }

    public int E() {
        return E;
    }

    public void addEdge(DirectedEdge e) {
        int v = e.from();
        int w = e.to();
        adj[v].add(e);
        indegree[w]++;
        E++;
    }

    public Iterable<DirectedEdge> adj(int v) {
        return adj[v];
    }

    public int outdegree(int v) {
        return adj[v].size();
    }

    public int indegree(int v) {
        return indegree[v];
    }

    public Iterable<DirectedEdge> edges() {
        Bag<DirectedEdge> list = new Bag<DirectedEdge>();
        for (int v = 0; v < V; v++) {
            for (DirectedEdge e : adj(v)) {
                list.add(e);
            }
        }
        return list;
    } 

    public String toString() {
        StringBuilder s = new StringBuilder();
        s.append(V + " " + E + NEWLINE);
        for (int v = 0; v < V; v++) {
            s.append(v + ": ");
            for (DirectedEdge e : adj[v]) {
                s.append(e + " ");
            }
            s.append(NEWLINE);
        }
        return s.toString();
    }
    /*测试数据 8 15 4 5 0.35 5 4 0.35 4 7 0.37 5 7 0.28 7 5 0.28 5 1 0.32 0 4 0.38 0 2 0.26 7 3 0.39 1 3 0.29 2 7 0.34 6 2 0.40 3 6 0.52 6 0 0.58 6 4 0.93 */
    public static void main(String[] args) {
        In in = new In();
        EdgeWeightedDigraph G = new EdgeWeightedDigraph(in);
        System.out.println(G);
    }

}

4.2通用算法

基础:边的松弛


通用最短路径算法:

4.3Dijkstra算法(边权重非负的加权有向图)

原理:




实现:

package graphs.shortestpaths;

import edu.princeton.cs.algs4.In;
import edu.princeton.cs.algs4.IndexMinPQ;
import edu.princeton.cs.algs4.Stack;

public class DijkstraSP {//最短路径的Dijkstra算法
    private double[] distTo;          // distTo[v] = distance of shortest s->v path
    private DirectedEdge[] edgeTo;    // edgeTo[v] = last edge on shortest s->v path
    private IndexMinPQ<Double> pq;    // priority queue of vertices

    public DijkstraSP(EdgeWeightedDigraph G, int s) {
        for (DirectedEdge e : G.edges()) {
            if (e.weight() < 0)
                throw new IllegalArgumentException("edge " + e + " has negative weight");
        }

        distTo = new double[G.V()];
        edgeTo = new DirectedEdge[G.V()];

        for (int v = 0; v < G.V(); v++)
            distTo[v] = Double.POSITIVE_INFINITY;
        distTo[s] = 0.0;

        // relax vertices in order of distance from s
        pq = new IndexMinPQ<Double>(G.V());
        pq.insert(s, distTo[s]);
        while (!pq.isEmpty()) {
            int v = pq.delMin();
            for (DirectedEdge e : G.adj(v))
                relax(e);
        }
    }

    // relax edge e and update pq if changed
    private void relax(DirectedEdge e) {
        int v = e.from(), w = e.to();
        if (distTo[w] > distTo[v] + e.weight()) {
            distTo[w] = distTo[v] + e.weight();
            edgeTo[w] = e;
            if (pq.contains(w)) pq.decreaseKey(w, distTo[w]);
            else                pq.insert(w, distTo[w]);
        }
    }

    public double distTo(int v) {
        return distTo[v];
    }

    public boolean hasPathTo(int v) {
        return distTo[v] < Double.POSITIVE_INFINITY;
    }

    public Iterable<DirectedEdge> pathTo(int v) {
        if (!hasPathTo(v)) return null;
        Stack<DirectedEdge> path = new Stack<DirectedEdge>();
        for (DirectedEdge e = edgeTo[v]; e != null; e = edgeTo[e.from()]) {
            path.push(e);
        }
        return path;
    }
    /*测试数据 8 15 4 5 0.35 5 4 0.35 4 7 0.37 5 7 0.28 7 5 0.28 5 1 0.32 0 4 0.38 0 2 0.26 7 3 0.39 1 3 0.29 2 7 0.34 6 2 0.40 3 6 0.52 6 0 0.58 6 4 0.93 */
    public static void main(String[] args) {
        In in = new In();
        EdgeWeightedDigraph G = new EdgeWeightedDigraph(in);
        int s = in.readInt();

        // compute shortest paths
        DijkstraSP sp = new DijkstraSP(G, s);
        // print shortest path
        for (int t = 0; t < G.V(); t++) {
            if (sp.hasPathTo(t)) {
                System.out.printf("%d to %d (%.2f) ", s, t, sp.distTo(t));
                for (DirectedEdge e : sp.pathTo(t)) {
                    System.out.print(e + " ");
                }
                System.out.println();
            }
            else {
                System.out.printf("%d to %d no path\n", s, t);
            }
        }
    }
}

4.4无环加权有向图的最短路径算法

原理:



实现:

package graphs.shortestpaths;

import edu.princeton.cs.algs4.In;
import edu.princeton.cs.algs4.Stack;
import graphs.directedgraph.DepthFirstOrder;

public class AcyclicSP {//无环加权有向图的最短路径算法
    private double[] distTo;         // distTo[v] = distance of shortest s->v path
    private DirectedEdge[] edgeTo;   // edgeTo[v] = last edge on shortest s->v path

    public AcyclicSP(EdgeWeightedDigraph G, int s) {
        distTo = new double[G.V()];
        edgeTo = new DirectedEdge[G.V()];

        for (int v = 0; v < G.V(); v++)
            distTo[v] = Double.POSITIVE_INFINITY;
        distTo[s] = 0.0;

        DepthFirstOrder dfs = new DepthFirstOrder(G);
        for (int v : dfs.reversePost()) {
            for (DirectedEdge e : G.adj(v))
                relax(e);
        }
    }

    // relax edge e
    private void relax(DirectedEdge e) {
        int v = e.from(), w = e.to();
        if (distTo[w] > distTo[v] + e.weight()) {
            distTo[w] = distTo[v] + e.weight();
            edgeTo[w] = e;
        }       
    }

    public double distTo(int v) {
        return distTo[v];
    }

    public boolean hasPathTo(int v) {
        return distTo[v] < Double.POSITIVE_INFINITY;
    }

    public Iterable<DirectedEdge> pathTo(int v) {
        if (!hasPathTo(v)) return null;
        Stack<DirectedEdge> path = new Stack<>();
        for (DirectedEdge e = edgeTo[v]; e != null; e = edgeTo[e.from()]) {
            path.push(e);
        }
        return path;
    }
    /*测试数据 8 13 5 4 0.35 4 7 0.37 5 7 0.28 5 1 0.32 4 0 0.38 0 2 0.26 3 7 0.39 1 3 0.29 7 2 0.34 6 2 0.40 3 6 0.52 6 0 0.58 6 4 0.93 */
    public static void main(String[] args) {
        In in = new In();
        EdgeWeightedDigraph G = new EdgeWeightedDigraph(in);
        int s =in.readInt();
        // find shortest path from s to each other vertex in DAG
        AcyclicSP sp = new AcyclicSP(G, s);
        for (int v = 0; v < G.V(); v++) {
            if (sp.hasPathTo(v)) {
                System.out.printf("%d to %d (%.2f) ", s, v, sp.distTo(v));
                for (DirectedEdge e : sp.pathTo(v)) {
                    System.out.print(e + " ");
                }
                System.out.println();
            }
            else {
                System.out.printf("%d to %d no path\n", s, v);
            }
        }
    }
}

4.5一般加权有向图中的最短路径问题(不含负权重环)

原理:



4.6总结三种最短路径算法