概述
判断无向图是否存在环是一个非常基础的操作,然后也对应了比较多的解法,我们用c++来做一些基础的实现。
题目给的条件是顶点个数、边的个数、边的信息。顶点的序号从0到n-1排列。
并查集解法
并查集有两个主要操作:
Union操作:将一条边对应的两个顶点放到一个连通集里,集合里的所有顶点都连通
Find操作:查询一个顶点所在集合的“老大”,并返回“老大”的编号
并查集原理
几句话解释一下为什么要用并查集,以及怎么用:
首先,并查集的特点是,将所有边的信息收录进一个parent数组,因此,当需要查询两个顶点是否连通的时候,只需要分别对两个顶点编号进行Find操作,然后比较返回值,返回值相同,拥有共同的老大,因此他们是连通的,返回值不同,则不连通。
其次,当我读取边的信息时,读入一对顶点,先查询是否连通,如果连通,说明这是这对顶点第二次连通了,因此一定存在环。就可以判定这个图中存在环。如果不连通,那么就用Union操作把他们连通,并且丢到同一个群组里。
读完所有边,如果都没出现环,则输出no,如果出现过环,则输出yes。
并查集写法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 int find (int * p, int tar) { int value = tar; while (p[value] != value) { value = p[value]; } return value; } void Union (int * p, int v1, int v2) { int r1 = find (p, v1); int r2 = find (p, v2); if (r1 != r2) p[r2] = r1; }
ok我们搞定了并查集之后,我们就可以直接完成这个题目了,上代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #include <iostream> using namespace std;int main () { int v,e,begin,end; cin >> v >> e; bool judge = false ; int * p = new int [v]; for (int i = 0 ; i < v; i++) p[i] = i; for (int i = 0 ; i < e; i++) { cin >> begin >> end; if (find (p, begin) != find (p, end)) Union (p, begin, end); else judge = true ; } if (judge) cout << "yes" ; else cout << "no" ; return 0 ; }
DFS解法
使用 DFS 可以判断一个无向图和有向中是否存在环。深度优先遍历图,如果在遍历的过程中,发现某个结点有一条边指向已访问过的结点,并且这个已访问过的结点不是上一步访问的结点,则表示存在环。
首先搞懂DFS的原理,看一下伪代码:
1 2 3 4 5 6 void DFS (顶点V) { visited[V]=true ; for (顶点V的所有邻接点W) if (!visited[W]) DFS (W); }
意思就是一次只走一条线,只要新的点有除了上一个点之外的未被访问过的其他邻接点,就往这个点走,并把这个点标记为已访问,直到没有符合条件的点了,退回到上一个点,去找上一个点的其他符合条件的点,直到退回到出发的位置。
怎么应用到本题呢,要找有没有环,就看看当前结点除了上一个点之外所有相邻的邻接点是不是都没有被访问过,如果存在被访问过的,那就说明存在环了。
对于我的修改,我会在注释里再做解释:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 #include <iostream> #include <vector> using namespace std;bool judge = false ;void DFS (int V, bool * visited,int v,int ** map) ; int main () { int v,e,begin,end; cin >> v >> e; bool * visited = new bool [v] {false }; int ** map = new int *[v]; for (int i = 0 ; i < v; i++) map[i] = new int [v]{0 }; for (int i = 0 ; i < e; i++) { cin >> begin >> end; map[begin][end] = 1 ; map[end][begin] = 1 ; } DFS (0 , visited, v, map); if (judge) cout << "yes" ; else cout << "no" ; return 0 ; } void DFS (int V, bool * visited,int v,int ** map) { visited[V] = true ; for (int i = 0 ; i < v; i++) { if (map[V][i]) { if (!visited[i]) { map[i][V] = 0 ; DFS (i, visited, v, map); } else { judge = true ; break ; } } } }
为什么找到未被访问的邻居结点,后要把这条边擦除之后再对这个点进行深搜呢。因为如果后续回退到这个邻居点的时候,以再去寻找这个邻接点的邻居,一定包括我们当前的V,那么这个时候无论如何V都已经被访问过了,所以会进入else分支,判定为有环,实际上这条边已经走过一次了,我们把它当成了两条不同的边。
这么做的本质其实就是把map[i][j]和map[j][i]联系起来,只要map[i][j]走过了,实质上也就是map[j][i]走过了,为了防止再走一遍,我们抹除这条边。
Author:
Alpaca Zhang
License:
Copyright (c) 2019 CC-BY-NC-4.0 LICENSE
Slogan:
Do you believe in DESTINY ?