一.算法简介

Tarjan 算法一种由Robert Tarjan提出的求解有向图强连通分量的算法,它能做到线性时间的复杂度。

我们定义:

如果两个顶点可以相互通达,则称两个顶点强连通(strongly connected)。如果有向图G的每两个顶点都强连通,称G是一个强连通图。有向图的极大强连通子图,称为强连通分量(strongly connected components)。

例如:在上图中,{1 , 2 , 3 , 4 } , { 5 } ,  { 6 } 三个区域可以相互连通,称为这个图的强连通分量。

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

再Tarjan算法中,有如下定义。

DFN[ i ] : 在DFS中该节点被搜索的次序(时间戳)

LOW[ i ] : 为i或i的子树能够追溯到的最早的栈中节点的次序号

当DFN[ i ]==LOW[ i ]时,为i或i的子树可以构成一个强连通分量。

二.算法图示

以1为Tarjan 算法的起始点,如图

顺次DFS搜到节点6

回溯时发现LOW[ 5 ]==DFN[ 5 ] ,  LOW[ 6 ]==DFN[ 6 ] ,则{ 5 } , { 6 } 为两个强连通分量。回溯至3节点,拓展节点4.

拓展节点1 , 发现1再栈中更新LOW[ 4 ],LOW[ 3 ] 的值为1

回溯节点1,拓展节点2

自此,Tarjan Algorithm 结束,{1 , 2 , 3 , 4 } , { 5 } ,  { 6 } 为图中的三个强连通分量。

不难发现,Tarjan Algorithm 的时间复杂度为O(E+V).

学习处

模板:

 void dfs(int u)
{
times++;//记录dfn顺序
dfn[u]=times;//赋值
low[u]=times;//先赋初值
vis[u]=true;//vis[i]用来判断i是否搜索过;
insta[u]=true;//表示是否在栈中,true为在栈中;
stack[top]=u;//栈顶
top++;
for(int i=head[u];i!=-;i=edge[i].next)// 以建图顺序枚举此点所连的边
{
int v=edge[i].to;//搜索到的点
if(!vis[v])//如果未搜索过即未入栈
{
dfs(v);//继续以此点进行深搜
low[u]=min(low[u],low[v]);//更新low值,此边为树枝边所以比较u此时的
} // low值(未更新时就是其dfn值)和v的low值
else
if(insta[v]==true)//如果搜索过且在栈中,说明此边为后向边或栈中横叉边
{
low[u]=min(low[u],dfn[v]);//更新low值,比较u此时的low值和v的dfn值
}
} if(low[u]==dfn[u])//相等说明找到一个强连通分量
{
while(top>&&stack[top]!=u)//开始退栈一直退到 u为止
{
top--;
insta[stack[top]]=false;
}
}
}

例题:

poj2186 - Popular Cows

题目大意:题目大意是:在一个牧群中,有N个奶牛,给定M对关系(A,B)表示A仰慕B,而且仰慕关系有传递性,问被所有奶牛(除了自己)仰慕的奶牛个数

解题思路:找出所有的连通分量,如果只有一个连通分量的出度为0,那么输出那个连通分量的点的个数即可,如果不唯一就输出0
因为连通分量的出度有两个的话,那么肯定至少存在一头牛不仰慕另外一头牛,所以我们至少保证要出度为0的连通分量唯一

解题思路:

1、用Tarjan求双连通分量然后缩成点。这些点会形成一棵树。

2、求树上的节点有多少个出度为零,如果有一个就输出那个点里包含的所有点(因为是缩点出来的树)。

注意:

1、给出的图会有不连通的可能,如果那样肯定输出零。因为不连通肯定不会有所有其他牛认为某只牛很牛的情况出现。

2、如果缩点后有多个出度为零的点,那么输出零。因为这样图虽然联通了,但是还是不会出现所有其他牛认为某只牛很牛的情况(自己画一下就知道啦)。

求强连通分量主要是为了简化图的构造,如果分量外的一个点能到达分量内的其中一个点,那么它必定能到达分量内的所有点,所以某种程度上,强连通分量可以简化成一个点。

#include <stdio.h>
#include <string.h>
const int MAXN = ;
const int MAXM = ;
struct node
{
int to,next;
} edge[MAXM];
int n,m,head[MAXN],dfn[MAXN],low[MAXN],stack1[MAXN],num[MAXN],du[MAXN],vis[MAXN],cnt,time,top,cut;
void init()
{
memset(dfn,,sizeof(dfn));
memset(low,,sizeof(low));
memset(head,-,sizeof(head));
memset(vis,,sizeof(vis));
memset(num,,sizeof(num));
memset(du,,sizeof(du));
cnt=;
time=;
top=;
cut=;
}
void addedge(int u,int v)
{
edge[cnt].to=v;
edge[cnt].next=head[u];
head[u]=cnt;
cnt++;
}
int min(int a,int b)
{
if(a>b)a=b;
return a;
}
void dfs(int u,int fa)
{
dfn[u]=time;
low[u]=time;
time++;
vis[u]=;
stack1[top]=u;
top++;
for(int i=head[u]; i!=-; i=edge[i].next)
{
int v=edge[i].to;
if(!vis[v])
{
dfs(v,u);
low[u]=min(low[u],low[v]);
}
else if(vis[v])
{
low[u]=min(low[u],dfn[v]);
}
}
if(low[u]==dfn[u])
{
cut++;
while(top>&&stack1[top]!=u)
{
top--;
vis[stack1[top]]=;
num[stack1[top]]=cut;
}
}
}
int main()
{
int i,u,v;
while(scanf("%d%d",&n,&m)!=EOF)
{
init();
for(i=; i<m; i++)
{
scanf("%d%d",&u,&v);
addedge(u,v);
}
for(int i=; i<=n; i++)
{
if(!vis[i])
{
dfs(i,);
}
}
for(i=; i<=n; i++)
{
for(int j=head[i]; j!=-; j=edge[j].next)
{
if(num[i]!=num[edge[j].to])
{
du[num[i]]++;
}
}
}
int sum=,x;
for(i=; i<=cut; i++)
{
if(!du[i])
{
sum++;
x=i;
}
}
if(sum==)
{
sum=;
for(i=; i<=n; i++)
{
if(num[i]==x)
{
sum++;
}
}
printf("%d\n",sum);
}
else
{
puts("");
}
}
return ;
}

tarjan算法,一个关于 图的联通性的神奇算法的更多相关文章

  1. 推荐一个算法编程学习中文社区-51NOD【算法分级,支持多语言,可在线编译】

    最近偶尔发现一个算法编程学习的论坛,刚开始有点好奇,也只是注册了一下.最近有时间好好研究了一下,的确非常赞,所以推荐给大家.功能和介绍看下面介绍吧.首页的标题很给劲,很纯粹的Coding社区....虽 ...

  2. 笔试题&amp;面试题:设计一个复杂度为n的算法找到单向链表倒数第m个元素

    设计一个复杂度为n的算法找到单向链表倒数第m个元素.最后一个元素假定是倒数第0个. 提示:双指针查找 相对于双向链表来说,单向链表仅仅能从头到尾依次訪问链表的各个节点,所以假设要找链表的倒数第m个元素 ...

  3. 一个快速、高效的Levenshtein算法实现——代码实现

    在网上看到一篇博客讲解Levenshtein的计算,大部分内容都挺好的,只是在一些细节上不够好,看了很长时间才明白.我对其中的算法描述做了一个简单的修改.原文的链接是:一个快速.高效的Levensht ...

  4. C++神奇算法库——#include<algorithm>

    算法(Algorithm)为一个计算的具体步骤,常用于计算.数据处理和自动推理.C++ 算法库(Algorithms library)为 C++ 程序提供了大量可以用来对容器及其它序列进行算法操作的函 ...

  5. 一个简单文本分类任务-EM算法-R语言

    一.问题介绍 概率分布模型中,有时只含有可观测变量,如单硬币投掷模型,对于每个测试样例,硬币最终是正面还是反面是可以观测的.而有时还含有不可观测变量,如三硬币投掷模型.问题这样描述,首先投掷硬币A,如 ...

  6. 如何用 js 实现一个类似微信红包的随机算法

    如何用 js 实现一个类似微信红包的随机算法 js, 微信红包, 随机算法 "use strict"; /** * * @author xgqfrms * @license MIT ...

  7. 开源一个比雪花算法更好用的ID生成算法(雪花漂移)

    比雪花算法更好用的ID生成算法(单机或分布式唯一ID) 转载及版权声明 本人从未在博客园之外的网站,发表过本算法长文,其它网站所现文章,均属他人拷贝之作. 所有拷贝之作,均须保留项目开源链接,否则禁止 ...

  8. 蚁群算法简介(part 1:蚁群算法之绪论)

    群算法是Marco Dorigo在1992年提出的一种优化算法,该算法受到蚂蚁搜索食物时对路径的选择策略的启示.蚁群算法作为群体智能算法的一种利用分布式的种群搜索策略来寻找目标函数的最优解.蚁群算法与 ...

  9. EM算法(2):GMM训练算法

    目录 EM算法(1):K-means 算法 EM算法(2):GMM训练算法 EM算法(3):EM算法运用 EM算法(4):EM算法证明 EM算法(2):GMM训练算法 1. 简介 GMM模型全称为Ga ...

随机推荐

  1. 【270】IDL处理GeoTIFF数据

    参考:将原GeoTIFF数据的投影坐标信息赋值到新创建的文件上 pro tiff_projection ;启动ENVI e = ENVI(/HEADLESS) ;打开文件 file = 'D:\01- ...

  2. hibernate学习笔记(5)在数据库中存取图片

    如何从数据库读取存入的图片,即Blob(二进制)数据. 先从数据库读取对象. 再从获取的对象中得到blob对象. 通过blob对象的getBinaryStream()方法获取input输出流. 之后通 ...

  3. hibernate学习笔记(2)持久化类测试

    持久化类的创建: 创建一个共有的不带参数的构造方法: public void Students(){ } 创建一个带参数的构造方法: (快捷键创建) 生成get,set方法: *可以不用此方法创建持久 ...

  4. C++——const

    参考:https://www.cnblogs.com/Forever-Kenlen-Ja/p/3776991.html ; //修饰变量,a不能该内容(更改为其他的值) int* const p = ...

  5. Blender 工具使用——显示键盘和鼠标操作

    Blender 工具使用--显示键盘和鼠标操作 Blender自己本身就带有显示按键和鼠标的功能,就是3D View: Screencast Keys插件. 打开 File(文件) -> Use ...

  6. 高性能MySQL笔记-第5章Indexing for High Performance-001B-Tree indexes(B+Tree)

    一. 1.什么是B-Tree indexes? The general idea of a B-Tree is that all the values are stored in order, and ...

  7. 【java】小技巧和注意事项

    1.字符串反向比较  “abc”.equals(sting) 2.文档注释 /** *注释内容 */ 3.

  8. 利用excel制作二维码

    1 将想要通过扫描二维码访问的目标网址放入A1单位格 2 在excel 编辑区右击选择“自定义功能区” 3 然后将“开发者工具”选上 4 点击菜单栏的“开发者工具---插入--->其他控件” 5 ...

  9. MySQL - ODBC安装错误问题!

    MySQL的ODBC安装时候可能会出错,主要原因是缺少VC支持库,需要2010版本的VC支持库!!X86和X64分别对应MySQL对应的ODBC,不能安装一个两个都搞定,如果需要安装两个ODBC驱动, ...

  10. MapReduce Kmeans算法含测试数据

    使用时,需要修改K值,args值 运行流程: 先初始化中心点->map中和距离最近的中心点生成一对传入reduce->reduce中把相同key值的存到一起->更新中心点,计算和上一 ...