Tarjan算法模板 详解

思想:

做一遍DFS,用dfn[i]表示编号为i的节点在DFS过程中的访问序号(也可以叫做开始时间)用low[i]表示i节点DFS过程中i的下方节点所能到达的开始时间最早的节点的开始时间。初始时dfn[i]=low[i]

在DFS过程中会形成一搜索树。在搜索树上越先遍历到的节点,显然dfn的值就越小。

DFS过程中,碰到哪个节点,就将哪个节点入栈。栈中节点只有在其所属的强连通分量已经全部求出时,才会出栈。

如果发现某节点u有边连到搜索树中栈里的节点v,则更新u的low 值为dfn[v](更新为low[v]也可以)。
如果一个节点u已经DFS访问结束,而且此时其low值等于dfn值,则说明u可达的所有节点,都不能到达任何在u之前被DFS访问的节点 ---- 那么该节点u就是一个强连通分量在DFS搜索树中的根。

此时将栈中所有节点弹出,包括u,就找到了一个强连通分量

以poj 1236 Network of Schools 为例说明

192K      0MS
#include <string.h>
#include <stdio.h>
#define V      105
#define E      100500

struct edge
{
      int to, next;
}Edge[E];
int head[V], e, n;

int indeg[V], outdeg[V]; //点的入度和出度数
int belong[V], low[V], dfn[V], scc, cnt;//dfn[]:遍历到u点的时间; low[]:u点可到达的各点中最小的dfn[v]
int S[V], top;
bool vis[V];//v是否在栈中

int addedge(int u, int v)
{
      Edge[e].to = v;
      Edge[e].next = head[u];
      head[u] = e++;
      return 0;
}
void tarjan(int u)
{
      int v;
      dfn[u] = low[u] = ++cnt;//开始时dfn[u] == low[u]
      S[top++] = u;//不管三七二十一进栈
      vis[u] = true;
      for (int i=head[u]; i!=-1; i=Edge[i].next)
      {
            v = Edge[i].to;
            if (dfn[v] == 0)//如果v点还未遍历
            {
                  tarjan(v);//向下遍历
                  low[u] = low[u] < low[v] ? low[u] : low[v];//确保low[u]最小
            }
            else if (vis[v] && low[u] > dfn[v])//v在栈中,修改low[u]
                  low[u] = dfn[v];
      }
      if (dfn[u] == low[u])//u为该强连通分量中遍历所成树的根
      {
            ++scc;
            do
            {
                  v = S[--top];//栈中所有到u的点都属于该强连通分量,退栈
                  vis[v] = false;
                  belong[v] = scc;
            } while (u != v);
      }

}

int solve()
{
      scc = top = cnt = 0;
      memset(dfn, 0, sizeof(dfn));
      memset(vis, false, sizeof(vis));
      for (int u=1; u<=n; ++u)
            if (dfn[u] == 0)
                  tarjan(u);
      return scc;
}

void count_deg()
{
      memset(indeg, 0, sizeof(indeg));
      memset(outdeg, 0, sizeof(outdeg));
      for (int u=1; u<=n; ++u)
            for (int i=head[u]; i!=-1; i=Edge[i].next)
            {
                  int v = Edge[i].to;
                  if (belong[u] != belong[v])
                  {
                        indeg[belong[v]]++;
                        outdeg[belong[u]]++;
                  }
            }
}

int main()
{
      int u, v, i;
      while (~scanf("%d", &n))
      {
            e = 0;
            memset(head, -1, sizeof(head));
            for (u=1; u<=n; ++u)
                  while (scanf("%d", &v) && v != 0)
                        addedge(u, v);
            solve();
            if (scc == 1)
                  printf("1\n0\n");
            else
            {
                  count_deg();
                  int inc = 0, outc = 0;
                  for (i=1; i<=scc; ++i)
                  {
                        if (indeg[i] == 0)
                              inc++;
                        if (outdeg[i] == 0)
                              outc++;
                  }
                  printf("%d\n%d\n", inc, (inc > outc ? inc : outc));
            }
      }
      return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值