强连通分量Kosaraju 算法(笔记)

Kosaraju 算法

两遍DFS,两个vector数组(存边和反向边),一个栈

  1. 第一遍DFS:
    从第一个点出发(其实是都可以,按顺序来呗,方便),进行正向边的DFS,当某一个点没有下一个点可去,或者所有下一个点都去过,那就将该点放入栈中并回溯,直到回到起始点,由于从1开始不一定可以DFS遍历完所有点,因此向后找没有走过的点,再次DFS,直到遍历完所有点。
    (源自bilibili_up主Biosas)
    第一遍DFS得到所有点的入栈顺序。

  2. 第二遍DFS:
    从栈顶出发(取出),进行反向边DFS,和正向相似。

  3. DFS第一遍找所有环,但这些环上可能“节外生枝”,第二遍就是一个减枝的过程,因为环可以出枝,但是反向变之后,环还是环,但是从环没办法到枝上了。

  4. 洛谷B3609

#include<bits/stdc++.h>
using namespace std;
const int maxn=10010;
const int maxm=100010;
int vis[maxn],temp[maxn];
int n,m,cnt=0,num=0;
stack<int>stk;
vector<int>G[maxn];//正向边
vector<int>G_t[maxn];//反向边
vector<int>ans[maxn];//依据题目用到的,存储一个强连通分量的每一个节点
void dfs1(int x)
{
	vis[x]=1;
	for(int i=0;i<G[x].size();i++)
	{
		int y=G[x][i];
		if(!vis[y])
		{
			dfs1(y);
		}
	}
	stk.push(x);//回溯到x(for循环完了),入栈
}

void dfs2(int x)
{
	vis[x]=1;
	for(int i=0;i<G_t[x].size();i++)
	{
		int y=G_t[x][i];
		if(!vis[y])
		{
			//temp[num++]=y;//忽略
			ans[num].push_back(y);//存第num个强连通分量的点
			dfs2(y);
		}
	}
}

void kosaraju()
{
	int con=0;
	memset(vis,0,sizeof(vis));
	for(int i=1;i<=n;i++)
	{
		if(!vis[i])dfs1(i);
	}
	memset(vis,0,sizeof(vis));
	while(!stk.empty())
	{
//		memset(temp,0,sizeof(temp));
//		num=0;
		int u=stk.top();
		stk.pop();
		if(!vis[u])
		{
//			temp[num++]=u;
			num++;
			ans[num].push_back(u);
			dfs2(u);
			con++;
//			for(int i=0;i<num;i++)cout<<temp[i]<<" ";
//			cout<<endl;
		}
	}
	memset(vis,0,sizeof(vis));
	cout<<con<<endl;
//	for(int j=1;j<=num;j++)
//	{
//		sort(ans[j].begin(),ans[j].end());
//		for(int i=0;i<ans[j].size();i++)
//		{
//			cout<<ans[j][i]<<" ";
//		}
//		cout<<endl;
//	}
	for(int i=1;i<=num;i++)sort(ans[i].begin(),ans[i].end());
	//对vector容器的num行排序
	for(int i=1;i<=n;i++)//题目的输出要求,按序按大小输出
	{
		int flag=0;
		if(!vis[i])
		{
			for(int j=1;j<=num;j++)
			{
				for(int k=0;k<ans[j].size();k++)
				{
					if(i==ans[j][k])flag=1;
					break;
				}
				if(flag)
				{
					for(int k=0;k<ans[j].size();k++)
					{
						vis[ans[j][k]]=1;
						cout<<ans[j][k]<<" ";
					}
					cout<<endl;
					break;
				}
			}
		}
	}
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++)
	{
		int x,y;scanf("%d%d",&x,&y);
		G[x].push_back(y);
		G_t[y].push_back(x);
	}

	kosaraju();
	return 0;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值