每日一省之————有向图(有向非赋权图)

1 有向图的数据结构表示


 import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

/**
 * 该类用于表示有向图这种数据结构
 * @author lhever 2017年2月12日 下午9:23:31
 * @version v1.0
 */
public class DirectedGraph
{
    private static final String NEWLINE = System.getProperty("line.separator");
    private final int V;// 顶点数目
    private int E;// 边的数目
    private List<Integer>[] adj;// 存储顶点邻接顶点的列表
    private int[] indegree;// 存储顶点入度的列表

    /**
     * 构造
     * @param V 顶点数目
     * @author lhever 2017年2月12日 下午7:37:10
     * @since v1.0
     */
    @SuppressWarnings("unchecked")
    public DirectedGraph(int V)
    {
        if (V <= 0)
        {
            throw new IllegalArgumentException("有向图中的顶点数必须是一个正整数");
        }
        this.V = V;
        this.E = 0;
        indegree = new int[V];
        adj = (List<Integer>[]) new ArrayList[V];
        for (int v = 0; v < V; v++)
        {
            adj[v] = new ArrayList<Integer>();
        }
    }

    /**
     * 构造方法
     * @param G
     *            一个已知的有向图对象
     * @author lhever 2017年2月12日 下午7:37:47
     * @since v1.0
     */
    public DirectedGraph(DirectedGraph G)
    {
        this(G.V());
        this.E = G.E();
        for (int v = 0; v < V; v++)
        {
            this.indegree[v] = G.indegree(v);
        }
        for (int v = 0; v < G.V(); v++)
        {
            // reverse so that adjacency list is in same order as original
            Stack<Integer> reverse = new Stack<Integer>();
            for (int w : G.adj[v])
            {
                reverse.push(w);
            }
            for (int w : reverse)
            {
                adj[v].add(w);
            }
        }
    }

    /**
     * 返回有向图的顶点数目
     * 
     * @return
     * @author lhever 2017年2月12日 下午7:35:56
     * @since v1.0
     */
    public int V()
    {
        return V;
    }

    /**
     * 返回有向图的边数
     * 
     * @return
     * @author lhever 2017年2月12日 下午7:36:17
     * @since v1.0
     */
    public int E()
    {
        return E;
    }

    private void validateVertex(int v)
    {
        if (v < 0 || v >= V)
        {
            throw new IndexOutOfBoundsException("顶点编号  " + v + " 必须介于 0 与" + (V - 1) + "之间");
        }
    }

    /**
     * 往有向图中添加一条边
     * 
     * @param v
     * @param w
     * @author lhever 2017年2月12日 下午7:35:28
     * @since v1.0
     */
    public void addEdge(int v, int w)
    {
        validateVertex(v);
        validateVertex(w);
        adj[v].add(w);
        indegree[w]++;
        E++;
    }

    /**
     * 返回与顶点v相邻的顶点列表
     * 
     * @param v
     * @return
     * @author lhever 2017年2月12日 下午7:34:45
     * @since v1.0
     */
    public Iterable<Integer> adj(int v)
    {
        validateVertex(v);
        return adj[v];
    }

    /**
     * 返回顶点v的出度
     * 
     * @param v
     * @return
     * @author lhever 2017年2月12日 下午7:33:37
     * @since v1.0
     */
    public int outdegree(int v)
    {
        validateVertex(v);
        return adj[v].size();
    }

    /**
     * 返回顶点v的入度
     * 
     * @param v
     * @return
     * @author lhever 2017年2月12日 下午7:33:12
     * @since v1.0
     */
    public int indegree(int v)
    {
        validateVertex(v);
        return indegree[v];
    }

    /**
     * 返回有向图的反向图
     * 
     * @return
     * @author lhever 2017年2月12日 下午7:31:01
     * @since v1.0
     */
    public DirectedGraph reverse()
    {
        DirectedGraph R = new DirectedGraph(V);
        for (int v = 0; v < V; v++)
        {
            for (int w : adj(v))
            {
                R.addEdge(w, v);
            }
        }
        return R;
    }

    public String toString()
    {
        StringBuilder s = new StringBuilder();
        s.append(V + "个顶点, " + E + " 条边 " + NEWLINE);
        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(NEWLINE);
        }
        return s.toString();
    }

    public static void main(String[] args)
    {
        DirectedGraph g = new DirectedGraph(13);
        g.addEdge(0, 1);
        g.addEdge(0, 5);
        g.addEdge(2, 3);
        g.addEdge(2, 0);
        g.addEdge(3, 2);
        g.addEdge(3, 5);
        g.addEdge(4, 2);
        g.addEdge(4, 3);
        g.addEdge(5, 4);
        g.addEdge(6, 0);
        g.addEdge(6, 4);
        g.addEdge(6, 9);
        g.addEdge(7, 8);
        g.addEdge(7, 6);
        g.addEdge(8, 9);
        g.addEdge(8, 7);
        g.addEdge(9, 10);
        g.addEdge(9, 11);
        g.addEdge(10, 12);
        g.addEdge(11, 12);
        g.addEdge(11, 4);
        g.addEdge(12, 9);

        System.out.println(g);
    }
}

2 使用字符而串而不是数字命名的有向图的数据结构


import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 许多实际问题可以抽象为有向图进行解决,但往往实际问题抽象为图后,其顶点名一般是字符串,该类用于将字符串
 * 类型的顶点名与数字命名的顶点进行关联,以便于我们用图的一般方法进行解决问题。
 */
public class SymbolDirectedGraph
{
    private Map<String, Integer> st; // 字符串名称 -> 数字名称
    private String[] keys; // 数字名称 -> 字符串名称
    private DirectedGraph G;

    public SymbolDirectedGraph(List<List<String>> lists)
    {
        st = new HashMap<String, Integer>();
        for (List<String> list : lists)
        {
            for (String key : list)
            {
                if (!st.containsKey(key))
                {
                    st.put(key, st.size());
                }
            }

        }

        // 建立反向索引,以便根据顶点索引可以获取对应的顶点名
        keys = new String[st.size()];
        for (String name : st.keySet())
        {
            keys[st.get(name)] = name;
        }

        G = new DirectedGraph(st.size());
        for (List<String> list : lists)
        {
            int v = st.get(list.get(0));
            for (int i = 1; i < list.size(); i++)
            {
                int w = st.get(list.get(i));
                G.addEdge(v, w);
            }
        }
    }

    /**
     * 判断有向图中是否包含名字为s的顶点
     * 
     * @param s
     * @return
     * @author lhever 2017年2月12日 下午10:54:00
     * @since v1.0
     */
    public boolean contains(String s)
    {
        return st.containsKey(s);
    }

    /**
     * 返回与字符串命名的顶点相关联的数字顶点
     * 
     * @param s
     * @return
     * @author lhever 2017年2月12日 下午10:54:33
     * @since v1.0
     */
    public int index(String s)
    {
        return st.get(s);
    }

    /**
     * 根据数字顶点查找与其关联的字符串顶点名
     * 
     * @param v
     * @return
     * @author lhever 2017年2月12日 下午10:55:49
     * @since v1.0
     */
    public String name(int v)
    {
        return keys[v];
    }

    public DirectedGraph G()
    {
        return G;
    }


    //      (A) 0————————1(B)
    //         /| \       |         
    //        / |  \      |         
    //       /  3   2(C)  |        
    //      /  (D)        |        
    //  (E) 4————————————5(F)        
    //       \         /         
    //        \       /         
    //         \     /
    //          \   /
    //          6(G)
    public static void main(String[] args)
    {
        List<List<String>> lists = new ArrayList<List<String>>();
        List<String> li01 = new ArrayList<String>();
        li01.add("A");
        li01.add("B");
        li01.add("C");
        li01.add("D");
        li01.add("E");

        List<String> li02 = new ArrayList<String>();
        li02.add("B");
        li02.add("F");

        List<String> li03 = new ArrayList<String>();
        li03.add("E");
        li03.add("F");
        li03.add("G");

        List<String> li04 = new ArrayList<String>();
        li04.add("F");
        li04.add("G");

        lists.add(li01);
        lists.add(li02);
        lists.add(li03);
        lists.add(li04);
        SymbolDirectedGraph sg = new SymbolDirectedGraph(lists);
        DirectedGraph G = sg.G();
        System.out.println(G);
    }
}

3 采用深度优先计算有向图中顶点的可达性

import java.util.ArrayList;
import java.util.List;

/**
 * 该类用于对有向图进行深度优先访问
 * @author lhever 2017年2月12日 下午9:22:33
 * @version v1.0
 */
public class DirectedDFS
{
    private boolean[] marked; // marked[v] = true if v is reachable
                              // from source (or sources)
    private int count; // number of vertices reachable from s

    /**
     * 利用深度优先算法计算有向图G中从源顶点s可以到达的顶点
     * 
     * @param G
     *            the 有向图
     * @param s
     *            起始顶点(源顶点)
     */
    public DirectedDFS(DirectedGraph G, int s)
    {
        marked = new boolean[G.V()];
        dfs(G, s);
    }

    public DirectedDFS(DirectedGraph G, Iterable<Integer> sources)
    {
        marked = new boolean[G.V()];
        for (int v : sources)
        {
            if (!marked[v]) {
                dfs(G, v);
            }
        }
    }

    /**
     * 有向图的深度优先
     * 
     * @param G
     *            有向图
     * @param v
     * @author lhever 2017年2月12日 下午8:09:30
     * @since v1.0
     */
    private void dfs(DirectedGraph G, int v)
    {
        count++;
        marked[v] = true;
        for (int w : G.adj(v))
        {
            if (!marked[w]) {
                dfs(G, w);
            }
        }
    }

    /**
     * 判断从源顶点到顶点v之间是否存在一条有向路径
     * 
     * @param v
     * @return
     * @author lhever 2017年2月12日 下午8:11:07
     * @since v1.0
     */
    public boolean marked(int v)
    {
        return marked[v];
    }

    /**
     * 返回从源顶点可达的所有顶点数
     * 
     * @return
     * @author lhever 2017年2月12日 下午8:12:53
     * @since v1.0
     */
    public int count()
    {
        return count;
    }

    public static void main(String[] args)
    {
        DirectedGraph g = new DirectedGraph(13);
        g.addEdge(0, 1);
        g.addEdge(0, 5);
        g.addEdge(2, 3);
        g.addEdge(2, 0);
        g.addEdge(3, 2);
        g.addEdge(3, 5);
        g.addEdge(4, 2);
        g.addEdge(4, 3);
        g.addEdge(5, 4);
        g.addEdge(6, 0);
        g.addEdge(6, 4);
        g.addEdge(6, 9);
        g.addEdge(7, 8);
        g.addEdge(7, 6);
        g.addEdge(8, 9);
        g.addEdge(8, 7);
        g.addEdge(9, 10);
        g.addEdge(9, 11);
        g.addEdge(10, 12);
        g.addEdge(11, 12);
        g.addEdge(11, 4);
        g.addEdge(12, 9);

        System.out.println(g);

        List<Integer> sources = new ArrayList<Integer>();
        sources.add(0);
        sources.add(3);
        sources.add(8);

        DirectedDFS dfs = new DirectedDFS(g, sources);

        // 打印出从源顶点集合中的任意一个顶点可达的顶点
        for (int v = 0; v < g.V(); v++)
        {
            if (dfs.marked(v))
            {
                System.out.print(v + " ");
            }
        }
        System.out.println();
    }

}

4 判断有向图中是否存在环(有向环)


import java.util.Stack;

/**
 * 该类用于计算有向图中是否存在有向环
 * @author lhever 2017年2月12日 下午9:21:53
 * @version v1.0
 */
public class DirectedCycle {

    private boolean[] marked;      //如果marked[v]= true, 则表明顶点v已经被访问
    private int[] edgeTo;          // edgeTo[v]表示有向路径中到达顶点v的前一个顶点
    private boolean[] onStack;     // onStack[v] = true, 则顶点v在当前递归调用栈中
    private Stack<Integer> cycle;    // 有向图中的有向环,不存在则为null

   /**
    * 判断有向图中是否存在有向环,如果有,则找出这样其中一条环
    * @param G
    * @author lhever 2017年2月12日 下午8:35:22
    * @since v1.0
    */
    public DirectedCycle(DirectedGraph G)
    {
        marked = new boolean[G.V()];
        onStack = 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);
            }

        }
    }

    /**
     * 计算有向图中顶点的拓扑排序,同时找出一条有向环
     * @param G
     * @param v
     * @author lhever 2017年2月12日 下午8:43:59
     * @since v1.0
     */
    private void dfs(DirectedGraph G, int v)
    {
        onStack[v] = true;
        marked[v] = true;
        for (int w : G.adj(v))
        {

            if (cycle != null) // 如果已经找到一条有向环,则不在继续计算
            {
                return;
            }
            else if (!marked[w]) // 如果还没有找到有向环,则采用深度优先递归进一步计算
            {
                edgeTo[w] = v;
                dfs(G, w);
            }
            else if (onStack[w]) // 如果当前dfs的顶点与递归调用栈中的顶点重复,则说明找到了一条有向环
            {
                cycle = new Stack<Integer>();
                for (int x = v; x != w; x = edgeTo[x])
                {
                    cycle.push(x);
                }
                cycle.push(w);
                cycle.push(v);
                assert check();
            }
        }
        // 当前递归调用栈结束后的回溯
        onStack[v] = false;
    }

    /**
     * 判断有向图中是否存在有向环
     * @return
     * @author lhever 2017年2月12日 下午8:54:50
     * @since v1.0
     */
    public boolean hasCycle()
    {
        return cycle != null;
    }

   /**
    * 返回有向图中的一条有向环,如果存在的话。
    * @return
    * @author lhever 2017年2月12日 下午8:55:26
    * @since v1.0
    */
    public Stack<Integer> cycle()
    {
        return cycle;
    }

    /**
     * 如果有向图hasCycle()方法表明存在一条有向环,则进行进一步的验证,以便确认是否的确存在这样一条有向环
     * @return
     * @author lhever 2017年2月12日 下午8:56:28
     * @since v1.0
     */
    private boolean check()
    {
        if (hasCycle())
        {
            // verify cycle
            int first = -1, last = -1;
            for (int v : cycle())
            {
                if (first == -1) {
                    first = v;
                }
                last = v;
            }
            if (first != last)
            {
                System.err.printf("有向环开始于顶点 %d 并结束于顶点 %d\n", first, last);
                return false;
            }
        }

        return true;
    }

    public static void main(String[] args)
    {

        DirectedGraph g = new DirectedGraph(13);
        g.addEdge(0, 1);
        g.addEdge(0, 5);
        g.addEdge(2, 3);
        g.addEdge(2, 0);
        g.addEdge(3, 2);
        g.addEdge(3, 5);
        g.addEdge(4, 2);
        g.addEdge(4, 3);
        g.addEdge(5, 4);
        g.addEdge(6, 0);
        g.addEdge(6, 4);
        g.addEdge(6, 9);
        g.addEdge(7, 8);
        g.addEdge(7, 6);
        g.addEdge(8, 9);
        g.addEdge(8, 7);
        g.addEdge(9, 10);
        g.addEdge(9, 11);
        g.addEdge(10, 12);
        g.addEdge(11, 12);
        g.addEdge(11, 4);
        g.addEdge(12, 9);

        System.out.println(g);

        DirectedCycle dc = new DirectedCycle(g);
        if (dc.hasCycle())
        {
            System.out.print("有向环中的顶点是: ");
            Stack<Integer> cycle = dc.cycle();
            while(!cycle.isEmpty())
            {
                Integer v = cycle.pop();
                System.out.print(v + " ");
            }
            System.out.println();
        } else
        {
            System.out.println("该有向图中不存在有向环");
        }
        System.out.println();
    }

}

5 有向图中基于深度优先搜索的顶点排序(前序、后序、逆后序)


import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

/**
 * 该类用于计算有向图中基于深度优先搜索的顶点排序
 * @author lhever 2017年2月12日 下午10:04:25
 * @version v1.0
 */
public class DepthFirstOrder {

    private boolean[] marked;          
    private int[] pre;                 
    private int[] post;                
    private List<Integer> preorder;   
    private List<Integer> postorder; 
    private Stack<Integer> reversePostOrder;
    private int preCounter;           
    private int postCounter;          

   /**
    * 有向图中基于深度优先搜索的顶点排序(前序、后序、逆后序)
    * @param G
    * @author lhever 2017年2月12日 下午9:41:35
    * @since v1.0
    */
    public DepthFirstOrder(DirectedGraph G)
    {
        pre = new int[G.V()];
        post = new int[G.V()];
        postorder = new ArrayList<Integer>();
        preorder = new ArrayList<Integer>();
        reversePostOrder = new Stack<Integer>();
        marked = new boolean[G.V()];
        for (int v = 0; v < G.V(); v++)
        {
            if (!marked[v])
            {
                dfs(G, v);
            }
        }
    }


    private void dfs(DirectedGraph G, int v)
    {
        marked[v] = true;
        pre[v] = preCounter++;
        preorder.add(v);
        for (int w : G.adj(v))
        {
            if (!marked[w])
            {
                dfs(G, w);
            }
        }
        postorder.add(v);
        reversePostOrder.push(v);
        post[v] = postCounter++;
    }

   /**
   * 返回顶点v在前序排序序列中的序号
    * @param v
    * @return
    * @author lhever 2017年2月12日 下午9:47:24
    * @since v1.0
    */
    public int pre(int v)
    {
        return pre[v];
    }

    /**
     * 返回顶点v在后续排序序列中的序号
     * @param v
     * @return
     * @author lhever 2017年2月12日 下午9:47:56
     * @since v1.0
     */
    public int post(int v)
    {
        return post[v];
    }

   /**
    * 返回后续排序过的顶点
    * @return
    * @author lhever 2017年2月12日 下午9:48:29
    * @since v1.0
    */
    public Iterable<Integer> post()
    {
        return postorder;
    }

   /**
    * 返回前序排序后的顶点
    * @return
    * @author lhever 2017年2月12日 下午9:48:51
    * @since v1.0
    */
    public Iterable<Integer> pre()
    {
        return preorder;
    }

   /**
    * 返回逆后序排序过的顶点
    * @return
    * @author lhever 2017年2月12日 下午9:49:57
    * @since v1.0
    */
    public Iterable<Integer> reversePost()
    {
        Stack<Integer> reverse = new Stack<Integer>();
        for (int v : postorder)
        {
            reverse.push(v);
        }
        return reverse;
    }

    /**
     * 另外一种方法返回逆后序排序过的顶点
     * @return
     * @author lhever 2017年2月12日 下午9:49:57
     * @since v1.0
     */
    public Iterable<Integer> reversePost2()
    {
        return reversePostOrder;
    }


    /**
     * 用于验证通过 pre() 或者 post()得到的排序过得顶点的序号是否与分别与通过pre(v)或post(v)方法计算的一致
     * @param G
     * @return
     * @author lhever 2017年2月12日 下午11:05:55
     * @since v1.0
     */
    private boolean check(DirectedGraph G)
    {

        // 检验 通过post(v)得到的顶点V的序号是否与通过post()返回的顶点列表中对应顶点的序号一致
        int r = 0;
        for (int v : post())
        {
            if (post(v) != r)
            {
                System.out.println("post(v) 与  post() 计算结果不一致");
                return false;
            }
            r++;
        }

        // 检验通过pre(v)计算得到的顶点v的序号是否与通过 pre()返回的顶点列表中的对应顶点的序号一致
        r = 0;
        for (int v : pre())
        {
            if (pre(v) != r)
            {
                System.out.println("pre(v) 与 pre() 计算结果不一致");
                return false;
            }
            r++;
        }

        return true;
    }


    public static void main(String[] args)
    {

        DirectedGraph g = new DirectedGraph(13);
        g.addEdge(0, 1);
        g.addEdge(0, 5);
        g.addEdge(2, 3);
        g.addEdge(2, 0);
        g.addEdge(3, 2);
        g.addEdge(3, 5);
        g.addEdge(4, 2);
        g.addEdge(4, 3);
        g.addEdge(5, 4);
        g.addEdge(6, 0);
        g.addEdge(6, 4);
        g.addEdge(6, 9);
        g.addEdge(7, 8);
        g.addEdge(7, 6);
        g.addEdge(8, 9);
        g.addEdge(8, 7);
        g.addEdge(9, 10);
        g.addEdge(9, 11);
        g.addEdge(10, 12);
        g.addEdge(11, 12);
        g.addEdge(11, 4);
        g.addEdge(12, 9);

        System.out.println(g);

        DepthFirstOrder dfs = new DepthFirstOrder(g);
        System.out.println( "有向图合法性校验结果是:" + dfs.check(g));
        System.out.println("   顶点v  前序排序序号   后续排序序号");
        for (int v = 0; v < g.V(); v++)
        {
            System.out.println(String.format("%4d %4d %4d\n", v, dfs.pre(v), dfs.post(v)));
        }

        System.out.print("前序排序:  ");
        for (int v : dfs.pre())
        {
            System.out.print(v + " ");
        }
        System.out.println();

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

        System.out.print("逆后续排序:");
        for (int v : dfs.reversePost())
        {
            System.out.print(v + " ");
        }
        System.out.println();

    }

}

6 有向图中顶点的拓扑排序


import java.util.ArrayList;
import java.util.List;

/**
 * 该类用于计算有向图的拓扑排序
 * @author lhever 2017年2月12日 下午10:22:47
 * @version v1.0
 */
public class Topological {

    private Iterable<Integer> order;  // topological order
    private int[] rank;               // rank[v] = position of vertex v in topological order

    /**
     * 判断有向图是否具体拓扑排序,如果有,则找出其拓扑排序
     * @param G
     * @author lhever 2017年2月12日 下午10:23:45
     * @since v1.0
     */
    public Topological(DirectedGraph G)
    {
        DirectedCycle dc = new DirectedCycle(G);
        if (!dc.hasCycle())
        {
            DepthFirstOrder dfs = new DepthFirstOrder(G);
            order = dfs.reversePost();
            rank = new int[G.V()];
            int i = 0;
            for (int v : order)
            {
                rank[v] = i++;
            }
        }
    }

    /**
     * 返回有向图的拓扑排序序列
     * @return
     * @author lhever 2017年2月12日 下午10:30:30
     * @since v1.0
     */
    public Iterable<Integer> order()
    {
        return order;
    }

   /**
    * 判断有向图是否具有拓扑排序
    * @return
    * @author lhever 2017年2月12日 下午10:27:03
    * @since v1.0
    */
    public boolean hasOrder()
    {
        return order != null;
    }

    /**
     * 返回顶点v在拓扑排序的序列中的序号
     * @param v
     * @return
     * @author lhever 2017年2月12日 下午10:29:31
     * @since v1.0
     */
    public int rank(int v)
    {
        validateVertex(v);
        if (hasOrder())
        {
            return rank[v];
        } else
        {
            return -1;
        }
    }

    private void validateVertex(int v)
    {
        int V = rank.length;
        if (v < 0 || v >= V)
        {
            throw new IndexOutOfBoundsException("顶点 " + v + " 没有介于 0 和 " + (V - 1) + "之间");
        }
    }


    //      (A) 0————————1(B)
    //         /| \       |         
    //        / |  \      |         
    //       /  3   2(C)  |        
    //      /  (D)        |        
    //  (E) 4————————————5(F)        
    //       \         /         
    //        \       /         
    //         \     /
    //          \   /
    //          6(G)
    public static void main(String[] args)
    {
        List<List<String>> lists = new ArrayList<List<String>>();
        List<String> li01 = new ArrayList<String>();
        li01.add("A");
        li01.add("B");
        li01.add("C");
        li01.add("D");
        li01.add("E");

        List<String> li02 = new ArrayList<String>();
        li02.add("B");
        li02.add("F");

        List<String> li03 = new ArrayList<String>();
        li03.add("E");
        li03.add("F");
        li03.add("G");

        List<String> li04 = new ArrayList<String>();
        li04.add("F");
        li04.add("G");

        lists.add(li01);
        lists.add(li02);
        lists.add(li03);
        lists.add(li04);
        SymbolDirectedGraph sg = new SymbolDirectedGraph(lists);

        Topological topological = new Topological(sg.G());
        for (int v : topological.order())
        {
            System.out.print(sg.name(v) + "   " );
        }
    }

}

7 计算有向图中强连通分量的Kosaraju算法


import java.util.ArrayList;
import java.util.List;

/**
 * 该类其实是用于计算有向图的强连通分量的KosarajuSCC算法
 * 
 * @author lhever 2017年2月13日 下午11:42:54
 * @version v1.0
 */
public class KosarajuSCC
{

    private boolean[] marked; // marked[v] 为true, 则顶点v已经被访问
    private int[] id; // id[v]代表的是包含顶点v的那个强连通分量的id
    private int count; // 强连通分量的数目

    /**
     * 计算有向图的强连通分量
     * 
     * @param G
     * @author lhever 2017年2月12日 下午11:49:37
     * @since v1.0
     */
    public KosarajuSCC(DirectedGraph G)
    {
        // 计算有向图的反向图的逆后续
        DepthFirstOrder dfs = new DepthFirstOrder(G.reverse());

        // 以有向图的反向图的逆后序的顺序运行dfs
        marked = new boolean[G.V()];
        id = new int[G.V()];
        for (int v : dfs.reversePost())
        {
            if (!marked[v])
            {
                dfs(G, v);
                count++;
            }
        }

        // 用于验证id[]数组是否包含了正确的连通分量信息
        assert check(G);
    }

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

    /**
     * 返回强连通分量的数量
     * 
     * @return
     * @author lhever 2017年2月13日 下午11:47:21
     * @since v1.0
     */
    public int count()
    {
        return count;
    }

    /**
     * 判断顶点v和顶点w是否在同一个强连通分量里面
     * 
     * @param v
     * @param w
     * @return
     * @author lhever 2017年2月13日 下午11:47:52
     * @since v1.0
     */
    public boolean stronglyConnected(int v, int w)
    {
        return id[v] == id[w];
    }

    /**
     * 返回包含顶点v的强连通分量的id
     * 
     * @param v
     * @return
     * @author lhever 2017年2月13日 下午11:49:07
     * @since v1.0
     */
    public int id(int v)
    {
        return id[v];
    }

    private boolean check(DirectedGraph G)
    {
        TransitiveClosure tc = new TransitiveClosure(G);
        for (int v = 0; v < G.V(); v++)
        {
            for (int w = 0; w < G.V(); w++)
            {
                if (stronglyConnected(v, w) != (tc.reachable(v, w) && tc.reachable(w, v)))
                {
                    return false;
                }
            }
        }
        return true;
    }

    public static void main(String[] args)
    {
        DirectedGraph g = new DirectedGraph(13);
        g.addEdge(0, 1);
        g.addEdge(0, 5);
        g.addEdge(2, 3);
        g.addEdge(2, 0);
        g.addEdge(3, 2);
        g.addEdge(3, 5);
        g.addEdge(4, 2);
        g.addEdge(4, 3);
        g.addEdge(5, 4);
        g.addEdge(6, 0);
        g.addEdge(6, 4);
        g.addEdge(6, 9);
        g.addEdge(7, 8);
        g.addEdge(7, 6);
        g.addEdge(8, 9);
        g.addEdge(8, 7);
        g.addEdge(9, 10);
        g.addEdge(9, 11);
        g.addEdge(10, 12);
        g.addEdge(11, 12);
        g.addEdge(11, 4);
        g.addEdge(12, 9);
        System.out.println(g);
        KosarajuSCC scc = new KosarajuSCC(g);

        int M = scc.count();
        System.out.println("本系统包含" + M + "个连通分量");

        // 计算各个两条分量包含的顶点数
        @SuppressWarnings("unchecked")
        List<Integer>[] components = new ArrayList[M];
        for (int i = 0; i < M; i++)
        {
            components[i] = new ArrayList<Integer>();
        }
        for (int v = 0; v < g.V(); v++)
        {
            components[scc.id(v)].add(v);
        }

        // 打印结果
        for (int i = 0; i < M; i++)
        {
            for (int v : components[i])
            {
                System.out.print(v + " ");
            }
            System.out.println();
        }

    }

}


//


public class TransitiveClosure
{

    private DirectedDFS[] tc; // tc[v]中的 DirectedDFS对象用于计算所有可以从顶点 v到达的顶点

    public TransitiveClosure(DirectedGraph G)
    {
        tc = new DirectedDFS[G.V()];
        for (int v = 0; v < G.V(); v++)
        {
            tc[v] = new DirectedDFS(G, v);
        }
    }

    /**
     * 判断在有向图中是否存在一条从顶点v到定点w的有向路径
     * 
     * @param v
     * @param w
     * @return
     * @author lhever 2017年2月13日 下午11:56:35
     * @since v1.0
     */
    public boolean reachable(int v, int w)
    {
        return tc[v].marked(w);
    }

    public static void main(String[] args)
    {
        DirectedGraph g = new DirectedGraph(13);
        g.addEdge(0, 1);
        g.addEdge(0, 5);
        g.addEdge(2, 3);
        g.addEdge(2, 0);
        g.addEdge(3, 2);
        g.addEdge(3, 5);
        g.addEdge(4, 2);
        g.addEdge(4, 3);
        g.addEdge(5, 4);
        g.addEdge(6, 0);
        g.addEdge(6, 4);
        g.addEdge(6, 9);
        g.addEdge(7, 8);
        g.addEdge(7, 6);
        g.addEdge(8, 9);
        g.addEdge(8, 7);
        g.addEdge(9, 10);
        g.addEdge(9, 11);
        g.addEdge(10, 12);
        g.addEdge(11, 12);
        g.addEdge(11, 4);
        g.addEdge(12, 9);
        System.out.println(g);

        TransitiveClosure tc = new TransitiveClosure(g);
        System.out.print("     ");
        for (int v = 0; v < g.V(); v++)
        {
            System.out.print(String.format("%3d", v));
        }
        System.out.println();
        System.out.println("--------------------------------------------");

        // 打印有向图的传递闭包
        for (int v = 0; v < g.V(); v++)
        {
            System.out.print(String.format("%3d: ", v));
            for (int w = 0; w < g.V(); w++)
            {
                if (tc.reachable(v, w))
                {
                    System.out.print("  T");
                } else
                {
                    System.out.print("   ");
                }
            }
            System.out.println();
        }
    }

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值