图论:tarjan算法

9 篇文章 0 订阅
  • 可以求有向图强连通分量个数,每个强连通分量的节点数等,还可以求割点和桥
  • 强连通图G:G中任意两节点都可以相互到达
  • 强连通分量:G不是强连通图,但G的子图G'是强连通图,则G'为G强连通分量。强连通分量是环。
  • 割点:如果去掉图中节点v及与v相连的边后,图的强连通分量变多了,则v为割点
  • 桥:如果去掉某条边后图的强连通分量变多了,则该条边为桥。
  • 有割点不一定有桥,有桥一定有割点。如C为割点,但与C相连的边都不是桥。

Tarjan 割点判断

tarjan算法是对DFS的优化,在dfs时割点的依据:

  1. 如果一个节点是根节点,则如它的子节点数>1,则它为割点
  2. 如果不是根节点,设为u,则若它的子节点中有一个节点v不能到达u的祖先节点,则u为割点。

Tarjan求强连通分量

  1. dfn[] 数组:DFN[u]为节点 u 搜索的次序编号;可用来判断节点u是否被搜到过。
  2. low[] 数组:表示该点能直接或间接到达时间最小的顶点。例如:low[u]为节点 u 或其子树能够追溯到最早的栈中节点的次序号;
  3. stack 存储该连通子图中的所有点
  4. scnt:存强连通分量个数
  5. scc[]:记录每个节点属于的强连通分量编号

如: 

(1)从1开始DFS搜到6,其中经过3,5,都入了栈。由于6没孩子节点,所以循环结束,判断得dfn[6]==low[6],因为6就在栈顶,所以6单独作为一个强连通分量。

(3)从6回溯到5,同6一样,再没子节点了,出栈,单独作为一个强连通分量。

(3)回溯到3后,3继续搜到4,4搜到6,已搜过且不在栈中,再搜到1,1在栈中,更新low[4]=1,dfn[4]!=low[4],回溯到3.

(4)从4回溯到3后,3更新low[3]=low[4]=1。dfn[3]!=low[3],回溯到1.

(5)从3回溯到1后,1更新low[1]=low[3]=1。1继续搜到2,2搜到4发现4已经搜过且在栈中,更新low[2]=dfn[4]=5;回溯到1.

(6)dfn[1]==low[1],2,4,3,1依次出栈,构成强连通分量。

 

【NOIP2015】信息传递

theme:n个人传信息,指定每个人可以把信息传给谁(每个人可以从多个人那里获得信息,但只能将信息传给一个人)。开始时每个人只知道自己的生日,每经过一轮游戏将当前知道的信息告诉对应的人,问最少经过几轮有人听到了自己的生日信息?

solution:即求最小环中元素的个数,也就是最小强连通分量节点个数。

//求强连通分量中最小的节点个数
#include<iostream>
#include<vector>
#include<stack>
using namespace std;

const int SIZE=1000010;
int index,ans;
vector<int>E[SIZE];
stack<int>s;
int dfn[SIZE];
int low[SIZE];
bool exist[SIZE];//是否还在栈内

void initT(int n)
{
    fill(dfn,dfn+n+1,0);
    fill(low,low+n+1,0);
    fill(exist,exist+n+1,false);
    index=0;
    ans=n;
}

void mkEdge(int n)
{
    for(int i=1;i<=n;++i)
    {
        int a;
        cin>>a;
        E[i].push_back(a);
    }
}

void tarjan(int u)
{
    dfn[u]=low[u]=++index;
    s.push(u);
    exist[u]=true;
    for(int i=0;i<E[u].size();++i)
    {
        int v=E[u][i];
        if(!dfn[v])//每访问过
        {
            tarjan(v);
            low[u]=min(low[u],low[v]);
        }
        else if(exist[v])//访问过但还在栈中
            low[u]=min(low[u],dfn[v]);
    }

    //后面是求强连通分量的性质,如果只是判断就不用了
    if(dfn[u]==low[u])//该节点及其子节点能访问到的最小时间戳节点就是自己,说明它为强连通分量的根
    {        
       // ++scnt;
        int cnt=0;
        while(1)
        {
            int x=s.top();
            s.pop();
            cnt++;
           // scc[x]=scnt;
            if(u==x)
                break;
        }
        if(cnt>1)
            ans=min(ans,cnt);
    }
}

int main()
{
    int n;
    while(cin>>n&&n)
    {
        initT(n);
        mkEdge(n);
        for(int i=1;i<=n;++i)
            if(!dfn[i])
                tarjan(i);//如果执行多次说明原图不连通
        cout<<ans<<endl;
    }
}

判断割点

poj3713:Transferring Sylla

theme:给定一个无向图,判断是否每两个点间都有3条独立路径,独立路径即只共起点和终点。

solution:枚举每个点,判断删掉后图中是否有割点,如果有,则说明不是三连通图。

#include <cstdio>
#include <cstring>
#include <vector>
#include <algorithm>

using namespace std;

int del, root;
bool cut;
int dfn[510], low[510];

vector<int> e[510];
int n, m;
int tot;

void Tarjan(int u, int p) { // 当前节点,父亲节点
    if (cut) return;
    dfn[u] = low[u] = ++tot;
    int son = 0;
    for (vector<int>::iterator it = e[u].begin(); it != e[u].end(); ++it) {
        int v = *it;
        if (v == p || v == del) continue;
        if (!dfn[v]) {
            ++son;
            Tarjan(v, u);
            low[u] = min(low[u], low[v]);
            if ((u == root && son > 1) || (u != root && low[v] >= dfn[u])) { // 割点条件
                cut = 1;
                return;
            }
        } else {
            low[u] = min(low[u], dfn[v]);
        }
    }

}

int main() {
    while (scanf("%d%d", &n, &m) != EOF && n) {
        for (int i = 0; i < n; ++i) e[i].clear();
        for (int i = 0; i < m; ++i) {
            int u, v;
            scanf("%d%d", &u, &v);
            e[u].push_back(v);
            e[v].push_back(u);
        }
        cut = 0;
        for (int i = 0; i < n; ++i) {
            del = i;
            memset(dfn, 0, sizeof(dfn));
            tot = 0;
            root = !i;

            Tarjan(root, -1);
            if (cut) break;
            for (int j = 0; j < n; ++j) {
                if (j != del && !dfn[j]) {  // 不是连通图
                    cut = 1;
                    break;
                }
            }
            if (cut) break;
        }
        printf("%s\n", cut ? "NO" : "YES");
    }
    return 0;
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值