太久不做题,发现很多学过的算法又通通不记得了,或许因为当时既没有深刻理解,也没有及时总结。最近开始复习图论,说是复习,不如说重新学习更加恰当。算法是进入大学紧跟着c语言接触到的第二项“技术”,虽然在竞赛中就好像最终创业失败一样不得善终,总还是希望不辜负那段年轻的时光,也不辜负难得一份坚持了这么久的兴趣。

    我的图论学习也不是从Tarjan算法开始的,而是最近在看双连通分量相关问题的时候又想起了当年学习Tarjan时的一些好笑经历。当时学习完求强连通分量的Tarjan解法,又发现双连通分量也是用这个算法,以当时对其浅薄的认识还以为是同一个算法却不知为何可以解决不同的问题。后来才知道包括解决LCA问题在内的这些算法,都是由图灵奖获得者Robert Tarjan这位计算机泰斗所创,可惜他没有创造出更多更艺术的算法名字(==)。为了能够保留我再次学习算法的这些浅薄认识,也为了表达我对Tarjan的膜拜,我决定从Tarjan算法开始我的学习总结。
  1. 双连通分量

         点连通度:直观来说,就是使一个连通图成为非连通图所需要去掉的最小点数。

         边连通度:同理就是使一个连通图成为非连通图所需要去掉的最小边数。
割点:去掉这个点及所连的边,原图不连通
割边(桥):去掉这条边原图不连通
双连通图:一个无向连通图的点连通度大于1(不存在割点),那么该图就是点双连通图,如果边双连通度大于1,该图就是边双连通图(不存在桥,或者说图的任意两点间都有两条不相交的路径,如果有相交的边,显然这条边就是桥)。
双连通分量:是一个图的极大双连通子图。
双连通还是有很大的实际意义的,这一点我觉得也是图论有意思且牛叉的地方所在。比如一个通信网络,某一个通信点故障(割点),或一条通信线路故障(桥)会不会影响整个网络的通信。
    在一个图中,显然是由割点将各个双连通分量连接起来的(要么它们根本不连通),要求双连通分量,关键就是要把割点找到。
    什么样的点是割点呢?有下面两个图(对于一个无向图进行一次dfs后的搜索树):
               
在这两个图中U都是割点,因为对于第一个图,u不为树根,且u的儿子以及它们的后代没有指向u的祖先的后向边(有一个后代成立便成立,因为如上图即使v2有一条后向边,u仍为割点,因为它连接了v1及其后代)。
对于第二个图,u为树根,并且u有两个及以上的儿子,显然u将以其儿子们为根的子树分隔开来。
     为了判断一个点是不是割点,显然需要了解它的儿子与它的祖先的关系,为此,引入一个Low[]数组,来标记一个点能追溯到的最早的祖先标号(标号dfn[]由dfs获得),由第一个图知道,当dfn[u]<=low[v1]时,就能判断u是一个割点。
     low[u]的计算过程如下:
     a. dfs第一次搜索到u节点时,low[u] = dfn[u];
     b. u的儿子s已经被访问过(检查是不是指向祖先的后向边),low[u] = Min{low[u],dfn[s]};
     c. 检查u的未被访问的儿子们s,low[u] = Min{low[u],low[s]}。
 
     同时,判断桥也类似,设一条无向边(u,s),它为桥的条件是dfn[u]<low[s],注意这里不能取等号,我觉得可以简单理解为在下图的情况下,u仍是割点,(u,s)却不是桥。

          怎么求割点和桥已经比较清晰了,模板用题目测过再拿过来吧。
    【题目1】用POJ 2117测了一下求割点的算法,这个题目大意是要找去掉哪个点可以使原图分成最多的连通块,是一个比较简单的求割点的题目,核心代码如下(我又很无力地用了vector):
void DFS(int x){dfn[x] = low[x] = cnt++;int len = mp[x].size();for (int i = 0; i < len; i++){if (dfn[mp[x][i]] == -1){if (x == root)son ++;DFS(mp[x][i], x);low[x] = min(low[x],low[mp[x][i]]);if (x == root && son > 1 || x != root && dfn[x] <= low[mp[x][i]])cut[x] ++;}else{low[x] = min(low[x],dfn[mp[x][i]]);}}
}

  求解点双连通分量:会求割点和桥,求双连通分量只需要再增加一个stack即可,dfs时每访问一条边就将这条边加入stack,当出现割点u(dfn[u]<=low[v])的时候开始退栈,退到边(u,v)为止,退出栈的这些边和点就构成了一个双连通分支。(stack里也可以存点,但是存点要考虑割点会存在多个连通分量这件事,退栈的时候要小心,存边的时候只要把边的两头节点都放在一个连通分量里就ok了)

求边双连通分量:目前没有实际做过,通用的解法是先求出桥,原图删除桥之后就变成了一个一个的连通分量了。

ps.重边的问题:若两点之间有重边,显然这也组成了一个边双连通,在DFS时对重边要特殊处理一下(例如做一个与父节点之间的标记),当然有的问题要求重边也算桥(比如poj 3177)总之要注意是否有重边,重边该怎样处理。

【题目2】poj 2942,Knights of ther Round Table,是一道非常综合的图论题目,核心内容也是双连通分量。这道题目的大意是,n个人,有些人互相憎恨,要剔除一些人使剩下的人围成一个圈,满足:圈中有奇数个人;相邻的人不互相憎恨。注意同一个人可以参加多个会议。

   1. 首先,求出原图的补图(由憎恨图变成和谐图,边两端的点表示可以相邻)

   2. 对补图求点双连通分量,一个双连通分量里若存在奇圈,那么这个连通分量里的点就都可以参加会议了。因为这个分量里的点会几个一组分布在一个奇圈上,不知道怎么证明,只画了一些图。(比如原来的点分布在一个偶圈上,内部有一条边跟偶数条圈边形成一个奇圈,显然这条边跟剩下的偶数条边也形成了奇圈)相反,双连通分量里若不存在奇圈,那么这个连通分量里的点就都不能参加会议了。(注意点双连通分量里一个点可能存在多个双连通分量中,所以在一个双连通分量的点被删去在另一个双连通分量可能不需要被删去)

  3. 判断奇圈用到交叉染色的方法,对一条边的两个点染上不同的颜色,如果能够染成功,就说明没有奇圈。(判断二分图的方法,一个图是二分图当且仅当没有奇圈)

View Code

#include<cstdio>
#include<cstring>
#include<vector>
#include<algorithm>
using namespace std;
const int N = 1005;vector<int>mp[N];
vector<int>cut[N];
int G[N][N],low[N],dfn[N],stack[N],mark[N],color[N];
int m,n,cnt,stack_ptr,cut_num,flag;
void DFS(int x, int father)
{low[x] = dfn[x] = ++cnt;stack[stack_ptr++] = x;int len = mp[x].size();for (int i = 0; i < len; i++){int t = mp[x][i];if (!dfn[t]){DFS(t,x);low[x] = min(low[x], low[t]);if (dfn[x] <= low[t]) {cut[cut_num].push_back(x);        while (stack[stack_ptr-1] != t)cut[cut_num].push_back(stack[--stack_ptr]);cut[cut_num++].push_back(stack[--stack_ptr]);}}else if (t != father)low[x] = min(low[x], dfn[t]);}
}
void check(int x, int d, int col,int fa)
{color[x] = col;int len = mp[x].size();for (int i = 0; i < len; i++){int t = mp[x][i];if (t == fa) continue;vector<int>::iterator it = find(cut[d].begin(),cut[d].end(),t);if (it != cut[d].end())if (color[t] == 0)check(t,d,-col,x);else if (color[t] == col)flag = 1;}
}
int Tarjan()
{memset(low,0,sizeof(low));memset(dfn,0,sizeof(dfn));memset(cut,0,sizeof(cut));cnt = stack_ptr = cut_num = 0;for (int i = 1; i <= n; i++)if (!dfn[i])DFS(i,-1);memset(mark,0,sizeof(mark));for (int i = 0; i < cut_num; i++){memset(color,0,sizeof(color));int len = cut[i].size();flag = 0;if(len >= 3){check(cut[i][0],i,1,-1);if (flag)for (int j = 0; j < len; j++)mark[cut[i][j]] = 1;}}int ans = 0;for (int i = 1; i <= n; i++)if (!mark[i])ans++;return ans;
}
int main()
{while (scanf("%d%d",&n,&m) && n+m){int a,b;for (int i = 0; i <= n; i++){mp[i].clear();cut[i].clear();}memset(G,0,sizeof(G));while (m--){scanf("%d%d",&a,&b);G[a][b] = G[b][a] = 1;}for (int i = 1; i <= n; i++)for (int j = 1; j <= n; j++)if (i!=j && G[i][j]==0)mp[i].push_back(j);printf("%d\n",Tarjan());}return 0;
}

转载于:https://www.cnblogs.com/swcandy/archive/2013/04/09/3011475.html

Tarjan算法学习1-双连通相关推荐

  1. 算法学习-单调双端队列

    文章目录 基础知识 算法模板 相关题目 239.滑动窗口最大值 1438.绝对差不超过限制的最长连续子数组 862.和至少为K的最短子数组 1425.带限制的子序列和 1499.满足不等式的最大值 2 ...

  2. 『Tarjan算法 无向图的双联通分量』

    无向图的双连通分量 定义:若一张无向连通图不存在割点,则称它为"点双连通图".若一张无向连通图不存在割边,则称它为"边双连通图". 无向图图的极大点双连通子图被 ...

  3. Tarjan算法学习笔记

    一种由Robert Tarjan提出的求解有向图强连通分量的线性时间的算法. [有向图强连通分量] 在有向图G中,如果两个顶点间至少存在一条路径,称两个顶点强连通(strongly connected ...

  4. 专题讲座4 图的连通性和Tarjan算法 学习心得

    题目都好抽象,但又有点套路 题目地址: ZJNU-2022暑期专题-图的连通性和tarjan - Virtual Judge (vjudge.net) 目录 概念: 板子例题: 强连通分量 拓扑排序 ...

  5. Tarjan算法学习

    强连通: 略 无向图求割点: 割点即删掉一个点和连向它的边后,图不连通. 其实和求强连通差不多,还是按搜索树遍历下去,当遇到一条返祖边x->fa时,显然这条路径上的点都不是割点(x,fa除外). ...

  6. 图论之tarjan真乃神人也,强连通分量,割点,桥,双连通他都会

    先来%一下Robert Tarjan前辈 %%%%%%%%%%%%%%%%%% 然后是热情感谢下列并不止这些大佬的博客: 图连通性(一):Tarjan算法求解有向图强连通分量 图连通性(二):Tarj ...

  7. Tarjan算法 —— 强连通双连通缩点 模板

    TP 强连通缩点模板 双连通缩点模板 边双连通 点双连通 有向图 我们知道在一张 有向无环 图(也叫 DAG)中,肯定存在拓扑序.拓扑序的特殊顺序性质,能够允许我们在 O(n+m)O(n + m)O( ...

  8. 双连通图强连通图概念解释以及tarjan算法求解该类问题总结

    最近看了看类的相关题,感觉简单的题过于模板,但是对于难题的转化,如果对与这方面的概念不清楚,很难写,故总结一下. PS:博客里部分内容会和离散数学中的图论知识有联系,如果没有了解过相关知识可能比较难理 ...

  9. Tarjan算法应用 (割点/桥/缩点/强连通分量/双连通分量/LCA(最近公共祖先)问题)...

    转载自:http://hi.baidu.com/lydrainbowcat/blog/item/2194090a96bbed2db1351de8.html 基本概念: 1.割点:若删掉某点后,原连通图 ...

最新文章

  1. 对LinqtoExcel的扩展 【数据有限性,逻辑有效性】
  2. Open NI for Kinect安装测试
  3. 学习性代码和使用不存在的代码
  4. Microsoft Dynamic CRM 2013安装
  5. opencv 4快速入门_茶知识|茶道核心4元素,看懂你也可以快速入门茶道!先收藏...
  6. Scala算术运算符细节说明
  7. bootstrap 表格不用tr td如何写_Pandas还能用来写爬虫?
  8. JavaScript之面向对象学习四原型对象的动态性
  9. 《关于Win10系统下Oculus Senser USB无法识别的问题》
  10. 前n个正整数相乘的时间复杂度为_初一数学常考的21个知识点,掌握好,轻松110+!...
  11. 主键思维定势导致的惨案
  12. DHU 数据科学技术与应用【10】 第八次单元测验 时序与语音数据处理 答案
  13. n1盒子当无线打印服务器,n1下ubuntu安装cups配置airprint网络打印服务器
  14. PS_01_基本操作
  15. 用电机进行简单的PID参数整定
  16. conda install pytorch torchvision torchaudio cudatoolkit=11.6 -c pytorch -c conda-forge遇到的报错
  17. FI财务会计全局设置
  18. 小区疫情防控应对策略
  19. SAP中会计凭证红蓝冲相关分析测试笔记续(手工反记账红冲)
  20. 【Python 最全版(一)】—加解密、编码解码、进制转换、字符串转换

热门文章

  1. Struts拦截器使用
  2. Weekly Challenges - Week 11
  3. load data详解
  4. 设置ios6中UIViewController旋转
  5. ruby,rails环境架设配置 转载一文章.
  6. 'SVN更新' has encountered a problem :An internal error occurred during: svn错误
  7. 如何在vue中使用element-ui
  8. vs2015网站发布时,设置页面合并后程序集的文件版本
  9. Android 常用开源框架源码解析 系列 (四)Glide
  10. 蓦然回首,我是如何走上数据库开发这条路的?(一)