本文就是做个代码分析,顺便说下理解。

一、预备知识:

需要知道什么是: 回边、前向边、交叉边

二、上代码:

#include<algorithm>
#define NIL -1using namespace std;class Graph
{int n;list<int>*adj;void SCCUtil(int u,int disc[],int low[],int &time,bool stackMember[],stack<int>&stk);
public:Graph(int _n){   n = _n; adj = new list<int>[n]; }~Graph(){ delete [] adj; }void addEdge(int v, int w){  adj[v].push_back(w); }void SCC();
};

list动态数组存邻接表,SCC()是对外开放的打印图的强连通分量的方法,它调用SCCUtil()来查找强连通分量,完成始化和调用任务。

SCC():

void Graph::SCC()
{ int *disc = new int[n]; int *low = new int[n]; bool *stackMember = new bool[n]; stack<int> stk; int time = 0;for (int i = 0; i < n; i++) { disc[i] = NIL; low[i] = NIL; stackMember[i] = false; } for (int i = 0; i < n; i++) if (disc[i] == NIL)      //disc也充当visited数组的作用,标记是否已访问 SCCUtil(i, disc, low,time, stackMember,stk);
}

SCCUtil():
SCCUtil形参说明:
u: 要访问的下一个节点
disc[] :disc[u]存的顶点u在深度优先遍历中的次序(存的是被发现的时间)
low[]:low[u]存的是u节点通过其子孙或者它自己的回边能够到达的次序最小的祖先的次序
time: 时间戳,传的是引用
stackMember[]:标记进栈的顶点有哪些,栈内只有当前节点及其祖先
stk:栈,用于存放当前节点及其祖先,当找到一个强连通分量时,会将栈顶元素退出,
一直到将这个强连通分量退完

(注:当low[u] = disc[u]的时候,说明当前层处理的顶点u是一个强连通分量的根,因为算法一开始令low[u]=disc[u]=++time,即u顶点被访问的时间的为 比它父顶点time+1,而它能到达的次序最小的顶点也为这个,所以,如果后面发现它没有回边到自己的祖先,那它肯定是一个强连通分量的根(可能自成强连通分量,当它的子女的被先发现是强连分量的时候会先出栈和输出)=

void Graph::SCCUtil(int u, int disc[], int low[], int &time,bool stackMember[], stack<int>&stk)
{disc[u] = low[u] = ++time;   stk.push(u);stackMember[u] = true;list<int>::iterator it;for(it = adj[u].begin(); it != adj[u].end(); it++){int v = *it;if( disc[v] == -1)      //未访问过{SCCUtil(v,disc,low,time,stackMember,stk);low[u] = min(low[u], low[v]);      }else if(stackMember[v] == true)      //如果顶点v是u的祖先{low[u] = min(low[u], disc[v]);}}int w = 0;if( low[u] == disc[u])   //找到了一个强连通分量, {      //u为该强连通分量所在DFS子树的根 while(stk.top() != u)  //只能把以u为根打出来,不能把整个栈弹出 {w = (int) stk.top();cout<< w << " ";stackMember[w] = false; stk.pop();}w = (int) stk.top();  // u cout<< w << " "<<endl;stackMember[w] = false; stk.pop();}
}

算法的过程:
从传进来的第一个顶点u开始,先记录的它被发现的时间(次序)disc[u],把low[u]也设为这个,然后把它进栈,再遍历的它的邻居,如果他的邻居没有被访问过,那就递归访问它,它的邻居就是它的子女,子女可能也会找到它自己的子女,所以顶点u可能会有很多子孙,如果它的子女发现了一个访问过的顶点v,要先看下它是不自己的祖先,是的话low[u] = min(low[u],disc[v])会让它的low[u]的值等于disc[v](其实我觉得那句写成low[u] = disc[v]完全没问题,刚测试了,答案是一样的);不是的话忽略。假如递归到了叶子节点(它没有指向子女的边了),如果它有指向祖先的边(不是指向祖先的边忽略),那它可以更新low值,如果没有,在结束边遍历后它的low值会等于disc值,所以它自成一个连通分量,然后打印输出,退栈。之后程序回溯到这个叶子子女的双亲这一层,检查下它的子女的low值有没有比自己小,有就更新,没有就算了,然后继续遍历邻居看看自己有没有回边(指祖先的边)或者还有邻居没被访问。遍历完邻居后,看下low值等不等于disc值,等于,自己就是一个强连通分量的根,然后把这个强连同分量退掉,然后回溯balabalalalala…

看看维基上的动画吧,做的真好
顶点边上数字是那个顶点udisc[u]low[u]

ps:想暂停可以下下来,用windows medie player打开。

三、Tarjan原理理解

为什么low[u]==disc[u]u就是某个一个强连通分量的根呢?
因为深度优先搜索的时候,是以深度优先 的只有处理完了一个分支,它才会处理下一个分支,所以,一开始先假设所有的顶点都是自成一个单独的强连通分量,除非它u自己有回边到自己的祖先或者它u的子孙有回边能到它u的祖先以形成一个圈,否则这个顶点u及其子孙都和它u的祖先不在一个强连通分量里,而这个时候它的low没有被更新过。所以low[u] = disc[u]u肯定是某个连同分量的根。

四、全部代码和测试:

#include<iostream>
#include<list>
#include<stack>
#include<algorithm>
#define NIL -1using namespace std;class Graph
{int n;list<int>*adj;void SCCUtil(int u,int disc[],int low[],int &time,bool stackMember[],stack<int>&stk);
public:Graph(int _n){   n = _n; adj = new list<int>[n]; }~Graph(){ delete [] adj; }void addEdge(int v, int w);void SCC();
};
void Graph::addEdge(int v, int w)
{adj[v].push_back(w);
}
/************SCCUtil形参说明:
u ---> 要访问的下一个节点
disc ---->disc[u]存的顶点u在深度优先遍历中的次序(存的是被发现的时间)
low ----->low[u]存的是u节点通过其子孙或者它自己的回边能够到达的次序最小的祖先的次序
time ----> 时间戳,传的是引用
stackMember ----->标记进栈的顶点有哪些,栈内只有当前节点及其祖先
stk ------->栈,用于存放当前节点及其祖先,当找到一个强连通分量时,会栈顶元素退出,一直到将这个强连通分量退完
*/
void Graph::SCCUtil(int u, int disc[], int low[], int &time,bool stackMember[], stack<int>&stk)
{disc[u] = low[u] = ++time;   stk.push(u);stackMember[u] = true;   // list<int>::iterator it;for(it = adj[u].begin(); it != adj[u].end(); it++){int v = *it;if( disc[v] == -1){SCCUtil(v,disc,low,time,stackMember,stk);low[u] = min(low[u], low[v]);        }else if(stackMember[v] == true)          //v是u的祖先{low[u] = min(low[u], disc[v]);        //改成low[u] = disc[v]也可以}}int w = 0;if( low[u] == disc[u])   //找到了一个强连通分量, {      //u为该强连通分量所在DFS子树的根 while(stk.top() != u)  //只能把以u为根打出来,不能把整个栈弹出 {w = (int) stk.top();cout<< w << " ";stackMember[w] = false; stk.pop();}w = (int) stk.top();  // u cout<< w << " "<<endl;stackMember[w] = false; stk.pop();}
}   void Graph::SCC()
{ int *disc = new int[n]; int *low = new int[n]; bool *stackMember = new bool[n]; stack<int> stk; int time = 0;for (int i = 0; i < n; i++) { disc[i] = NIL; low[i] = NIL; stackMember[i] = false; } for (int i = 0; i < n; i++) if (disc[i] == NIL)      //disc也充当visited数组的作用,标记是否已访问 SCCUtil(i, disc, low,time, stackMember,stk);
}
int main()
{ cout << "\nSCCs in first graph \n"; Graph g1(5); g1.addEdge(1, 0); g1.addEdge(0, 2); g1.addEdge(2, 1); g1.addEdge(0, 3); g1.addEdge(3, 4); g1.SCC(); cout << "\nSCCs in second graph \n"; Graph g2(4); g2.addEdge(0, 1); g2.addEdge(1, 2); g2.addEdge(2, 3); g2.SCC(); cout << "\nSCCs in third graph \n"; Graph g3(7); g3.addEdge(0, 1); g3.addEdge(1, 2); g3.addEdge(2, 0); g3.addEdge(1, 3); g3.addEdge(1, 4); g3.addEdge(1, 6); g3.addEdge(3, 5); g3.addEdge(4, 5); g3.SCC(); cout << "\nSCCs in fourth graph \n"; Graph g4(11); g4.addEdge(0,1);g4.addEdge(0,3); g4.addEdge(1,2);g4.addEdge(1,4); g4.addEdge(2,0);g4.addEdge(2,6); g4.addEdge(3,2); g4.addEdge(4,5);g4.addEdge(4,6); g4.addEdge(5,6);g4.addEdge(5,7);g4.addEdge(5,8);g4.addEdge(5,9); g4.addEdge(6,4); g4.addEdge(7,9); g4.addEdge(8,9); g4.addEdge(9,8); g4.SCC(); cout << "\nSCCs in fifth graph \n"; Graph g5(5); g5.addEdge(0,1); g5.addEdge(1,2); g5.addEdge(2,3); g5.addEdge(2,4); g5.addEdge(3,0); g5.addEdge(4,2); g5.SCC(); return 0;
}

有向图的强连通分量--Tarjan算法---代码分析相关推荐

  1. 强连通分量(Tarjan算法)和缩点

    强连通分量(Tarjan算法)和缩点 一些定义 给定一张有向图,对于图中任意两个节点 xxx 和 yyy ,存在从 xxx 到 yyy 的路径,也存在从 yyy 到 xxx 的路径,则称该有向图为强连 ...

  2. 求无向图的连通分量或有向图的强连通分量—tarjan()ccf高速公路

    概念定义: 在图论中,连通图基于连通的概念. 1. 连通(无向图): 若顶点Vi能通过路径到达Vj,那么称为Vi和Vj是连通的 对无向图:若从顶点Vi到顶点Vj有路径相连(当然从j到i也一定有路径), ...

  3. 强连通分量(Tarjan算法) 图解

    强连通分量(Tarjan算法) 前言 第一件事:没事不要while(m–),会带来不幸 第二件事:看博客先看看评论,如果博主他写错了的话- 简介 先讲几个定义 强连通:两个顶点 uuu,vvv 可以相 ...

  4. 有向图强连通分量tarjan算法

    转自:http://www.byvoid.com/blog/scc-tarjan/ http://blog.csdn.net/geniusluzh/article/details/6601514 在有 ...

  5. 图之强连通、强连通图、强连通分量 Tarjan算法

    一.解释 在有向图G中,如果两个顶点间至少存在一条互相可达路径,称两个顶点强连通(strongly connected).如果有向图G的每两个顶点都强连通,称G是一个强连通图.非强连通图有向图的极大强 ...

  6. 打印有向图的强连通分量-----kosaraju算法(最简单的实现)

    一.kosaraju算法步骤: 1.首先对图G进行一次DFS,记录每个顶点完成的顺序(DFS树/林的叶子节点先完成,然后回溯到它双亲这一层,它个双亲递归遍历完自己的邻居并在这些递归完成回溯到这层后,它 ...

  7. 强连通分量——tarjan算法缩点

    一. 什么是强连通分量? 强连通分量:在有向图G中,如果两个顶点u,v间(u->v)有一条从u到v的有向路径,同时还有一条从v到u的有向路径,则称两个顶点强连通(strongly connect ...

  8. 有向图的强连通分量——Tarjan

    在同一个DFS树中分离不同的强连通分量SCC; 考虑一个强连通分量C,设第一个被发现的点是 x,希望在 x 访问完时立刻输出 C,这样就可以实现 在同一个DFS树中分离不同的强连通分量了. 问题就转换 ...

  9. Tarjan 求有向图的强连通分量

    Tarjan 算法与有向图的连通性 Tarjan 算法是基于对图进行深度优先搜索的算法,每个强连通分量为搜索树中的一棵子树.搜索时,把当前搜索树中未处理的节点加入一个栈,回溯时可以判断栈顶到栈中的节点 ...

最新文章

  1. LeetCode OJ:Remove Element(移除元素)
  2. el-autocomplete 使用相关问题
  3. 【机器学习】创建自己的电影推荐系统
  4. Python基础day03【字符串(定义、输入输出、常用方法)、列表(定义、基本使用、增删改查、嵌套)、元组】
  5. bash的配置文件以及加载的顺序
  6. Python3网络爬虫(四): 登录
  7. oracledatabase11gr2怎么打开_win10 安装oracle 11gR2_database(内附下载地址)
  8. java methodtype_java基于MethodHandle调用方法
  9. Linux系统编程(八)线程
  10. genewise运行过程中遇到的错误及其解决方法
  11. Lotus Notes 中导航的键盘快捷方式
  12. 30岁前,一定要完成哪些人生规划?
  13. 使用 openocd 调试 STM32F103
  14. 造梦无双服务器维护12月17日,《造梦无双》12月31日V0.82版本更新公告:迎战北王,寻斗天君...
  15. 计算机工程制图标注,工程制图与计算机辅助设计:第3章 组合体视图即尺寸标注...
  16. Python之条件竞争
  17. 99种用Racket说I love you的方式
  18. 千古以來:卍佛一心)悟道真机(转载)
  19. Tableau实用小技巧之——双轴图表设置同步轴
  20. 遗传基因科普(8):奇妙的双螺旋结构

热门文章

  1. python在哪里写代码-在哪里编写python代码
  2. python课程网课-有没有简单易懂的入门级Python辅导书或网络课程?
  3. python培训价目表-参加python培训要多少钱?
  4. php和python-Python与PHP:有什么区别?
  5. python工资高还是java-python工资高还是java
  6. 简单python脚本实例-终于晓得python入门脚本实例
  7. python需要下载哪些软件-学python下载什么软件开发
  8. python是什么意思啊-星号*在Python中是什么意思?
  9. python就业方向-Python的5大就业方向,薪资诱人前景好!
  10. 10.java之父被B站学习者下载达7000万次的Java视频教程你还没有看过知乎