Kosaraju 算法查找强连通分支

简介:

有向图 G = (V, E) 的一个强连通分支(SCC:Strongly Connected Components)是一个最大的顶点集合 C,C 是 V 的子集,对于 C 中的每一对顶点 u 和 v,有 u --> v 和 v --> u,亦即,顶点 u 和 v 是互相可达的。

实际上,强连通分支 SCC 将有向图分割为多个内部强连通的子图。如下图中,整个图不是强连通的,但可以被分割成 3 个强连通分支。

通过 Kosaraju 算法,可以在 O(V+E) 运行时间内找到所有的强连通分支。Kosaraju 算法是基于深度优先搜索(DFS),算法的描述如下:

  1. 创建一个空的栈 S,并做一次  DFS 遍历。在 DFS 遍历中,当在递归调用 DSF 访问邻接顶点时,将当前顶点压入栈中;
  2. 置换图(Transpose Graph);
  3. 从栈 S 中逐个弹出顶点 v,以 v 为源点进行 DFS 遍历。从 v 开始的 DFS 遍历将输出 v 关联的强连通分支。

例如,对于上面的图做第一次 DFS 遍历,然后反转图,则可理解为整个图中的边的方向均反转了。

下面是 Kosaraju 算法的 C# 实现。

复制代码
  1 using System;
  2 using System.Collections.Generic;
  3 using System.Linq;
  4 
  5 namespace GraphAlgorithmTesting
  6 {
  7   class Program
  8   {
  9     static void Main(string[] args)
 10     {
 11       Graph g = new Graph(5);
 12       g.AddEdge(1, 0, 11);
 13       g.AddEdge(0, 3, 13);
 14       g.AddEdge(2, 1, 10);
 15       g.AddEdge(3, 4, 12);
 16       g.AddEdge(0, 2, 4);
 17 
 18       Console.WriteLine();
 19       Console.WriteLine("Graph Vertex Count : {0}", g.VertexCount);
 20       Console.WriteLine("Graph Edge Count : {0}", g.EdgeCount);
 21       Console.WriteLine();
 22 
 23       List<List<int>> sccs = g.Kosaraju();
 24       foreach (var scc in sccs)
 25       {
 26         foreach (var vertex in scc)
 27         {
 28           Console.Write("{0} ", vertex);
 29         }
 30         Console.WriteLine();
 31       }
 32 
 33       Console.ReadKey();
 34     }
 35 
 36     class Edge
 37     {
 38       public Edge(int begin, int end, int weight)
 39       {
 40         this.Begin = begin;
 41         this.End = end;
 42         this.Weight = weight;
 43       }
 44 
 45       public int Begin { get; private set; }
 46       public int End { get; private set; }
 47       public int Weight { get; private set; }
 48 
 49       public override string ToString()
 50       {
 51         return string.Format(
 52           "Begin[{0}], End[{1}], Weight[{2}]",
 53           Begin, End, Weight);
 54       }
 55     }
 56 
 57     class Graph
 58     {
 59       private Dictionary<int, List<Edge>> _adjacentEdges
 60         = new Dictionary<int, List<Edge>>();
 61 
 62       public Graph(int vertexCount)
 63       {
 64         this.VertexCount = vertexCount;
 65       }
 66 
 67       public int VertexCount { get; private set; }
 68 
 69       public IEnumerable<int> Vertices { get { return _adjacentEdges.Keys; } }
 70 
 71       public IEnumerable<Edge> Edges
 72       {
 73         get { return _adjacentEdges.Values.SelectMany(e => e); }
 74       }
 75 
 76       public int EdgeCount { get { return this.Edges.Count(); } }
 77 
 78       public void AddEdge(int begin, int end, int weight)
 79       {
 80         if (!_adjacentEdges.ContainsKey(begin))
 81         {
 82           var edges = new List<Edge>();
 83           _adjacentEdges.Add(begin, edges);
 84         }
 85 
 86         _adjacentEdges[begin].Add(new Edge(begin, end, weight));
 87       }
 88 
 89       public List<List<int>> Kosaraju()
 90       {
 91         Stack<int> stack = new Stack<int>();
 92 
 93         // Mark all the vertices as not visited (For first DFS)
 94         bool[] visited = new bool[VertexCount];
 95         for (int i = 0; i < visited.Length; i++)
 96           visited[i] = false;
 97 
 98         // Fill vertices in stack according to their finishing times
 99         for (int i = 0; i < visited.Length; i++)
100           if (!visited[i])
101             FillOrder(i, visited, stack);
102 
103         // Create a reversed graph
104         Graph reversedGraph = Transpose();
105 
106         // Mark all the vertices as not visited (For second DFS)
107         for (int i = 0; i < visited.Length; i++)
108           visited[i] = false;
109 
110         List<List<int>> sccs = new List<List<int>>();
111 
112         // Now process all vertices in order defined by Stack
113         while (stack.Count > 0)
114         {
115           // Pop a vertex from stack
116           int v = stack.Pop();
117 
118           // Print Strongly connected component of the popped vertex
119           if (!visited[v])
120           {
121             List<int> scc = new List<int>();
122             reversedGraph.DFS(v, visited, scc);
123             sccs.Add(scc);
124           }
125         }
126 
127         return sccs;
128       }
129 
130       void DFS(int v, bool[] visited, List<int> scc)
131       {
132         visited[v] = true;
133         scc.Add(v);
134 
135         if (_adjacentEdges.ContainsKey(v))
136         {
137           foreach (var edge in _adjacentEdges[v])
138           {
139             if (!visited[edge.End])
140               DFS(edge.End, visited, scc);
141           }
142         }
143       }
144 
145       void FillOrder(int v, bool[] visited, Stack<int> stack)
146       {
147         // Mark the current node as visited and print it
148         visited[v] = true;
149 
150         // Recur for all the vertices adjacent to this vertex
151         if (_adjacentEdges.ContainsKey(v))
152         {
153           foreach (var edge in _adjacentEdges[v])
154           {
155             if (!visited[edge.End])
156               FillOrder(edge.End, visited, stack);
157           }
158         }
159 
160         // All vertices reachable from v are processed by now, 
161         // push v to Stack
162         stack.Push(v);
163       }
164 
165       Graph Transpose()
166       {
167         Graph g = new Graph(this.VertexCount);
168 
169         foreach (var edge in this.Edges)
170         {
171           g.AddEdge(edge.End, edge.Begin, edge.Weight);
172         }
173 
174         return g;
175       }
176     }
177   }
178 }
复制代码

输出结果如下:

参考资料







本文转自匠心十年博客园博客,原文链接:http://www.cnblogs.com/gaochundong/p/kosaraju_algorithm.html,如需转载请自行联系原作者

目录
相关文章
|
算法 Java C++
【洛谷算法题】P5709-Apples Prologue / 苹果和虫子【入门2分支结构】
【洛谷算法题】P5709-Apples Prologue / 苹果和虫子【入门2分支结构】
|
3月前
|
机器学习/深度学习 算法 数据挖掘
决策树算法大揭秘:Python让你秒懂分支逻辑,精准分类不再难
【9月更文挑战第12天】决策树算法作为机器学习领域的一颗明珠,凭借其直观易懂和强大的解释能力,在分类与回归任务中表现出色。相比传统统计方法,决策树通过简单的分支逻辑实现了数据的精准分类。本文将借助Python和scikit-learn库,以鸢尾花数据集为例,展示如何使用决策树进行分类,并探讨其优势与局限。通过构建一系列条件判断,决策树不仅模拟了人类决策过程,还确保了结果的可追溯性和可解释性。无论您是新手还是专家,都能轻松上手,享受机器学习的乐趣。
56 9
|
4月前
|
机器学习/深度学习 算法 数据挖掘
决策树算法大揭秘:Python让你秒懂分支逻辑,精准分类不再难
【8月更文挑战第2天】决策树算法以其直观性和解释性在机器学习领域中独具魅力,尤其擅长处理非线性关系。相较于复杂模型,决策树通过简单的分支逻辑实现数据分类,易于理解和应用。本示例通过Python的scikit-learn库演示了使用决策树对鸢尾花数据集进行分类的过程,并计算了预测准确性。虽然决策树优势明显,但也存在过拟合等问题。即便如此,无论是初学者还是专家都能借助决策树的力量提升数据分析能力。
53 4
|
7月前
|
算法 JavaScript
算法(分治、贪心、dp、回溯、分支限界)总结
算法(分治、贪心、dp、回溯、分支限界)总结
|
7月前
|
算法 测试技术 编译器
【算法】优先队列式分支限界法,以01背包问题为例
📑 例题:01背包问题 题目链接:采药-洛谷 当洛谷上不让下载测试用例,可以试试:采药-ACWing
924 0
|
存储 算法 搜索推荐
动态规划、回溯搜索、分治算法、分支定界算法
动态规划、回溯搜索、分治算法、分支定界算法
100 0
|
算法 Java C++
【洛谷算法题】P2433-小学数学 N 合一【入门2分支结构】
【洛谷算法题】P2433-小学数学 N 合一【入门2分支结构】
|
算法
文本主题相关的主要算法分支与思考
文本主题相关的主要算法分支与思考
70 0
|
存储 算法 Python
秒懂算法 | 排列树模型——旅行商问题的分支限界法
介绍旅行商问题的队列式分支限界法、优先队列式分支限界法及其改进、改进算法的Python编程实战。
808 1
秒懂算法 | 排列树模型——旅行商问题的分支限界法
|
算法 C语言
C语言基础(有关三角形面积,阶乘算法,sqrt,pow函数,海伦公式,gets,getchar,scanf的区别,字符转换,增长率计算,的分支和循环的结构程序设计)
C语言基础(有关三角形面积,阶乘算法,sqrt,pow函数,海伦公式,gets,getchar,scanf的区别,字符转换,增长率计算,的分支和循环的结构程序设计)