无向图的路径判断

题目描述:

现有一个由1,2,3,4...,n个点,m条边组成的无向图。请判断给定的t组数据x, y(x表示起点,y表示终点),针对每组数据是否存在一条通路,如果存在则输出Yes,否则输出No

输入:

第1行:n m t (n、m、t使用空格隔开,其中n表示结点的数量,结点由1开始编号;m表示边的数量;t表示待判断的起点、终点有多少对。2<=n、m、t<=1000)
接下来的m行,每行一对整数a b,a、b使用空格隔开,表示结点a与结点b之间存在一条边
接下来的t行,每行一对整数x y, x、y使用空格隔开,如果结点x与结点y之间存在一条通路则输出Yes,否则输出No;每行一个。

输出:

如果指定的起点与终点之间存在一条通路则输出Yes,否则输出No,每行一个

样例输入:

5 3 4
1 2
3 4
1 4
2 3
2 4
3 5
2 5

样例输出:

Yes
Yes
No
No

思路:

这道题十分的朴素,简单,基本上是一个模板题,但是,这次我会介绍三个做法,其中最后一个做法是拓展,可以做了解哈

1 - [BFS 做法]

这个十分简单啊,就是将每个点都bfs一遍,它连通块(经过的点)上的所有点都记为1,最后询问的时候,看他们之间是否记为1,是一的话就说明这两个是联通着的,输出“Yes"即可。

代码:

#include <bits/stdc++.h>
using namespace std;
 
vector<int> edge[1005];
int n,m,t;
queue <int> pancheng;
int ans[1005][1005];
int vis[1005];
 
void bfs(int wb){
    memset(vis,0,sizeof(vis));
    pancheng.push(wb);
    vis[wb] = 1;
    while(pancheng.size()){
        int tmp = pancheng.front();
        pancheng.pop();
        ans[wb][tmp] = 1;
        ans[tmp][wb] = 1;
        for(int i = 0;i < edge[tmp].size();i++){
            int to = edge[tmp][i];
            if(vis[to] == 0){
                vis[to] = 1;
                pancheng.push(to);
            }
        }
    }
}
 
int main(){
    scanf("%d%d%d",&n,&m,&t);
    for(int i = 1;i <= m;i++){
        int u,v;
        scanf("%d%d",&u,&v);
        edge[u].push_back(v);
        edge[v].push_back(u);
    }
     
    for(int i = 1;i <= n;i++){
        bfs(i);
    }
     
    for(int i = 1;i <= t;i++){
        int x,y;
        scanf("%d%d",&x,&y);
        if(ans[x][y] == 1){
            cout <<"Yes"<<endl;
        }
        else{
            cout<<"No"<<endl;
        }
    }
    return 0;
} 

2 - [DFS 做法]

这个做法也是十分的简单,先存图,询问的时候,dfs x 点,在dfs的时候,遍历周围每一个点,再将每一个点dfs一次,如果再dfs的路径上遇到了 y 点,可以直接全部停止,return true

代码:

#include <bits/stdc++.h>
using namespace std;

vector<int> edge[1005];
int n,m,t;
int vis[1005];

bool dfs(int wb,int mb){
	vis[wb] = 1;
	if(wb == mb){
		return true;
	}
	else{
		int len = edge[wb].size();
		for(int i = 0;i < len;i++){
			int to = edge[wb][i];
			if(vis[to] == 0){
				vis[to] = 1;
				if(dfs(edge[wb][i],mb)){
					return true;
				}
				vis[to] = 0;	
			}
		}
	}
	return false;
}

int main(){
	scanf("%d%d%d",&n,&m,&t);
	for(int i = 1;i <= m;i++){
		int u,v;
		scanf("%d%d",&u,&v);
		edge[u].push_back(v);
		edge[v].push_back(u);
	}
	
	
	for(int i = 1;i <= t;i++){
		int x,y;
		scanf("%d%d",&x,&y);
		memset(vis,0,sizeof(vis));
		if(dfs(x,y)){
			cout<<"Yes"<<endl;
		}
		else{
			cout<<"No"<<endl;
		}
	}
	return 0;
} 

3 - [并查集 做法]

这个做法只做简单的拓展,代码看起来简单易懂,且速度快,很好写。
但是其中的原理,需要细细消化。

并查集,可以简单的理解成,可以走通的所有点,都聚集在一个集合里(这个集合是树的形式),要判断这两个点是否联通,只用判断它们是不是在一棵树里,即它们是不是同一个树根

其实并查集的道理也就那样,就是上面讲述的一样。但是有一种特殊的情况,假如,这棵树运气特别不好,这棵树恰好连成一条链,那么查找的话就要将这棵树全部遍历一遍,时间复杂度还是很大。此处我们就可以用一种方法——————路径压缩

路径压缩的原理也是很简单,就是将其父亲节点的根变成这个点的父节点即可。

其实并查集就这些了,下面给出完整代码

代码:

#include <bits/stdc++.h>
using namespace std;
 
int n,m,t;
int fa[1005];//存每个节点的父节点,根的父节点就是它自己(并查集一般都是这么写的) 
 
int find_root(int x){//找这个点根 
    if(fa[x] == x){
        return x;
    }
    else{
        int ret = find_root(fa[x]);
        fa[x] = ret;
        return ret;
    }
}
 
void merge(int x,int y){//合并两个点,将其所在的两个树的根连在一起 
    int rx = find_root(x);
    int ry = find_root(y);
    if(rx != ry){
        fa[rx] = ry;
    }
}
 
int main(){
    scanf("%d%d%d",&n,&m,&t);
    for (int i = 1;i <= n;i++){
        fa[i] = i;
    }//一开始所有点的父节点都是它自己(根的父节点就是它自己) 
    for (int i = 1;i <= m;i++){
        int a,b;
        scanf("%d%d",&a,&b);
        merge(a,b);//两个点之间有联通,将其所在的两个树相连 
    }
    for (int i = 1;i <= t;i++){
        int a,b;
        scanf("%d%d",&a,&b);
        if(find_root(a) == find_root(b)){//如果是同一个根(即同一棵树) 
            cout<<"Yes"<<endl;
        }
        else{
            cout<<"No"<<endl;
        }
    }
    return 0;
}
posted @ 2022-04-16 22:07  XDFZ武斌  阅读(218)  评论(0)    收藏  举报