求最小环 —— 并查集 与 Floyd

对于一个图,如何求出其中的最小环(不包括一元环)?

很显然,对于一个无向图,每一条边都是一个二元环;对于有向图,可以考虑从每一个点出发,用DFS求出它到自己的距离,如果遍历了$N$个点仍未便利到自己,则该点不在环中。但是这样做的复杂度非常感人……如果没有分析错的话最坏应该是$O(n^2 m)$

可以考虑有一些优化, 对于一个点,如果它在一个环中,那么它在该环上dfs过的点一定在环中。在回溯一个点的过程中,如果搜到环,那么在搜下一个点的时候就不清空他们的标记数组。对于搜不到环的点,它的入度一定为一,如果不为一,那么删除它入边所连的入度为零的点后入度为零。所以可以预处理,删除入度为零的点和它所连的边(如果后面还有用就标记),然后再按照上述方式dfs,时间复杂度为$O(nm)$

但是对于稠密图,复杂度还是太高,甚至高过$O(n^3)$,这听起来是不是有些耳熟? 没错,Floyd。

用 $k$ 作为当前搜索点,不断求出其它点的最短路径和路经所在环长,即

   $dis[i][j] = std::max(dis[i][j], dis[i][k], dis[j][k]), ans = std::max(ans, dis[i][j]+G[k][i]+G[j][k]); // G[][] 为邻接矩阵$

时间复杂度为 $O(N^3)$

当然,在$N>1000$的稠密图中,这种方法的复杂度依然很感人。

由于连边的关系也可以看作是树中“父与子”的关系,则可以使用并查集,通过路径压缩求出在某一图中的环,若存在两个连边的点在同一并查集中,那么一定有环,环的权值为两点到祖先的权值和加1。

为什么这样是对的呢? 在一个路径已经被压缩的树中,任意两节点在图中一定相连,当某一点的父节点成为该点的子节点时,该点一定在环上。只要没有重边,那么该方法一定适用。

Floyd 代码:

int G[N][N], dis[N][N];
int main(){
    for(int k = 1; k <= n; k++){
    for(int i=1;i<=k-1;i++)    
         for(int j=i+1;j<=k-1;j++)
              ans = std::max(ans, dis[i][j]+G[k][i]+G[k][j]);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
          dis[i][j] = std::max(dis[i][j], G[i][k]+G[k][j]);                   
    }
   //...                             
    return 0;
}

并查集代码

 1 int find(int x){ 
 2     if(f[x] != x){
 3         int o = f[x];
 4         f[x] = find(f[x]);
 5         d[x] += d[o];
 6     }
 7     return f[x];
 8 }
 9 int main(){
10   scanf("%d", &n);
11   for(int i=1;i<=n;i++) f[i] = i;
12   for(int i=1;i<=n;i++){
13     scanf("%d", &x);
14     int a = find(i), b = find(x);
15     if(a != b) { f[a] = b; d[i] = d[x]+1; }
16     else  min = std::min(min, d[x] + d[i] + 1);
17   }
18   printf("%d
" ,min);
19   return 0;
20 }

 来写个裸题练练吧!https://www.luogu.org/problemnew/show/P2661

原文地址:https://www.cnblogs.com/fseject-2002/p/9724557.html