无向图判环(DFS与并查集)

博客围绕无向图判环展开,以XP学长校园漫步为背景给出题目。输入多组测试数据,包含建筑数量和大路数量等信息,输出判断是否会重复到达建筑。介绍了两种判环方法,DFS是深度优先遍历时若有已访问结点则有环,并查集是边的邻接点在同一集合则有环。

无向图判环(DFS与并查集)

ps:一道本校院赛题,最后一小时应该开这道题的,导致到比赛结束这道题都看都没看

XP的校园漫步


题目描述


众所周知,XP学长即将毕业了,所以他觉得在校园里来一次漫步,在学校中有N个标志性的建筑,XP学长并不喜欢走小路,因此他会随机的选择某条大路从某个建筑走到另一个建筑,XP学长当然想要走遍校园内的所有建筑,但他会随机的选择某一条路去漫步,当然XP学长走完某一条路,绝对不会走这一条路,也就是说,如果XP学长当前所在建筑为v,他选择走v->u的某一条大路e,到达u建筑后,他在之后的漫步里不会再选择这条路,但是这仍然可能导致他到达某个建筑两次,XP学长表示他不想观看同样的建筑两次,因此他想问问你,如果他随机的选择这些路去漫步,是否会出现到达一个建筑两次或两次以上的现象,当然了,XP学长可以选择任意的建筑作为他的起始点。


输入


输入包含多组测试用例,第一行输入一个T表示测试数据组数,(1<=T<=10),每组测试数据首先输入有两个整数N,M,N表示校园内有N个建筑(分别用1-N来标记它们),M表示校园内有M条大路,它们连接着这些建筑。(2<=N<=105,1<=M<=105),接下来M行每行两个整数u,v,表示u,v之间有一条大路(1<=u,v<=N),每条大路都是无向的,保证图中不出现自环现象,不保证图是连通的。


输出


如果XP学长随机漫步,会出现上述现象,请输出YES,否则输出NO。

样例输入

1
4 3
1 2
2 3
1 3

样例输出

YES

提示
在样例中,XP学长可以选择1作为他的起始建筑,那么他可能走这样的一条路径:1->2->3->1,此时XP学长到达了建筑1两次,因此你需要输出YES。

题意还是很简单,就是判断无向图是否存在环!!

一、DFS:

深度优先遍历时,若存在已经访问过的结点(在正在访问的子图中)则表示有环存在

#include<bits/stdc++.h>
using namespace std;
int vis[100010];           //判断是否已经访问过
vector<int>mp[100010];        //类似于邻接链表
int dfs(int u,int pre)      //u表示当前访问的结点,pre为它的前驱
{
    int len=mp[u].size();
    for(int i=0; i<len; i++)
    {
        int v=mp[u][i];
        if(v==pre)
            continue;
        if(vis[u]==0)
        {
            vis[u]=1;
            if(dfs(v,u))
                return 1;    //存在环
        }
        else
            return 1;   //存在环
    }
    return 0;
}
int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        memset(mp,0,sizeof(mp));
        int n,m;
        scanf("%d%d",&n,&m);
        for(int i=1; i<=m; i++)
        {
            int u,v;
            scanf("%d%d",&u,&v);
            mp[u].push_back(v);     //邻接点
            mp[v].push_back(u);     //同上
        }
        int flag=0;
        for(int i=1; i<=n; i++)
        {
            if(vis[i]==0)
            {
                vis[i]=1;
                if(dfs(i,0))
                {
                    flag=1;
                    break;
                }
            }
        }
        if(flag==1)
            printf("YES\n");    //表示存在环
        else
            printf("NO\n");    //表示不存在环
    }
}

二、并查集:

若出现一条边的邻接点在同一个集合里,则可证明有环存在

#include<bits/stdc++.h>
using namespace std;
int par[100010];        //该结点的父亲
int get_par(int a)    //找该结点的父亲
{
    if(par[a]!=a)
        par[a]=get_par(par[a]);
    return par[a];
}
int Merge(int u,int v)    //合并
{
    int par_u=get_par(u);
    int par_v=get_par(v);
    if(par_u==par_v)
        return 0;    //表示在同一集合里面
    par[par_v]=par_u;
    return 1;
}
int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        int n,m;
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++)
            par[i]=i;    //初始化
        int flag=0;
        for(int i=1;i<=m;i++)
        {
            int u,v;
            scanf("%d%d",&u,&v);
            if(Merge(u,v)==0)    //u,v在同一集合里面
            {
                flag=1;     //有环存在
            }
        }
        if(flag==1)
            printf("YES\n");
        else
            printf("NO\n");
    }
    return 0;
}

### 无向图的算法实现 在无向图断是否存在可以通过多种方法来完成,常见的方法包括深度优先搜索(DFS)、广度优先搜索(BFS),以及并查集等。下面详细介绍基于 DFS 的解决方案。 #### 基于深度优先搜索 (DFS)检测 通过深度优先遍历可以有效地检测无向图中的。核心思想是在访问节点的过程中记录当前路径上的父节点,并检查是否有边连接到已经访问过的节点且该节点不是其直接父节点的情况。如果发现这样的情况,则说明存在。 具体实现逻辑如下: - 使用布尔 `visited` 来标记已访问的节点。 - 对每一个未被访问的节点执行递归调用。 - 如果遇到一个已经被访问过但并非当前节点的父亲节点,则表明找到了一条回路。 下面是 C++ 中的一个简单实现: ```cpp #include <iostream> #include <vector> using namespace std; class Graph { int V; // Number of vertices vector<vector<int>> adj; public: Graph(int V); void addEdge(int v, int w); bool isCyclicUtil(int v, vector<bool>& visited, int parent); bool isCyclic(); }; Graph::Graph(int V) { this->V = V; adj.resize(V); } void Graph::addEdge(int v, int w) { adj[v].push_back(w); adj[w].push_back(v); } bool Graph::isCyclicUtil(int v, vector<bool> &visited, int parent) { visited[v] = true; for(auto i : adj[v]) { if(!visited[i]){ if(isCyclicUtil(i, visited, v)) return true; } else if(parent != i){ return true; } } return false; } bool Graph::isCyclic() { vector<bool> visited(V, false); for(int u=0;u<V;u++) { if (!visited[u]) if (isCyclicUtil(u, visited, -1)) return true; } return false; } ``` 上述代码定义了一个类 `Graph` 并实现了函 `isCyclic()` 和辅助函 `isCyclicUtil()` 。当调用 `isCyclic()` 方法时会返回布尔值指示图中是否存在[^1]。 #### Java 版本的实现 对于同样的问题,在 Java 编程语言下也可以采用类似的策略解决。这里给出一段完整的测试程序作为参考: ```java import java.util.*; public class CycleDetection { private Map<Integer, List<Integer>> adjacencyList = new HashMap<>(); private Set<Integer> visitedNodes = new HashSet<>(); public void addEdge(int source, int destination) { adjacencyList.putIfAbsent(source, new ArrayList<>()); adjacencyList.get(source).add(destination); adjacencyList.putIfAbsent(destination, new ArrayList<>()); adjacencyList.get(destination).add(source); } public boolean detectCycle() { for(Integer node : adjacencyList.keySet()) { if( !visitedNodes.contains(node)){ if(dfsDetectCycle(node,-1)) return true; } } return false; } private boolean dfsDetectCycle(int currentNode,int parentNode){ visitedNodes.add(currentNode); for(Integer neighbor : adjacencyList.getOrDefault(currentNode,new ArrayList<>())){ if(!visitedNodes.contains(neighbor)){ if(dfsDetectCycle(neighbor,currentNode)) return true; }else{ if(neighbor!=parentNode) return true; } } return false; } public static void main(String[] args)throws Exception{ String filename="D:\\Desktop\\graph5.txt"; CycleDetection cd=new CycleDetection(); Scanner sc=new Scanner(new File(filename)); while(sc.hasNext()){ int src=sc.nextInt(),dest=sc.nextInt(); cd.addEdge(src, dest); } sc.close(); System.out.println("是否存在:"+cd.detectCycle()); } } ``` 此段代码展示了如何读取文件构建邻接表形式存储的图结构,并利用递归方式查找其中可能存在的任何循关系[^4]。 ### 总结 无论是使用哪种编程语言或者据结构表示法,关键是理解基本原理——即借助栈特性模拟探索过程的同时维护必要的状态信息以便识别重复访问情形下的异常状况。这种方法能够满足时间复杂度 O(N+M)[^2]的要求,适用于大多实际应用场景。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值