[算法模版]Tarjan爷爷的两种图论算法

[算法模版]Tarjan爷爷的两种图论算法

前言

Tarjan爷爷发明了很多图论算法,这些图论算法有很多相似之处(其中一个就是我都不会)。这里会对这三种算法进行简单介绍。

定义

强连通(strongly connected): 在一个有向图\(G\)里,设两个点$ a, b \(发现,由\)a\(有一条路可以走到\)b\(,由\)b\(又有一条路可以走到\)a\(,我们就叫这两个顶点\)(a,b)$强连通。

强连通图: 如果 在一个有向图\(G\)中,每两个点都强连通,我们就叫这个图,强连通图。

分量:把一个向量分解成几个方向的向量的和,那些方向上的向量就叫做该向量(未分解前的向量)的分量。

强连通分量(strongly connected components/SCC):在一个有向图G中,有一个子图,这个子图每2个点都满足强连通,我们就叫这个子图叫做强连通分量。

比如说这个图,在这个图中呢,点1与点2互相都有路径到达对方,所以它们强连通。

而在这个有向图中,点1 2 3组成的这个子图,是整个有向图中的强连通分量。

dfn[i]:指第\(i\)个点的\(dfs\)序。

low[i]:指第\(i\)个点的子树内的所有点通过反祖边能走到的点的\(dfn\)的最小值。

先行结论

  • 在一个无向图上,跑一棵生成树。可以证明非树边只有反祖边,没有横叉边。

Tarjan算法求割点/割边(针对无向图)

割点

首先,一个比较显然的结论就是所有的叶子结点和根节点都不是割点。对于其他节点u来说,只要有至少一个儿子v满足low[u]>=dfn[v],就证明v若不通过他父亲就回不去。那这个点就是割点。根节点只需要判断是不是有两棵子树就好了。实现起来可以当作有两个点满足low[u]>=dfn[b]。因为因为根节点\(dfn\)为1,所以一定满足条件。如果两个根结点儿子不通过根节点就能联通,那么他们一定在一个子树。

void tarjan(int now,int ff){
    low[now]=dfn[now]=++idx;
    for(int i=head[now];i;i=side[i].next){
        int v=side[i].v;
        if(side[i].id==ff)continue;
        if(!dfn[v]){
            tarjan(v,side[i].id);
            low[now]=min(low[now],low[v]);
            if(low[v]>=dfn[now]){ans[now]++;}
        }
        else{
            low[now]=min(dfn[v],low[now]);
        }
    }
    return;
}
void output(){
    for(int i=1;i<=n;i++){
        if((ans[i]&&!root[i])||(ans[i]>=2&&root[i])){
            cout<<i<<' ';//输出所有割点
        }
    }
} 

割边

和割点基本一样。只需要把\(low[v]>=dfn[u]\)改成\(low[v]>dfn[u]\)即可。同时需要判断,不能再次走刚刚走过的边。

如果点u的至少一个儿子v满足low[v]>dfn[u]。就证明不通过这条边无法走到上面。所以这是条割边。

Tarjan算法求点双/边双(针对无向图)

边双

和楼下的强连通分量很像,唯一的区别就是需要特殊判断一下,不能通过从父亲下来的那条边走上去。(因为强连通分量是有向图,走不上去,所以不存在这个问题)

实现起来也很简单,只用简单修改一下dfs函数:

dfs(u,f)其中u为当前节点,f为走到这个点通过的边的编号。

当在遍历u的所有边试图向下dfs时,只需要加一个if(现在准备选择的边的编号==f)continue;即可。

点双

咕咕咕

Tarjan算法求强连通分量(针对有向图)

stack<int> tp;
void dfs(int u)
{
    dfn[u]=low[u]=++cnt1;//初始化每个未访问过的节点
    tp.push(u);
    for(int i=head[u];i;i=side[i].next)
    {
        int v=side[i].v;
        if(!dfn[v])dfs(v),low[u]=min(low[u],low[v]);
        else if(!scc[v])low[u]=min(low[u],dfn[v]);//被访问过却没有SCC编号(在栈里),证明在同一个强连通分量。因为栈维护的是一条有一个节点到它儿子的路径。所以如果栈顶的点u到栈中任意一点v有边。就证明存在u到v的一个环。
    }
    if(dfn[u]==low[u])//如果这个点是它所在强连通分量中dfn最小的,则有它来承担输出整个SCC的任务
    {
        int s=tp.top(),id=++cnt2;tp.pop();
        scc[s]=id;
        while(s!=u)s=tp.top(),tp.pop(),scc[s]=id;
    }
}

参考资料

全网最!详!细!Tarjan算法讲解

割点和桥

原文地址:https://www.cnblogs.com/GavinZheng/p/11791970.html

时间: 2024-10-12 17:08:29

[算法模版]Tarjan爷爷的两种图论算法的相关文章

【转】两种非对称算法原理:RSA和DH

转自:http://blog.chinaunix.net/uid-7550780-id-2611984.html 两种非对称算法原理:RSA和DH 虽然对称算法的效率高,但是密钥的传输需要另外的信道.非对称算法RSA和DH可以解决密钥的传输问题(当然,它们的作用不限于此).这两个算法的名字都是来自于算法作者的缩写,希望有朝一日能够出现用中国人命名的加密算法.非对称算法的根本原理就是单向函数,f(a)=b,但是用b很难得到a. RSA算法 RSA算法是基于大数难于分解的原理.不但可以用于认证,也可

集合相似度对比的两种计算算法

相似度对比的两种计算算法:Jaccard similarity相似性系数和Ochiai coefficient落和系数 Jaccard coefficient:A,B分别代表符合某种条件的集合:两个集合交集的大小/两个集合并集的大小,交集=并集意味着2个集合完全重合. Ochiai coefficient:A,B分别代表符合某种条件的集合:两个集合的交集大小/两个集合大小的几何平均值.是余弦相似性的一种形式. 相关参考链接:http://en.wikipedia.org/wiki/Jaccard

经典的两种排序算法

一.冒泡排序 int temp = 0; for (int j = 1; j < a.Length; j++) { for (int i = 0; i < a.Length - j; i++)//内循环,每走一趟会把最小值放到最后 { if (a[i] < a[i + 1]) { temp = a[i]; a[i] = a[i + 1]; a[i + 1] = temp; } } } 二.选择法排序 int min; for (int j = 0; j< a.Length; j++

算法—比较两种排序算法:选择排序和插入排序

现在我们已经实现了两种排序算法,我们很自然地想知道选择排序和插入排序哪种更快.这里我们第一次用实践说明我们解决这个问题的办法. 性质:对于随机排序的无重复主键的数组,插入排序和选择排序的运行时间是平方级别的,两者之比应该是一个较小的常数. 例证:这个结论在过去的半个世纪中已经在许多不同类型的计算机上经过了验证.在1980年本书第一版完成之时插入排序就比选择排序快一倍,现在仍然是这样,尽管那时这些算法将10万条数据排序需要几个小时而现在只需要几秒钟.在你的计算机上插入排序也比选择排序快一些吗?可以

算法整理(二)---快速排序的两种实现方式:双边扫描和单边扫描

首先简单谈下快速排序的特点,时间复杂度O(nLog n),最差时间复杂度O(n^2),平均时间O(nLog n).因为用到了函数栈,空间复杂度为O(lg n),最差为O(n).是一种不稳定的排序方法.基本思想是分治法,这位大大的http://blog.csdn.net/morewindows/article/details/6684558 讲的非常清楚了,分治法+挖坑法,我就不多说了.就是以某个数为参照,使得左边的都小于他,右边的数都大于他.然后对他的左右两个区间采取同样的方法进行递归. 就其整

最大连续子段和的两种线性算法

问题描述:给一个数组a1,a2,...,an.求这个数组的最大连续子段和.(非空子段) 即,定义Sij=ai+...+aj,则题目要求的是 max{Sij}(1<=i<=j<=n) N^3枚举和优化之后的N^2枚举就不说了,还有NlogN的二分算法也不提,想了解的可以看我的另一篇博客:http://www.cnblogs.com/itlqs/p/5097504.html 这里主要详解两种O(n)的算法. 方法一:动态规划 dp[i]表示以第i位为结尾的最大子段和.那么转移方程就是:dp[

一列数字的规则如下;1,1,2,3,5,8,13,21,34........ 求第30位数字是多少,用递规和非递归两种方法算法实现

斐波纳契数列(Fibonacci Sequence),又称黄金分割数列.在数学上,斐波纳契数列以如下被以递归的方法定义:F0=0,F1=1,Fn=F(n-1)+F(n-2)(n>=2,n∈N*)在现代物理.准晶体结构.化学等领域,斐波纳契数列都有直接的应用,现在我从算法的角度,利用递归和非递归两种方式来进行实现: 一:递归 这个数列是用递归来实现的经典例子. private static  long Fibonacci(int n)         {             long resu

判断Java对象死亡的两种常用算法

当对象不馁引用的时候,这个对象就是死亡的,等待GC进行回收. 1.引用计数法 概念: 给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就增加1:当应用失效时,计数器值就减1:任何时刻计数器为0是对象就是不可能再被使用的. 但是: 主流的java虚拟机并没有选用引用计数器算法,其中一个最主要的原因是:它很难解决对象之间是相互循环引用. 优点:算法的实现简单,判定效率高,大部分情况下是一个不错的算法.很多地方应用到它 缺点: 引用和去引用伴随假发和减法,影响性能. 致命的缺陷:对于循环引

两种快速排序算法性能的比较

先来看看第一种快速排序算法(QuickSort1): #include <iostream> #include <fstream> #include <ctime> #define MAXNUM 1024 using namespace std; void QuickSort(int A[], int low, int high) { if(low>=high) return; int first = low; int last = high; int key =