sth. about tarjan 第二弹~~考前复习版

图算法进阶
本文深入探讨了图论中的三种核心算法:强连通分量、割点检测与边双连通分量分析,并通过具体代码实例讲解了这些算法的具体实现过程。

唔 RP++

Part 1
最基本的强联通分量
bzoj 1654

注意 编号+的位置
还有 low[x] 的更新!!!

low[x]=min(low[x],low[v])  (!dfn[v])
low[x]=min(low[x],dfn[v]) (else)

恩恩别的记住统计的条件就好了。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
//by mars_ch
int n,m;
int dfn[10005],low[10005],stack[10005],instack[10005],top;
int id,sz[10005];
int tim,tot,first[10005];
struct data
{
    int f,t,nxt;
}e[50005*2];
void add(int a,int b)
{
    e[tot].f=a,e[tot].t=b;
    e[tot].nxt=first[a];
    first[a]=tot++;
}
void tarjan(int x)
{
    dfn[x]=low[x]=++tim;
    stack[++top]=x;
    instack[x]=1;
    for(int i=first[x];i!=-1;i=e[i].nxt)
    {
        int t=e[i].t;
        if(!dfn[t])
        {
            tarjan(t);
            low[x]=min(low[t],low[x]);
        }
        else if(instack[t])
        {
            low[x]=min(low[x],dfn[t]);
        }
    }
    if(low[x] == dfn[x])
    {
        ++id;
        while(1)
        {
            int t=stack[top--];
            instack[t]=0;
            sz[id]++;
            if(t == x) break;
        }
    }
}
int main()
{
    scanf("%d%d",&n,&m);
    memset(first,-1,sizeof(first));
    for(int i=1;i<=m;i++)
    {
        int a,b;
        scanf("%d%d",&a,&b);
        add(b,a);
    }
    for(int i=1;i<=n;i++)
    {
        if(!dfn[i])
        {
            tarjan(i);
        }
    }
    int ans=0;
    for(int i=1;i<=id;i++)
    {
        if(sz[i]>1)
        {
            ans++;
        }
    }
    printf("%d\n",ans);
    return 0;
}

Part2
割点

还是 low[t]>=dfn[x]
搜索树的根节点的节点数 的 数目

还有就是 加加减减的 答案

ps被m=0 坑到了输入

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
//by mars_ch
int n,m;
struct data
{
    int f,t,nxt;
}e[50005*2];
int first[10005],tot;
int stack[10005],instack[10005];
int dfn[100005],low[100005];
int ans=0;
int tim,top,son,maxx;
void add(int a,int b)
{
    e[tot].f=a,e[tot].t=b;
    e[tot].nxt=first[a],first[a]=tot++;
}
void tarjan(int x,int root)
{
    dfn[x]=low[x]=++tim;
    int child=0;
    for(int i=first[x];i!=-1;i=e[i].nxt)
    {
        int t=e[i].t;

        if(!dfn[t])
        {
            tarjan(t,root);
            low[x]=min(low[x],low[t]);
            if(x == root) son++;
            else if(low[t]>=dfn[x])
            {
                child++;
            }
        }
        else 
        {
            low[x]=min(low[x],dfn[t]);
        }
    }
    ans=max(ans,child+1);
}
int main()
{
    while(scanf("%d%d",&n,&m) && (n!=0 || m!=0))
    {
        memset(first,-1,sizeof(first));
        memset(dfn,0,sizeof(dfn));
        memset(low,0,sizeof(low));
        ans=0,maxx=0,tot=0;
        int cnt=0;
        if(m == 0) printf("%d\n",n-1);
        else
        {
            for(int i=1;i<=m;i++)
            {
                int a,b;
                scanf("%d%d",&a,&b);
                add(a,b);
                add(b,a);
            }
            for(int i=0;i<n;i++)
            {
                if(!dfn[i])
                {
                    cnt++;
                    son=0;
                    tarjan(i,i);
                    //printf("the number of son is %d\n",son);
                    if(son>1) ans=max(ans,son);
                    maxx=max(maxx,ans);
                }
            }
        //  printf("%d %d\n",cnt,maxx);
            printf("%d\n",cnt-1+maxx);
        }
    }   return 0;
}

Part3
边双连通分量
恩 没什么 和割点比较像 就是 平行边

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
//by mars_ch
int n,m;
struct data
{
    int f,t,nxt;
}e[100005*2];
int first[1005],tot;
void add(int a,int b)
{
    e[tot].f=a,e[tot].t=b;
    e[tot].nxt=first[a],first[a]=tot++;
}
int stack[1005],instack[1005],top,idx,tim;
int low[1005],dfn[1005],belong[1005],deg[1005];
void tarjan(int x,int fa)
{
    dfn[x]=low[x]=++tim;
    stack[++top]=x,instack[x]=1;
    for(int i=first[x];i!=-1;i=e[i].nxt)
    {
        int t=e[i].t;
        if(t == fa) continue;
        if(!dfn[t])
        {
            tarjan(t,x);
            low[x]=min(low[x],low[t]);
        }
        else if(instack[t])
        {
            low[x]=min(low[x],dfn[t]);
        }
    }
    if(low[x] == dfn[x])
    {
        ++idx;
        while(1)
        {
            int t=stack[top--];
            belong[t]=idx;
            if(t == x) break;
        }
    }
}
int main()
{
    scanf("%d%d",&n,&m);
    memset(first,-1,sizeof(first));
    for(int i=1;i<=m;i++)
    {
        int a,b;
        scanf("%d%d",&a,&b);
        add(a,b);
        add(b,a);
     } 
     for(int i=1;i<=n;i++)
     {
        if(!dfn[i])
        {
            tarjan(i,-1);
        }
     }
     for(int i=1;i<=n;i++)
     {
        for(int j=first[i];j!=-1;j=e[j].nxt)
        {
            int t=e[j].t;
            if(belong[i]!=belong[t])
            {
                deg[belong[t]]++;
             }
         }
     }
     int ans=0;
     for(int i=1;i<=idx;i++)
     {
        if(deg[i] == 1)
        {
            ans++;
         }
     }
     printf("%d\n",(ans+1)/2);
    return 0;
}
<think>嗯,用户让我继续写“4. Tarjan 算法解析”,我需要先回想一下之前的内容是否已经介绍过Tarjan算法的背景。如果这是系列文章的一部分,用户可能之前已经讲解了其他算法,比如Kosaraju或Gabow,现在轮到Tarjan了。不过用户可能没看过前面的部分,所以我得先简要说明Tarjan算法是用来做什么的。 Tarjan算法主要用于寻找有向图中的强连通分量(SCCs),对吧?那它的核心思想是基于深度优先搜索(DFS)和栈结构的应用。这时候需要解释清楚强连通分量的定义,确保读者明白这个概念。比如,强连通分量是指图中的最大子图,其中任意两个节点可以互相到达。 接下来,我需要分解算法的步骤。首先,Tarjan算法使用DFS遍历图中的每个节点,维护两个关键数组:索引数组index和low值数组。index记录节点被访问的顺序,而low值表示当前节点能够回溯到的最早的节点索引。这一步可能需要详细说明,比如每个节点初始化时index和low都被设置为初始值,比如-1或者未访问状态,然后在DFS过程中更新。 然后,当某个节点的low值等于index值时,说明找到了一个强连通分量的根节点。这时需要将栈中该节点及之后入栈的节点出,组成一个强连通分量。这里可能需要解释栈的作用,即用来存储当前路径上的节点,帮助确定哪些节点属于同一个SCC。 接下来,可能需要举例说明。例如,一个简单的有向图,通过Tarjan算法的步骤,展示如何标记index和low值,何时出栈中的节点形成SCC。这样读者可以更直观地理解算法的执行过程。 还要提到算法的时间复杂度,Tarjan算法的时间复杂度是O(V + E),线性时间,这是它的优势之一,适用于大规模图处理。同时,对比其他算法如Kosaraju,可能需要提到Tarjan算法只需一次DFS,而Kosaraju需要两次,但用户可能不需要这部分对比,除非特别说明。 另外,注意是否需要代码示例。如果用户希望深入实现,可能需要提供伪代码或者具体编程语言的示例,比如Python或Java。但用户可能只需要解析,所以伪代码可能更合适,加上注释解释关键步骤。 可能遇到的常见问题,比如如何处理未访问的节点,栈的状态管理,以及如何正确更新low值。例如,当遇到已访问但仍在栈中的节点时,如何更新当前节点的low值,这需要详细说明,避免读者混淆。 此外,是否需要讨论应用场景?比如在编译器优化、社交网络分析中寻找强连通分量,或者用于其他图论问题,比如2-SAT问题的求解。这些应用可以增强读者对算法重要性的理解。 还要注意术语的准确性,比如“强连通分量”是否正确,是否有其他变体,比如在无向图中的连通分量,但Tarjan算法主要处理有向图的情况。需要明确这一点,避免混淆。 最后,是否需要总结Tarjan算法的优缺点?比如优点高效,缺点实现相对复杂,需要维护多个数据结构和状态。或者是否要提到Tarjan的其他算法,比如用于最近公共祖先(LCA)的算法,但这里可能无关,需专注于SCC部分。 总之,结构大致分为:简介、算法思想、详细步骤、示例说明、复杂度分析、应用场景、可能的代码实现或伪代码,以及总结。需要根据用户的深度需求调整详细程度,但用户现在要求的是“解析”,所以应该较为详细地分步骤讲解,并确保逻辑清晰。###</think> 4. Tarjan 算法解析 Tarjan 算法由 Robert Tarjan 于 1972 年提出,是一种基于深度优先搜索(DFS)的高效算法,用于**寻找有向图中的强连通分量(Strongly Connected Components, SCCs)**。其核心思想是通过一次遍历,同时记录节点的访问状态和回溯信息,实现线性时间复杂度(O(V+E))。 --- #### **核心概念** 1. **强连通分量(SCC)** 有向图中的一组节点,其中任意两个节点均可通过路径互相到达,且这组节点是“最大”的(无法添加其他节点后仍保持强连通性)。 2. **关键数据结构** - **索引(Index)**:记录节点被 DFS 访问的顺序(类似时间戳)。 - **Low 值**:记录当前节点通过非父子边(回边)能回溯到的最早祖先节点的索引。 - **栈(Stack)**:临时存储当前 DFS 路径上的节点,辅助判断 SCC。 --- #### **算法步骤** 1. **初始化** - 为每个节点维护两个数组:`index[]`(访问顺序)和 `low[]`(回溯能力)。 - 使用栈存储当前路径上的节点,并用 `onStack[]` 标记节点是否在栈中。 2. **DFS 遍历** - 对未访问的节点启动 DFS,分配 `index` 和 `low` 初始值(如递增的时间戳)。 - 遍历邻接节点: - 若邻接节点未访问,递归 DFS,并更新当前节点的 `low` 值为 `min(low[current], low[neighbor])`。 - 若邻接节点已在栈中(属于当前路径),更新 `low` 值为 `min(low[current], index[neighbor])`。 3. **识别 SCC** - 当回溯到某个节点时,若其 `low == index`,说明它是当前 SCC 的根节点。 - 从栈中出节点,直到根节点出栈,出的所有节点构成一个 SCC。 --- #### **示例说明** 以有向图 `A→B→C→A, C→D→E, E→D` 为例: 1. DFS 访问 A→B→C,C 发现回边到 A(`low[C] = min(low[C], index[A]) = 0`)。 2. 回溯到 B、A 时更新 `low`,直到根节点 A 满足 `low[A] == index[A]`,出 A、B、C 组成 SCC {A,B,C}。 3. 继续访问 D→E,E 发现回边到 D(`low[E] = index[D]`),回溯到 D 时满足 `low[D] == index[D]`,出 D、E 组成 SCC {D,E}。 --- #### **代码框架(Python 风格伪代码)** ```python def tarjan(u): index[u] = low[u] = global_index global_index += 1 stack.push(u) onStack[u] = True for v in graph[u]: if index[v] == -1: # 未访问 tarjan(v) low[u] = min(low[u], low[v]) elif onStack[v]: # 已访问且在栈中 low[u] = min(low[u], index[v]) if low[u] == index[u]: # 发现 SCC 根节点 scc = [] while True: v = stack.pop() onStack[v] = False scc.append(v) if v == u: break result.append(scc) ``` --- #### **复杂度与应用** - **时间复杂度**:O(V+E),每个节点和边仅访问一次。 - 编译器依赖分析(如代码优化)。 - 社交网络中的社群发现。 - 解决 2-SAT 问题等。 --- #### **优势与局限** - **优势**:无需预先知道图的结构,单次 DFS 即可完成。 - **局限**:递归实现可能导致栈溢出(可通过迭代 DFS 优化)。 通过维护 `index` 和 `low` 的巧妙设计,Tarjan 算法将 SCC 的发现过程无缝嵌入到 DFS 中,成为图论中的经典范例。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值