精华内容
下载资源
问答
  • Kosaraju算法
    2017-12-08 21:00:00

    Kosaraju算法是求解有向图强连通分量(strong connected component)的三个著名算法之一,能在线性时间求解出一个图的强分量。用Sedgewick爷爷的话说,就是“现代算法设计的胜利”。

    什么是强连通分量?在这之前先定义一个强连通性(strong connectivity)的概念:有向图中,如果一个顶点s到t有一条路径,t到s也有一条路径,即s与t互相可达,那么我们说s与t是强连通的。那么在有向图中,由互相强连通的顶点构成的分量,称作强连通分量

    首先说一些离散数学相关的结论,由强连通性的概念可以发现,这是一个等价关系

    证明:

    一,按照有向图的约定,每个顶点都有到达自身的路径,即自环,即任意顶点s到s可达,满足自反性;

    二,如果s与t是强连通的,则s到t存在路径,t到s存在路径,显然t与s也是强连通的,满足对称性;

    三,如果r与s强连通,s与t强连通,则r与s互相可达,s与t互相可达,显然r与t也互相可达,满足传递性。

    因此,强连通关系可导出一个等价类,这就是强连通分量。进一步的利用这结论可以知道,两个强连通分量之间木有交集(这个结论很重要)。事实上,图论与离散数学中的关系有非常密切的……关系。

    在编程求解强连通分量时,通常做法是对顶点进行编号,拥有相同编号的顶点属于同一个强连通分量。在求解完之后,通过对编号的比较可以迅速判断两个顶点是否是强连通的。

     

    ------------------------------分割线-----------------------------------

     

    Kosaraju算法过程上并不复杂。要求解一个有向图的强连通分量,第一步:在该图的逆图上运行DFS,将顶点按照后序编号的顺序放入一个数组中(显然,这个过程作用在DAG上得到的就是一个拓扑排序);第二步:在原图上,按第一步得出的后序编号的逆序进行DFS。也就是说,在第二次DFS时,每次都挑选当前未访问的结点中具有最大后序编号的顶点作为DFS树的树根。

    Kosaraju算法的显著特征是,第一,引用了有向图的逆图;第二,需要对图进行两次DFS(一次在逆图上,一次在原图上)。而且这个算法依赖于一个事实:一个有向图的强连通分量与其逆图是一样的(即假如顶点任意顶点s与t属于原图中的一个强连通分量,那么在逆图中这两个顶点必定也属于同一个强连通分量,这个事实由强连通性的定义可证)。由于算法的时间取决于两次DFS,因此时间复杂度,对于稀疏图是O(V+E),对于稠密图是O(V²),可见这是一个线性算法。Kosaraju的结论是,在第二次DFS中,同一棵搜索树上的结点属于一个强连通分量。

    证明:假设顶点s与t属于第二次DFS森林(注意,第二次是在原图上搜索)的同一棵树,r是这棵树的根结点。那么有以下两个事实:一,原图中由r可达s,这蕴含在逆图中从s到r有一条路径;二,r在逆图中的后序编号大于s(r是树根,因此r的后序编号比树中所有的其他结点的都大)。现在要证明的是在逆图中从r到s也是可达的。

    好,两个事实结合起来:一,假设逆图中r到s不可达,且s到r存在路径,那么这条路径将使s的后序编号比r大,与事实一矛盾,排除;二,假设逆图中r到s存在路径,正是这条r到s的路径使得r有更大的后序编号,则r与s是强连通的,假设成立(看上去比较勉强,个人认为这应该是一个空证明)。显然,两个事实导出一个结论:逆图中,r与s互相可达。同理,r与t也互相可达,根据传递性,第二次DFS森林中同一棵树中的所有顶点构成一个强连通分量。

    另一方面,会不会一个强连通分量的所有顶点没有出现在第二次DFS森林的同一颗树中呢?答案是:不会。因为根据DFS的性质,如果r与s强连通,那么由r开始的DFS必定能搜到s。

    证毕。

    可见Kosaraju的方法能够找出有向图的强连通分量,那么为什么这个方法可行呢?或者如何实现呢?这正是Kosaraju算法最为精妙的地方,关键在于第二次DFS选取的顺序:在第一次DFS中,将顶点按照后序编号存放,第二次DFS就按照这个顺序的逆序进行搜索,这保证每次选取的根结点(刚才证明中的r结点)都具有未访问结点中最大的后序编号,则搜索中拓展的结点的后序编号都比根结点小,这样也就满足了事实二。

    补充:Kosaraju算法虽然是线性的,但是需要两次DFS,跟另外两个著名的求解强分量的算法相比,这是一个劣势。但是Kosaraju算法有个神奇之处在于:计算之后的强分量编号的顺序,刚好是该有向图K(D)(kernel DAG, 核心DAG)的一个拓扑排序!因此Kosaraju算法同时提供了一个计算有向图K(D)拓扑排序的线性算法。这个结果在一些应用中非常重要。

    最后附上我的实现~就一目了然啦~

     

    ---------------------------分割线again--------------------------------

     

    // Kosaraju算法邻接矩阵实现

     

    static int cnt, cntR, pre[MAXV], postR[MAXV];

    int Kosaraju(Graph G) {

      int v;

      // 初始化全局变量

      cnt cntR 0;

      for (v 0; G->V; ++v)

        pre[v] postR[v] -1;

      // 第一次DFS,计算逆图的后序编号

      for (v 0; G->V; ++v)

        if (pre[v] == -1)

          dfsPostR(G, v);

      cnt 0;

      for (v 0; G->V; ++v)

        G->sc[v] -1;  // G->sv[v]表示顶点v的强连通分量编号

      // 第二次DFS,强连通分量编号

      for (v G->V 1; >= 0; --v) {

        // 注意搜索的顶点顺序是逆图后序编号的逆序

        if (G->sc[postR[v]] == -1) {

          dfsSC(G, postR[v]);

          ++cnt;  // 对一棵树编号之后计数器值加1

        }

      }

      return cnt;  // 返回强连通分量的个数

    }

     

    void dfsPostR(Graph G, int v) {

      // 对逆图后序编号

      int t;

      pre[v] cnt++;

      for (t 0; G->V; ++t)

        if (G->adj[t][v] == 1)  // 注意!!!邻接矩阵引用逆图,因此是G->adj[t][v]

          if (pre[t] == -1)

            dfsPostR(G, t);

      postR[cntR++] v;  // 后序编号,注意是计数器做数组下标

    }

     

    void dfsSC(Graph G, int v) {

      int t;

      G->sc[v] cnt; // 计数器作为编号

      for (t 0; G->V; ++t)

        if (G->adj[v][t] == 1)

          if (G->sc[t] == -1)

            dfsSC(G, t);

    }

     

    int GraphSC(Graph G, int s, int t) {

      // 比较顶点的强连通分量编号即可判断是否强连通

      return G->sc[s] == G->sc[t];

    }


    from:http://blog.sina.com.cn/s/blog_4dff87120100r58c.html

    更多相关内容
  • Kosaraju算法详解

    2020-08-29 11:10:00
    主要为大家详细介绍了Kosaraju算法Kosaraju算法可以计算出一个有向图的强连通分量,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
  • kosaraju 算法Kosaraju 算法是一种线性时间算法,用于查找有向图的强连通分量算法Kosaraju 算法的工作原理如下: 设 G 为有向图,S 为空栈。 虽然 S 不包含所有顶点: 选择不在 S 中的任意顶点 ''v''。从 ''v'' 开始...
  • 2. 用Kosaraju算法实现了强连通分量的求解。其中data中包含的GoolNodes测试集为Google提供的网页之间的连接经转化而来,每一个结点均代表一个网页。 3. 缺点:为了使用以前的CGraph类,强行添加了结点文件,其中第一...
  • kosaraju算法_算法_

    2021-10-01 04:08:56
    利用拓扑排序实现kosaraju算法具体实现及源码
  • 13.1 Sharir-Kosaraju算法

    2022-04-06 09:26:47
    从概念定义、到算法详解、Python实现讲解Kosaraju算法

    强连接组件

      Sharir-Kosaraju算法,有时候也简称Kosajaru算法,其目的是为了计算出图的强连通分量。强连通分量,英文为strongly connected component,简称SCC。
      首先,我们要先了解什么是强连接分量。强连接分量,是图的一部分,在这一部分里,每个点都可以连接到其他点。如下图(图片来自斯坦福大学),存在五个强连接分量:

    在这里插入图片描述
      可以看出,强连接分量要么是一个环,要么是几个边相邻的环,要么是两个反向平行边,要么是单独的一个点。而上图覆盖了四种情况。
      求出强连接分量后,可以做什么?首先是可以凝结Condensation。凝结是将强连接分量缩成一个点,这对于大型图简化来说是非常重要的。

    Kusajaru算法

      该算法分为三步:
      1. DFS算法计算完成时间。DFS算法可以计算出每个节点的发现时间与完成时间。
      2. 计算逆图,所谓逆图,就是将边进行反向后产生的图。
      3. 在逆图上继续DFS,因为前面正向了,如果反向还能访问,那无疑是一个强连接组件。DFS以最大访问时间的节点开始,如此DFS找出所有的强连接组件。
      以上图为例子,我们先计算访问时间,下图中的括号部分表示的是发现时间与访问时间:
    在这里插入图片描述
      然后再产生逆图:
    在这里插入图片描述

      按结束时间从大到小遍历,产生强连通组件,先是E,发现时间戳为26:
    在这里插入图片描述

      然后是A,发现时间戳为22:
    在这里插入图片描述
      然后是C,发现时间戳为19:
    在这里插入图片描述
      然后是K,发现时间戳为12:
    在这里插入图片描述

      然后是L,发现时间戳为11:
    在这里插入图片描述

    Python实现

    # _*_ coding:utf-8 _*_
    
    
    class UnweightedGraph:
        def __init__(self, vertices, edges):
            self.__vertices = vertices
            self.__edges = edges
    
        @property
        def vertices(self):
            return self.__vertices
    
        @vertices.setter
        def vertices(self, value):
            self.__vertices = value
    
        @property
        def edges(self):
            return self.__edges
    
        @edges.setter
        def edges(self, value):
            self.__edges = value
    
        def kosajaru(self):
            # 第一步 DFS计算时间
    
            time_stack = self.dfs_time_stack()
            # 创建逆图
            reversed_edges = [None] * len(self.__edges)
            # 遍历原图,生成逆图
            for from_vertex, neighbours in enumerate(self.__edges):
                for to_vertex in neighbours:
                    if reversed_edges[to_vertex] is None:
                        reversed_edges[to_vertex] = []
                    reversed_edges[to_vertex].append(from_vertex)
    
            # 打印一下逆图
            reversed_graph = UnweightedGraph(self.__vertices, reversed_edges)
            # 对逆图进行DFS
            # scc的列表,是一个二维数组,里面有多个scc,每个scc是一个list
            return UnweightedGraph.dfs_scc(time_stack, reversed_edges)
    
        white = 0
        gray = 1
        black = 2
    
        def dfs_time_stack(self):
            # 着色
            colors = [UnweightedGraph.white for _ in self.__vertices]
            times = [{'d': None, 'f': None} for _ in self.__vertices]
            time = 1
            time_stack = []
            for root, _ in enumerate(self.__vertices):
                # 其实可以用栈代替
                if not colors[root] == UnweightedGraph.white:
                    continue
                # 这里的i是森林的一个根
    
                times[root]['d'] = time
                time += 1
                stack = [root]
                while len(stack) > 0:
                    # 这一步就错了,不要直接弹出
                    v = stack[-1]
                    if colors[v] == UnweightedGraph.black:
                        stack.pop()
                        times[v]['f'] = time
                        time += 1
    
                        time_stack.append(v)
                    else:
                        colors[v] = UnweightedGraph.gray
                        for neighbor in self.__edges[v]:
                            if colors[neighbor] == UnweightedGraph.white:
                                times[neighbor]['d'] = time
                                time += 1
                                stack.append(neighbor)
                                colors[neighbor] = UnweightedGraph.gray
                        colors[v] = UnweightedGraph.black
    
    
            return time_stack
    
        @staticmethod
        def dfs_scc(time_stack, edges):
            # 着色
            visited = [False for _ in time_stack]
            # d代表discover f代表finish,发现时间戳和完成时间戳
            scc_list = []
            for root in reversed(time_stack):
                # 其实可以用栈代替
                if visited[root]:
                    continue
                # 这里的i是森林的一个根
                stack = [root]
                scc = []
                while len(stack) > 0:
                    v = stack.pop()
                    if visited[v]:
                        continue
                    for neighbor in edges[v]:
                        if not visited[neighbor]:
                            stack.append(neighbor)
                    visited[v] = True
                    scc.append(v)
                scc_list.append(scc)
            return scc_list
    
        def to_dot(self):
            s = 'digraph s {\n'
            for v in self.__vertices:
                s += f'"{v}";\n'
            for i, e in enumerate(self.__edges):
                for t in e:
                    s += f'\"{self.__vertices[i]}\"->"{self.__vertices[t]}";\n'
            s += '}\n'
            return s
    
    
    展开全文
  • kosaraju算法

    2020-08-23 17:55:08
    深入浅出的用Kosaraju算法计算得到有向图的强连通分量数

    kosaraju算法用来求有向图的强连通分量


    我们需要提前知道的知识

    什么是强连通

    有向图中,从v可以到达w, 从w也可以到达v,那么称v和w是强连通的
    如果一副图中任意两点强连通,这幅图也是强连通的。


    强连通分量

    1. 本图强连通分量为3
      在这里插入图片描述
    2. 本图强连通分量为4
      在这里插入图片描述
    3. 本图强连通分量为4
      在这里插入图片描述

    无向图的连通分量怎么计算

    kosaraju算法不过是在下面的基础上增加了几行,

    package Algorithm;
    
    public class CC {
        private int count;//连通分量个数
        private boolean[] marked;//标记是否访问过
        private int[] id;//id[v]=1,说明顶点v在分量1里面
        public CC(Graph g){
            marked=new boolean[g.V()];
            id=new int[g.V()];
            for (int i = 0; i <g.V(); i++) {
                if(!marked[i]){
                    dfs(g,i);
                    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){
            return id[v]==id[w];
        }
        public int count(){
            return count;
        }
        public int id(int v){
            return id[v];
        }
    }
    

    什么是顶点的逆后序排列

    调用dfs时,将顶点放在栈里面即可。

    package Algorithm.DirectedGraph;
    import java.util.LinkedList;
    public class DF_Order {
        private LinkedList<Integer>  reversePost;//栈
        private boolean[] marked;//标记是否访问过
        private int vNum;//顶点数目
        public DF_Order(DiGraph g){
            vNum=g.getV();
            reversePost=new LinkedList<>();
            marked=new boolean[g.getV()];
            for (int i = 0; i < g.getV(); i++) {
                if(!marked[i]){
                    dfs(g,i);
                }
            }
        }
        private void dfs(DiGraph g, int i) {
            marked[i]=true;
            for(int w:g.adj(i)){
                if(!marked[w]){
                    dfs(g,w);
                }
            }
            reversePost.push(i);
        }
        public Iterable<Integer> reversePost(){
            return reversePost;
        }
    }
    
    

    什么是反向图

    将有向图的边的指向反过来即可。
    在这里插入图片描述
    反向图:
    在这里插入图片描述


    kosaraju算法

    步骤

    1. 将图反向
    2. 得到反向图的逆后序排列
    3. 用逆后序的顺序遍历原图
    package Algorithm.DirectedGraph;
    
    public class KosarajuCC {
        private int[] id;
        private int count;
        private boolean[] marked;
        public KosarajuCC(DiGraph graph){
            marked=new boolean[graph.getV()];
            id=new int[graph.getV()];
            DF_Order order = new DF_Order(graph.reverse());
            for (int x:order.reversePost()){
                if(!marked[x]) {
                    dfs(graph,x);
                    count++;
                }
            }
        }
    
        private void dfs(DiGraph graph, int v) {
            marked[v]=true;
            id[v]=count;
            for (int x:graph.adj(v)){
                if(!marked[x]) dfs(graph,x);
            }
        }
        public boolean stronglyConnected(int v,int w){
            return id[v]==id[w];
        }
        public int getCount(){
            return count;
        }
    
    }
    
    
    

    对于该算法的理解

    我们从哪一个顶点进行DFS呢?
    按照顶点顺序:0 1 2 3 4 5
    那么0 1是一个强连通分量
    2 3 4 5是一个强连通分量,显然是不对的。
    按照逆后序就是: 0 1 4 5 2 3
    那么0 1是一个强连通分量
    4 5是一个强连通分量
    2 3是一个强连通分量
    先访问0 1 4 5,在进行2 3,那么2通向1或4 的道路被封死了。
    在这里插入图片描述

    用这个反向图的逆后序去深度优先搜索(正向)图,可以发现每次递归都是在同一个强连通分量之中。

    1. 如图
      在这里插入图片描述
      逆后序得到:0 1 4 5 2 3

    2. .
      在这里插入图片描述
      逆后序得到 4 5 2 3 0 1

    3. .
      在这里插入图片描述
      逆后序得到 2 3 0 1 4 5


    无向图,有向图的图结构

    package Algorithm.DirectedGraph;
    
    import Algorithm.Bag;
    
    public class DiGraph {
    
        private int v;
        private int e;
        private Bag<Integer>[] adj;
        public DiGraph(int v,int e){
            this.v=v;
            this.e=e;
            adj=(Bag<Integer>[]) new Bag[v];
            for (int i = 0; i < v; i++) {
                adj[i]=new Bag<Integer>();
            }
        }
        public DiGraph(int v){
            this.v=v;
            adj=(Bag<Integer>[]) new Bag[v];
            for (int i = 0; i < v; i++) {
                adj[i]=new Bag<Integer>();
            }
    
        }
        public int getV(){return v;};
        public int getE(){return e;};
        public void add(int v,int w){
            adj[v].add(w);
        }
        public Iterable<Integer> adj(int v){
            return adj[v];
        }
        public DiGraph reverse(){
            DiGraph diGraph = new DiGraph(v);
            for (int i = 0; i < v; i++) {
                for (int x:adj(i)){
                    diGraph.add(x,i);
                }
            }
            return diGraph;
        }
    }
    
    
    
    //无向图
    package Algorithm;
    public class Graph {
        private int V;
        private int E;
        private Bag<Integer>[] adj;//邻接表
        public Graph(int v,int e) {
            this.V = v;
            this.E=e;
            adj=(Bag<Integer>[]) new Bag[v];
            for (int i = 0; i < V; i++) {
                adj[i]=new Bag<Integer>();
            }
        }
        public Graph(int v){
            this.V=v;
            adj=(Bag<Integer>[]) new Bag[v];
            for (int i = 0; i < v; i++) {
                adj[i]=new Bag<Integer>();
            }
        }
        public int V(){
            //返回定点数
            return V;
        }
        public int E(){
            //返回边数
            return E;
        }
        public void addEdge(int v,int w){
            //添加一条边
            adj[v].add(w);
            adj[w].add(v);
            E++;
        }
        public Iterable<Integer> adj(int v){
          //和v相邻的所有顶点。只是输入的时候,不包含计算得出的所有
            return adj[v];
        }
    }
    
    
    package Algorithm;
    
    import java.util.Iterator;
    
    public class Bag<Item> implements Iterable<Item> {
        @Override
        public Iterator<Item> iterator() {//实现iterable,才可以foreach
            //iterator 迭代器
            return new ListIterator();
        }
        private class ListIterator implements Iterator<Item> {
            private node current=first;
            @Override
            public boolean hasNext() {
                return current!=null;
            }
    
            @Override
            public Item next() {
                Item item=current.val;
                current=current.next;
                return item;
            }
        }
        private class node{
            Item val;
            node next;
        }
        private int n=0;
        private  node first;
        public void add(Item item){
            node oldfirst=first;
            first=new node();
            first.val=item;
            first.next=oldfirst;
            n++;
        }
        public boolean isEmpty(){
            return first==null;
        }
        public int size(){
            return n;
        }
    }
    
    

    参考自:

    1. 知乎:https://www.zhihu.com/question/58926821
    2. 算法第四版(橙色)
    展开全文
  • 在生活中,可以发现在某些图中,如web底层结构、人际关系网,在图中可以发现高度聚集节点群的算法, 即寻找**“强连通分支Strongly Connected Components”算法。 强连通分支, 定义为图G的一个子集C,C中的任意两个...

    1、引入

    在生活中,可以发现在某些图中,如web底层结构、人际关系网,在图中可以发现高度聚集节点群的算法, 即寻找**“强连通分支Strongly Connected Components”算法。
    强连通分支, 定义为图G的一个子集C,C中的
    任意两个顶点v,w之间都有路径来回**,即(v,w)(w,v)都是C的路径,而且C是具有这样性质的最大子集
    下图是具有3个强连通分支的9顶点有向图
    一旦找到强连通分支, 可以据此对图的顶点进行分类, 并对图进行化简。
    在这里插入图片描述

    2、功能分析

    (1)转置概念

    在用深度优先搜索来发现强连通分支之前, 先熟悉一个概念: Transposition转置,一个有向图G的转置GT,定义为将图G的所有边的顶点交换次序,如将(v,w)转换为(w,v),可以观察到图和转置图在强连通分支的数量和划分上,是相同的。
    在这里插入图片描述

    (2)Kosaraju算法思路

    • 首先, 对图G调用DFS算法, 为每个顶点计算“结束时间”;
    • 然后, 将图G进行转置, 得到GT;
    • 再对GT调用DFS算法, 但在dfs函数中,对每个顶点的搜索循环里, 要以顶点的“结束时间”倒序的顺序来搜索
    • 最后, 深度优先森林中的每一棵树就是一 个强连通分支

    以一个例子为例,来阐述以上算法
    Step1:第一趟DFS
    在这里插入图片描述
    Step2:转置后第二趟DFS
    在这里插入图片描述
    Step3:深度优先森林结果
    在这里插入图片描述

    展开全文
  • Kosaraju算法 该算法旨在得到深度优先后续排列后的递归探索中,每次调用DFS的所有顶点都属于同一强联通分量。所以可以这么理解:当递归进入一个强联通分量时,把他锁死在这个强联通分量中(即不能从该强联通分量中...
  • Kosaraju 算法之前要先知道什么是强连通分量(SCC) 强连通分量 对于一个有向图顶点的子集 S ,如果在 S 内取两个顶点 u 和 v ,都能找到一条从 u 到 v 的路径,那么就称 S 是强连通的。 如果在强连通的顶点...
  • kosaraju算法通过两次DFS来实现,第一次对有向图D进行搜索,并标记顺序;第二次对有向图D的反图(见有向图的构造revers方法)进行搜索。从构造函数进入dfs函数之后,只要不退出则说明之后遍历的都是连通的。 class ...
  • 算法思想: 在一个有向图中,我们一定可以找到这样一个合理顺序,使得我们只需要按照这个顺序进行dfs遍历,那么每一次的dfs就可以使我们得到一个scc。合理顺序参考https://www.cnblogs.com/nullzx/p/6437926.html ...
  • 求强连通分量的Kosaraju算法和Tarjan算法的比较 一定义 在有向图中如果两个顶点vi,vj间有一条从vi到vj的有向路径同时还有一条从vj到vi的有向路径则称两个顶点强连通(strongly connected)如果有向图的每两个顶点都强...
  • 图论- 图的连通性- Kosaraju 算法.rar
  • Python:实现SCC的Kosaraju算法(附完整源码)
  • S. Rao KosarajuStrongly Connected Components In this tutorial, you ... Also, you will find working examples of Kosaraju's algorithm in C, C++, Java and Python.A strongly connected component is the portio
  • Kosaraju算法(强连通分量分解)

    千次阅读 多人点赞 2018-08-12 21:44:39
    对于一个有向图顶点的子集S,如果在S内取两个顶点u和v,都能找到一条从u到v的路径,那么就称S是强连通的。如果在强连通的顶点集合S中加入其他任意顶点集合后,它都不再是强连通的,那么就称S是原图的一个强连通分量...
  • Kosaraju算法用来解决有向图的连通性问题,算法的基本步骤: 1.对一幅有向图G,计算它的反向图GR的逆后序排列(一次dfs)。 2.按由1计算得到的顺序对G进行dfs操作,来访问所有未被标记的顶点。 3.在构造函数中,...
  • Kosaraju 算法

    2016-09-18 00:18:00
    在计算科学中,Kosaraju的算法(又称为–Sharir Kosaraju算法)是一个线性时间(linear time)算法找到的有向图的强连通分量。它利用了一个事实,逆图(与各边方向相同的图形反转, transpose graph)有相同的强连通...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,936
精华内容 774
关键字:

Kosaraju算法