Tarjan三大算法之双连通分量(割点,桥)

Tarjan算法解析
本文介绍了著名计算机科学家Robert Endre Tarjan提出的Tarjan算法中的割点和桥的概念。首先阐述了割点和桥的基本定义,接着详细讲解了如何利用深度优先搜索来识别无向图中的割点和桥,包括具体的实现代码。

Robert Endre Tarjan是一个美国计算机学家,他传奇的一生中发明了无数算法,统称为Tarjan算法。其中最著名的有三个,分别用来求解
1) 无向图的双连通分量
2) 有向图的强连通分量
3) 最近公共祖先问题
接下来几篇博客将分别讲述三个算法,首先是无向图的双连通分量,我们先从无向图的割点和桥讲起。

下面介绍中无向图中割点和桥的概念:
割点:一个结点称为割点(或者割顶)当且仅当去掉该节点极其相关的边之后的子图不连通。
桥:一条边称为桥(或者割边)当且仅当去掉该边之后的子图不连通。
割点:
首先我们考虑一个连通图(非连通图可以分别考虑连通块),我们从任意一个起点开始进行深度优先搜索,可以得到一棵树,并且这棵树中所有结点的子树之间不存在边,即没有跨越两棵子树的边(考虑一下,如果存在,那么与深度优先搜索树的定义互相矛盾)。于是有如下定理:
在无向连通图G中,
1、根结点u为割顶当且仅当它有两个或者多个子结点;
2、非根结点u为割顶当且仅当u存在结点v,使得v极其所有后代都没有反向边可以连回u的祖先(u不算)
在Tarjan算法里面,有两个时间戳非常重要,一个是dfn,意为深度优先数,即代表访问顺序;一个是low,意为通过反向边能到达的最小dfn。于是,上述定理中第二个条件(非根结点)可以简单地写成low[v]>=dfn[u]。
代码如下:

int n,m,stamp,low[1005],dfn[1005],iscut[1005];
vector<int> vec[1005];
void tarjan(int index,int fa){
    int child=0;
    low[index]=dfn[index]=++stamp;
    for(int i=0;i<vec[index].size();i++)
    {
        int tmp=vec[index][i];
        if(!dfn[tmp])
        {
            child++;
            tarjan(tmp,index);
            low[index]=min(low[index],low[tmp]);
            if(low[tmp]>=dfn[index])
                iscut[index]=1;
        }
        else if(dfn[tmp]<dfn[index] && tmp!=fa)
        {
            low[index]=min(low[index],dfn[tmp]);
        }
    }
    if(fa<0 && child==1)
        iscut[index]=0;
}

桥:
桥的求法其实也是类似的,它的求法可以看成是割顶的一种特殊情况,当结点u的子结点v的后代通过反向边只能连回v,那么删除这条边(u, v)就可以使得图G非连通了。用Tarjan算法里面的时间戳表示这个条件,就是low[v]>dfn[u]。
代码如下:

int n,stamp,dfn[1005],low[1005];
int cnt,ansx[10005],ansy[10005];
vector<int> vec[1005];
int rank[1005];
void addAns(int x,int y)
{
    if(x>y)
        swap(x,y);
    ansx[cnt]=x, ansy[cnt]=y;
    cnt++;
}
void tarjan(int index,int fa)
{
    int tmp;
    dfn[index]=low[index]=++stamp;
    for(int i=0;i<vec[index].size();i++)
    {
        tmp=vec[index][i];
        if(!dfn[tmp])
        {
            tarjan(tmp,index);
            low[index]=min(low[index],low[tmp]);
            if(low[tmp]>dfn[index])
                addAns(index,tmp);
        }
        else if(dfn[tmp]<dfn[index] && tmp!=fa)
        {
            low[index]=min(low[index],dfn[tmp]);
        }
    }
}
### Tarjan算法求解 Tarjan算法通过深度优先搜索(DFS)遍历图,利用DFS序和回溯边来判断。具体来说,如果一个节u存在至少一个子节v,使得v无法通过其他路径到达u的祖先节(即low[v] >= dfn[u]),则u为。对于根节,需要特殊处理,当它有两个或以上子节时,它才是。 ### Tarjan算法求解边的判断标准是,对于边(u, v),若low[v] > dfn[u],则(u, v)边。这是因为v无法通过其他路径到达u及其祖先,所以删除边(u, v)会使得图不连通。 ### Tarjan算法求解双连通分量双连通分量的求解可以通过Tarjan算法找到所有的边,然后将这些边从图中移除,剩下的每个连通分量即为一个边双连通分量双连通分量的求解较为复杂,需要在DFS过程中维护一个栈,每当发现一个时,就将栈中的节弹出,直到当前节v被弹出为止,这些节构成了一个双连通分量。 ### 实现代码示例 以下是一个使用Tarjan算法求解边以及边双连通分量的Python代码示例: ```python def tarjan(u, parent): global index_counter dfn[u] = low[u] = index_counter index_counter += 1 child_count = 0 for v in adj[u]: if v == parent: continue if dfn[v] == -1: stack.append((u, v)) child_count += 1 tarjan(v, u) low[u] = min(low[u], low[v]) if low[v] > dfn[u]: # Edge (u, v) is a bridge bridges.append((u, v)) if low[v] >= dfn[u] and parent != -1: # Node u is an articulation point articulation_points.add(u) elif dfn[v] < dfn[u]: stack.append((u, v)) low[u] = min(low[u], dfn[v]) if parent == -1 and child_count > 1: # Node u is an articulation point (root case) articulation_points.add(u) # Initialization n = 5 # Number of nodes in the graph adj = [[] for _ in range(n)] # Adjacency list representation of the graph dfn = [-1] * n # Discovery time of nodes low = [-1] * n # Lowest discovery time reachable index_counter = 0 bridges = [] # List to store bridges articulation_points = set() # Set to store articulation points stack = [] # Stack to keep track of edges for biconnected components # Example graph construction adj[0] = [1, 2] adj[1] = [0, 2] adj[2] = [0, 1, 3, 4] adj[3] = [2, 4] adj[4] = [2, 3] for i in range(n): if dfn[i] == -1: tarjan(i, -1) print("Articulation Points:", articulation_points) print("Bridges:", bridges) ``` ### 边双连通分量的实现 为了找到边双连通分量,可以在找到所有边后,将这些边从图中移除,然后对剩余的边进行连通分量的查找。每个连通分量即为一个边双连通分量。 ### 双连通分量的实现 双连通分量的实现需要在Tarjan算法中使用栈来跟踪边,每当发现一个时,就将栈中的边弹出,直到当前边(u, v)被弹出为止,这些边构成了一个双连通分量。 ### 总结 通过上述方法,可以有效地使用Tarjan算法来求解图中的边以及双连通分量问题。这种方法在处理大规模图时非常高效,因为它的时间复杂度是线性的,即O(V + E),其中V是顶数,E是边数[^1]。
评论 4
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值