有向带环图的各种遍历处理

《有向带环图的各种遍历处理》

防坑大招:先上一张母上大人设计的有向有环图,作为测试数据,(见附图)

要求:找到所有能到达4的节点x,can[x]数组中置对应值为1

自环

建边时,通过x!=y, 将自环边直接滤掉

for(int i=1;i<=m;i++)
    {
        int x,y;scanf("%d%d",&x,&y);
        if(x!=y) {v[x].push_back(y);p[y].push_back(x);}
    } 

反向DFS,OK

从终点t出发能到达的节点,此时t相当于根,等同于:以t为出发点,判断有向图的连通性。

反向建图,采用有向图的DFS遍历,所能达到的点构成一颗生成树。

   for(int i=1;i<=m;i++)
    {
        int x,y;scanf("%d%d",&x,&y);
        if(x!=y) {v[x].push_back(y);p[y].push_back(x);}    //y-x,建反图
    } 
    scanf("%d%d",&s,&t);can[t]=1;
    find(t);                               //从终点开始遍历


void find(int x)
{
    vis[x]=1;can[x]=1;                      //先置can标志,代表从该节点可以到达终点
    for(int i=0;i<p[x].size();i++)
    {
        if(!vis[p[x][i]])     find(p[x][i]);
    }
}

正向DFS,无法正确处理

正向DFS遍历判断连通性,代表从起点所能达到节点,需要遍历结束才能确认是否能达到终点t,再由t回溯时向前置can标志。

在DFS结束后置can标志,如果某点的孩子can,则它的父亲也can.

但是,can数组不对!遍历时每个节点按DFS序走,不能重复走!

成环的节点必然会重复走。比如:图中的2-3-2-4, 3节点可到达4,但必须二次经过2才可以!

for(int i=1;i<=n;i++) if(!vis[i]) find(i); //main函数

void find(int x)
{
    vis[x]=1;
    if(x==t) return;
    for(int i=0;i<v[x].size();i++)
    {
        if(!vis[v[x][i]]) {find(v[x][i]);}    //问题出在!vis[]的判断,节点不可重复走
        if(can[v[x][i]]) can[x]=1;            //DFS结束后反向置can标志
    }
}

环,重复走节点

图中2-3-2,   3-13-3,2-5-6-2、6-7-8-7等,节点2、3、6等重复走。

引入out数组,计算每个节点的出度,只有还有边可走,就继续,不管该节点有没有访问过。

for(int i=1;i<=m;i++)
{
        int x,y;scanf("%d%d",&x,&y);
        if(x!=y) {v[x].push_back(y);out[x]++;p[y].push_back(x);}  //读边时计算出度out
 } 

   
for(int i=1;i<=n;i++) if(!vis[i]) find(i);

void find(int x)
{
    vis[x]=1;
    if(x==t) return;
    for(int i=0;i<v[x].size();i++)
    {
        if(out[v[x][i]]>0||!vis[v[x][i]]) {out[x]--;find(v[x][i]);} //加入out判断,--
        if(can[v[x][i]]) can[x]=1;
    }
}

此种写法相当于欧拉通路的判断,每个边走一次

然后栈中的点都是可以到达的,弹栈置can数据值

然而,还不如直接用欧拉通路的算法更简洁。。。

void dfs(int i)
{
	for (int j=0; j<v[i].size();j++)   //枚举边
	{
		if (!vis[i][j])              //边是否访问过
		{	
		 	vis[i][j]=1;
			dfs(v[i][j]);
		}
	} 
	stk.push(i);   
}
 

环,重复走边

1-9-2-10-9-2-4,  9-2边重复走。其实,也相当于有割边唉~~~

边允许重复走,top数据不起作用。尝试清除vis[]标志,允许重复走。结果可以,没有WA,但TLE!

多次扫描,效率太低~~

void find(int x)
{
    if(x==t) return;
    if (can[v[x][i]]==1) return;   //剪枝
    for(int i=0;i<v[x].size();i++)
    {
    	
        if(!vis[v[x][i]]){
		vis[v[x][i]] =1;
		find(v[x][i]);
		vis[v[x][i]] =0;     //清标志
	}
        if(can[v[x][i]]) can[x]=1;
    }
}

for(int i=1;i<=n;i++) if(!can[i]) find(i);

visit[]数组访问标志多值,代表N次不同进入

visit[x]=0,1,2…          其中:>=2代表多次访问,成环;

此方法一般用于判断是否有环,简单环

然而。。。只有40分

void find(int x)
{
    vis[x]++;
    for(int i=0;i<v[x].size();i++)
    {
        if(vis[v[x][i]]<2) find(v[x][i]); 
        if(can[v[x][i]]) can[x]=1;	
    }
    vis[x]++;
}


for(int i=1;i<=n;i++) if(vis[i]<2) find(i);

 

Tarjan强连通分量

先缩点,按有向无环图遍历。

对于同一个连通分量中的点,只要有一个点到达,则连通分量中所有点全可以到达。

void tarjan(int x)
{
	sa.push(x);ins[x]=1;
	dfn[x]=low[x]=++indexs;
	for(int i=0;i<v[x].size();i++)
	{
		if(!dfn[v[x][i]]) 
		{
			tarjan(v[x][i]);
			low[x]=min(low[x],low[v[x][i]]);
		}
		else if(ins[v[x][i]]) low[x]=min(dfn[v[x][i]],low[x]);
	}
	if(dfn[x]==low[x])
	{
		while(1)
		{
			int tmp=sa.top();sa.pop();ins[tmp]=0;
			belong[tmp]=x;
			if(tmp==x) break;
		}
	}
}
void find(int x)
{
	vis[x]=1;
	for(int i=0;i<vi[x].size();i++)
	{
		if(!vis[vi[x][i]]) find(vi[x][i]);
		if(can[vi[x][i]]) can[x]=1;
	}
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++)
	{
		int x,y;scanf("%d%d",&x,&y);
		if(x!=y) {v[x].push_back(y);p[y].push_back(x);}
	} 
	scanf("%d%d",&s,&t);can[t]=1;
	for(int i=1;i<=n;i++) if(!dfn[i]) tarjan(i);
	for(int i=1;i<=n;i++)
	{
		for(int j=0;j<v[i].size();j++)
		{
			if(belong[i]!=belong[v[i][j]])	
			{
				vi[belong[i]].push_back(belong[v[i][j]]);	
			}	
		}
	} 
	can[belong[t]]=1;find(belong[s]);
	for(int i=1;i<=n;i++) if(can[belong[i]]) can[i]=1;

交上去AC掉了,还行,跑的挺快。除了代码量较大别的都很完美。

有向带环图正向DFS的终极大法!!!

 

 

 

 

    原文作者:数据结构之图
    原文地址: https://blog.csdn.net/Mmm040403/article/details/83687775
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞