强连通算法--Tarjan的缩点及染色

43 篇文章 1 订阅

Tarjan算法相当于在一个有向图中找有向环,那么我们Tarjan算法最直接的能力就是缩点辣缩点基于一种染色实现,我们在Dfs的过程中,尝试把属于同一个强连通分量的点都染成一个颜色,那么同一个颜色的点,就相当于一个点。比如刚才的实例图中缩点之后就可以变成这样:

将一个有向带环图变成了一个有向无环图(DAG图)。很多算法要基于有向无环图才能进行的算法就需要使用Tarjan算法实现染色缩点,建一个DAG图然后再进行算法处理。在这种场合,Tarjan算法就有了很大的用武之地辣!

 

我们再引入一个数组color【i】表示节点i的颜色,再引入一个数组stack【】实现一个栈,然后在Dfs过程中每一次遇到点都将点入栈,在每一次遇到关键点的时候将栈内元素弹出,一直弹到栈顶元素是关键点的时候为止,对这些弹出来的元素进行染色即可

板子:

void Tarjan(int u)//此代码仅供参考
{
    vis[u]=1;
    low[u]=dfn[u]=cnt++;
    stack[++tt]=u;
    for(int i=0;i<mp[u].size();i++)
    {
        int v=mp[u][i];
        if(vis[v]==0)Tarjan(v);
        if(vis[v]==1)low[u]=min(low[u],low[v]);
    }
    if(dfn[u]==low[u])
    {
        sig++;//第几个强连通集
        do    //为每个强连通集的点做标记(染色)
        {
            low[stack[tt]]=sig;
            color[stack[tt]]=sig;
            vis[stack[tt]]=-1;
        }
        while(stack[tt--]!=u);
    }
}

 

Poj 2553

题目大意:给你一堆点,一堆边,让你找到缩点之后出度为0的节点, 然后将节点编号从小到大排序输出。

思路:Tarjan,缩点染色判断出度为0的强连通分量,将整个集合排序,输出即可。

#include <iostream>
#include <algorithm>
#include<vector>
#include<string.h>
#include<stdio.h>
#define maxn 1000000
//此代码仅供参考,用于求一个图存在多少个强连通分量
using namespace std;
vector<int>mp[maxn];
int ans[maxn];
int degree[maxn];
int color[maxn];
int stack[maxn];
int vis[maxn];
int dfn[maxn];
int low[maxn];
int n,m,tt,cnt,sig;
void init()//初始化 
{
	memset(degree,0,sizeof(degree));
	memset(color,0,sizeof(color));
	memset(stack,0,sizeof(stack));	
	memset(low,0,sizeof(low));
	memset(dfn,0,sizeof(dfn));
	memset(vis,0,sizeof(vis));
	for(int i=1;i<=n;i++)//vector的初始化 
	mp[i].clear();
}

void tarjin(int u)
{
	vis[u]=1;
	low[u]=dfn[u]=cnt++;
	stack[++tt]=u;//每扫过一个新点,则入栈 
	for(int i=0;i<mp[u].size();i++)
	{
		int v=mp[u][i];
		if(vis[v]==0)tarjin(v);//递归 
		if(vis[v]==1)low[u]=min(low[u],low[v]);	//晦朔	
	}
	//判点 是否为一个强连通分量 
	if(dfn[u]==low[u])
	{
		sig++;
		do
		{
			color[stack[tt]]=sig;
			//同一个连通图染一个色 
		}
		while(stack[tt--]!=u);//出栈操作 			
	}
	
}

void slove()//询问有多少个强连通分量 
{
	sig=0;tt=-1;cnt=1;
	for(int i=1;i<=n;i++)//跑一遍点集 
	{
		if(vis[i]==0)//串一下各个联通集 
	     tarjin(i);//跑dfs 
	}
	
	for(int i=1;i<=n;i++)//n个点 
	{
		for(int j=0;j<mp[i].size();j++)
		{
			int v=mp[i][j];
			
			//判别 出度 是否为0 
			if(color[i]!=color[v])
			degree[color[i]]++;		
		}	
	}
	int tot=0;
	for(int i=1;i<=sig;i++)
	{
		if(degree[i]>0)continue;
		//这个联通集的出度不为0,跳过 
		
		for (int j=1;j<=n;j++)
		{
			if(color[j]==i)
			ans[tot++]=j;
			//缩点,把每个出度为0的强联通的点计入数组 
		} 	
	} 
	
	sort(ans,ans+tot);
	for(int i=0;i<tot;i++)
	{
	   if(i==0)printf("%d",ans[i]);
	   else printf(" %d",ans[i]);
	} 
	cout<<endl;
}


int main()
{
	while(scanf("%d",&n)!=EOF)
	{
		if(n==0)break;//点的个数 
		scanf("%d",&m);
		init();
		for(int i=0;i<m;i++)//存图方式 
		{
			int x,y;
			scanf("%d%d",&x,&y);
			mp[x].push_back(y);	
		}
		slove();		
	}
	return 0;
}





抄的大神的这是链接

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值