每周一算法:无向图的最小环

题目链接

观光之旅

题目描述

给定一张无向图,求图中一个至少包含 3 3 3 个点的环,环上的节点不重复,并且环上的边的长度之和最小。

该问题称为无向图的最小环问题。

你需要输出最小环的方案,若最小环不唯一,输出任意一个均可。

输入格式

第一行包含两个整数 N N N M M M,表示无向图有 N N N 个点, M M M 条边。

接下来 M M M 行,每行包含三个整数 u , v , l u,v,l uvl,表示点 u u u 和点 v v v 之间有一条边,边长为 l l l

输出格式

输出占一行,包含最小环的所有节点(按顺序输出),如果不存在则输出 No solution.

样例 #1

样例输入 #1

5 7
1 4 1
1 3 300
3 1 10
1 2 16
2 3 100
2 5 15
5 3 20

样例输出 #1

1 3 5 2

提示

【数据范围】

1 ≤ N ≤ 100 1≤N≤100 1N100,
1 ≤ M ≤ 10000 1≤M≤10000 1M10000,
1 ≤ l < 500 1≤l<500 1l<500

算法思想

根据题目描述,求的是无向图中的最小环,要求环中至少包含 3 3 3 个节点,且环上的节点不重复。当边权都为正数式,最小环中的节点一定不会重复,否则就不是最小环了,如下图所示。

在这里插入图片描述

求最小环的长度

无向图的最小环问题可以使用「Floyd」算法解决。基本思想是:

  • 当外层循环 k k k刚开始时, d [ i , j ] d[i,j] d[i,j]保存着从节点 i i i j j j经过编号不超过 k − 1 k-1 k1的最短路径长度
  • 此时,如果引入新节点 k k k构成了环,那么环的长度为 d [ i , j ] + g [ j ] [ k ] + g [ k ] [ i ] d[i,j]+g[j][k]+g[k][i] d[i,j]+g[j][k]+g[k][i],如下图所示:
    在这里插入图片描述
    那么, m i n { d [ i , j ] + g [ j ] [ k ] + g [ k ] [ i ] } min\{d[i,j]+g[j][k]+g[k][i]\} min{d[i,j]+g[j][k]+g[k][i]},其中 1 ≤ i < j < k 1\le i\lt j\lt k 1i<j<k,就是满足以下两个条件的最小环长度:
    • 由编号不超过 k k k的节点构成
    • 经过节点 k k k

1 ∼ n 1\sim n 1n枚举 k k k,取上式的最小值,就可以得到整张图的最小环长度。

求最小环上的节点

除了计算最小环之外,题目还要求记录最小环的上所有节点。当更新最小环时,环上的节点包含 i i i i i i j j j之间最短路上的节点,以及 i i i k k k。那么如何得到 i i i j j j之间最短路上的节点

使用Floyd算法计算最短路时,当 d [ i ] [ j ] > d [ i ] [ k ] + d [ k ] [ j ] d[i][j]>d[i][k]+d[k][j] d[i][j]>d[i][k]+d[k][j]时,可以更新节点 i i i j j j的最短路,同时记录节点 i i i j j j的最短路是经过 k k k点中转得到的,不妨记 p o s [ i , j ] = k pos[i,j]=k pos[i,j]=k

那么经过节点 i i i j j j的最短路径的可以分成两个部分:

  • 节点 i i i k k k的最短路
  • 节点 k k k j j j的最短路

可以通过递归的方式,分别获取这两部分经过的节点。

时间复杂度

Floyd算法内可以同时求最小环和最短路,因此时间复杂度为 O ( n 3 ) O(n^3) O(n3)

代码实现

#include <bits/stdc++.h>
using namespace std;
const int N = 105, INF = 0x3f3f3f3f;
int n, m;
int g[N][N], d[N][N];
int pos[N][N];//pos[i][j]表示i和j最短路经过k点中转
vector<int> path; //保存最小环路径
void get_path(int i, int j)
{
    if(pos[i][j] == 0) return; //i和j之间不存在中转点
    int k = pos[i][j]; //k是i和j最最短路的中转点
    get_path(i, k); //递归后取i-k最短路上的节点
    path.push_back(k);
    get_path(k, j); //递归后取k-j最短路上的节点
}
int main()
{
    cin >> n >> m;
    memset(g, 0x3f, sizeof g); //初始化邻接矩阵
    for(int i = 1; i <= n; i ++) g[i][i] = 0;
    while (m -- )
    {
        int a, b, c;
        cin >> a >> b >> c;
        g[a][b] = g[b][a] = min(g[a][b], c); //无向图,可能存在重边
    }
    int ans = INF;
    memcpy(d, g, sizeof d); //初始化最短路
    for(int k = 1; k <= n; k ++)
    {
        //计算由编号不超过k的节点构成的最小环
        for(int i = 1; i < k; i ++) //枚举环中的点
            for(int j = i + 1; j < k; j ++)
            {
                if((long long)d[i][j] + g[j][k] + g[k][i] < ans) //出现更小的环
                {
                    ans = d[i][j] + g[j][k] + g[k][i];
                    path.clear(); //清除之前的最小环路径
                    path.push_back(k); //k-i-最短路路径-j
                    path.push_back(i);
                    get_path(i, j);//获取i-j最短路径上的节点
                    path.push_back(j);
                }
            }
        //计算最短路
        for(int i = 1; i <= n; i ++)
            for(int j = 1; j <= n; j ++)
                if(d[i][j] > d[i][k] + d[k][j])
                {
                    d[i][j] = d[i][k] + d[k][j];
                    pos[i][j] =k; //记录最短路中转点
                }
    }
    if(ans == INF) puts("No solution.");
    else //存在最小环
    {
        for(int i : path) cout << i << " ";
    }
}
  • 12
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
要使用Floyd算法找到无向图的最小,可以先使用Floyd算法求出图中任意两个顶点之间的最短路径,然后再遍历所有的三角形(三个顶点组成的)找到最小的。 以下是一个使用Python实现Floyd算法求解无向图最小的示例: ```python INF = float('inf') def floyd(graph): n = len(graph) dist = [[INF] * n for _ in range(n)] # 初始化距离矩阵 next_vertex = [[-1] * n for _ in range(n)] # 记录最短路径上的下一个顶点 # 初始化距离矩阵和下一个顶点矩阵 for i in range(n): for j in range(n): if i == j: dist[i][j] = 0 elif graph[i][j] != 0: dist[i][j] = graph[i][j] next_vertex[i][j] = j # 使用Floyd算法计算最短路径和下一个顶点 for k in range(n): for i in range(n): for j in range(n): if dist[i][j] > dist[i][k] + dist[k][j]: dist[i][j] = dist[i][k] + dist[k][j] next_vertex[i][j] = next_vertex[i][k] return dist, next_vertex def find_min_cycle(graph): n = len(graph) min_cycle_length = INF dist, next_vertex = floyd(graph) # 遍历所有三角形,找到最小的 for u in range(n): for v in range(u+1, n): for w in range(v+1, n): if graph[u][v] != 0 and graph[v][w] != 0 and graph[w][u] != 0: cycle_length = dist[u][v] + dist[v][w] + dist[w][u] - 2 * (graph[u][v] + graph[v][w] + graph[w][u]) if cycle_length < min_cycle_length: min_cycle_length = cycle_length return min_cycle_length ``` 使用上述代码,可以通过传入一个邻接矩阵表示的无向图,来计算出最小的长度。其中,`graph`是一个二维列表,表示图的邻接矩阵,如果两个顶点之间没有边,则对应位置的值为0。 注意,这里的最小指的是由三个顶点组成的,并且每个顶点都与相邻的两个顶点相连。如果图中不存在这样的,则返回无穷大(INF)。 希望能够帮助到你!如有更多问题,请继续提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

少儿编程乔老师

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值