关于Tarjan算法简单笔记

(简要笔记,仅供参考,可能存在瑕疵或者不正确的地方,望指正)

(1)基于dfs。

(2)用于求解有向图强连通分量的线性时间。

强连通:有向图的两点a和b,a能到b,b能到a。

强连通分量:有向图的子图(有向图的一部分),上面任意两点都能互相到达。

1组成

(1)int dfn[N]//在搜索中到达该点的顺序

(2)int low[N]//最小子树的根,也是可以到达的点中dfn[i]的最小值。

(3)stack<int>vv//不断加入点,当里面的点组成了强连通分量时,就从顶部将这些点删掉。

(4)bool vis[N]//判断该点是否入栈。

(5)int num;//用于给dfn[i],low[i]。

(6)图的存储,这里用链式前向星

struct node{
int to;//终点
int next;//上一条边的编号。
}edge[N];
int head[N];//每个点遍历的起点。
int tot;//编号
void add(int x,int y)
{
tot++;
edge[tot].to=y;
edge[tot].next=head[x];
head[x]=tot;
}

tarjan与强连通分量(有向图)

void tarjan(int x)
{
low[x]=dfn[x]=++num;//初始化
vv.push(x);
vis[x]=ture;//将x点压入栈中。
for(i=head[x];i;i=edge[i].next)
{
int to=edge[i].to;
if(!dfn[to])//如果to没有搜索过,那么就向下访问
        {
        tarjan(to);
        low[x]=min(low[x],low[to])//to深搜回来维护low[x];
        }
else if(vis[to])//如果已经在栈中,说明,to和x强连通,两者在同一个强连通分量中。
        low[x]=min(low[x],dfn[to]);
}
if(low[x]==dfn[x])//x为一个强连通分量的根节点。
{
        while(vv.top()!=x)
        {
        vis[vv.top()]=false;
        vv.pop();
        }
        vis[vv.top()]=false;
        vv.pop();//将这个强连通分量所有点从栈中弹出。
}
return;
}

tarjan与有向图缩点

和上面差不多

int color[N];//color[i]表示i点属于哪个强连通分量,把强连通分量看成了一个点。

int count;//记录强连通分量数量,并且作为编号染色。
void tarjan(int x)
{
low[x]=dfn[x]=++num;//初始化
vv.push(x);
vis[x]=ture;//将x点压入栈中。
for(i=head[x];i;i=edge[i].next)
{
int to=edge[i].to;
if(!dfn[to])//如果to没有搜索过,那么就向下访问
        {
        tarjan(to);
        low[x]=min(low[x],low[to])//to深搜回来维护low[x];
        }
else if(vis[to])//如果已经在栈中,说明,to和x强连通,两者在同一个强连通分量中。
        low[x]=min(low[x],dfn[to]);
}
if(low[x]==dfn[x])//x为一个强连通分量的根节点。
{
count++;
        while(vv.top()!=x)
        {
        color[vv.top()]=count;
        vis[vv.top()]=false;
        vv.pop();
        }
        color[vv.top()]=count;
        vis[vv.top()]=false;
        vv.pop();//将这个强连通分量所有点从栈中弹出。
}
return;
}

遍历

for(i=1;i<=n;i++)
if(!dfn(i))
tarjan(i);

tarjan与割边判定(无向图)

//无向图中链式前项星的tot有一部分(比如割边,边联通分量)从2开始

int tot=1;
bool birdge[2*N]//用于标记割边
void tarjan(int x,int in_edge)//节点x,以及到达x的路径in_edge
{
dfn[x]=low[x]=++num;
for(int i=head[x];i;i=edge[i].next)
        {
        int to=edge[i].to;
        if(!dfn[to])//如果to没有访问过,那就dfs
                {
                tarjan(to,i);//通过i到to点。
                low[x]=min(low[x],low[to]);
                if(low[to]>dfn[x]])//割边判定
                birdge[i]=birdge[i^1]=ture;//有向边i和它的反向边i^1构成了割边。
                }
        else if(i!=(in_edge^1))//如果不是从in_edge的反向边回到x的,那么就有另外的边
                {
                low[x]=min(low[x],dfn[to]);
                }
         }
}

 tarjan与割点判定(无向图)

int root;记录搜索的起点
bool cut[N];//记录是否为割点
void tarjan(int x)
{
dfn[x]=low[x]=++num;
int flag=0;//记录该点连接了几个子树
for(int i=head[x];i;i=edge[i].next)
        {
        int to=edge[i].to;
        if(!dfn[to])
        {
        tarjan[to];
        low[x]=min(low[x],low[y]);
        if(low[y]>=dfn[x])//割点判定
                {
                flag++;
                if(x!=root||flag>1)//如果x不是搜索的点或者是多个子树的交点
                   cut[x]=ture;//x为割点
                }
        }
        }
        else
        low[x]=min(low[x],dfn[y]);
}
//遍历
for(int i=1;i<=n;i++)
        if(!dfn[i])
        {
        root=i;
        tarjan(i);
        }

tarjan与边双联通分量

int color[N];//标记每个点所属的联通分量
int dcc;//标记联通分量数量和编号。
void dfs(int x)
{
         color[x]=dcc;
        for(int x=head[x];i;i=edge[i].next)
        {
        int to=edge[i].to;
        if(color[to]||birdge[i])//如果编号为i的边是割边或者color已经标记过了
        continue;
        dfs(y);
        }
}
//遍历
int main(){
for(int i=1;i<=n;i++)
        {
        ++dcc;
        dfs(i);
        }
}

tarjan与e-DCC缩点

//把边两个联通分量看成两个节点,这里用链式前向星存储

node edge_c[2*N];
int headc[N];
int tc=1;//记录边的数量以及编号。
void add_c(int x,int y)
{
        edge_c[++tc]=y;
        edge_c[tc].next=headc[x];
        head[x]=tc;
}
//遍历,可能会构造出重边
for(int i=2;i<=tot;i++)
{
        int x=edge[i^1].to,y=edge[i].to;//由一条无向边相连的两个点。
        if(color[x]==color[y])//如果两个点属于同一个子集,无事发生。
        continue;
        add_c(color[x],color[y]);//建边
}

tarjan与点双联通分量

int root;//遍历搜索的起点。
stack<int>vv;//用栈维护点联通分量.
vector<int>dcc[N];//记录点联通分量。
int cnt;//记录点联通分量的数量和编号
void tarjan(int x)
{
        dfn[x]=low[x]=++num;
        vv.push(x);//将放入栈内
        if(x==root&&head[x]==0)//如果x为搜索起点并且没有边和他相连
        {
        dcc[++cnt].push_back(x);//将x放入编号为cnt+1的点联通分量中
        return;
        }
        int flag=0;//记录节点连接的子树的数量
        for(int i=head[x];i;i=edge[i].next)
        {
        int to=edge[i].to;
        if(!dfn[to])
        {
                tarjan(to);
                low[x]=min(low[x],low[to]);
                if(low[to]>=dfn[x])
                {
                        flag++;
                        if(x!=root||flag>1)//如果不是搜索起点或者x连了多个子树
                        cut[x]=ture;//x为割点
                        cnt++;
                        int z;
                        do
                        {
                        z=vv.top();
                        dcc[cnt].push_back(z);
                        vv.pop();
                        }while(z!=y)
                dcc[cnt].push_back(x);
                }
        }
        else
                low[x]=min(low[x],dfn[y]);
        }
}
//遍历
for(int i=1;i<=n;i++)
        if(!dfn[i])
        {
        root=i;
        tarjan(i);
        }

tarjan与v-DCC缩点

num=cnt;//把割点看成新的节点,从cnt+1开始。
int new_id[N];//记录节点编号
for(int i=1;i<=n;i++)
{
if(cut[i])
        {
                new_id[i]=++num;
        }
}
int tc=1;
for(int i=1;i<=cnt;i++)
{
        for(int j=0;j<dcc[i].size();j++)
        {
        int x=dcc[i][j];
        if(cut[x])
                {
                add_c(i,new_id[x]);
                add_c(new_id[x],i);
                 }
        else
        color[x]=i;
        }
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值