floyd求最小环

floyd求最小环

1 定义:

通常来说最小环是针对有向图而言

从一个点出发,经过一条简单路径回到起点成为环.图的最小环就是所有环中长度最小的.

2.怎样求最小环呢?

1传统的解决方法(dijkstra):
        任意一个环的权值,我们都可以看成两个有边相连的结点i、j的直接距离加上i、j间不包含边(边i->j)的最短路径。求最短路径我们第一个想到的就是Dijkstra算法。而Dijkstra所求的是一个点到所有点的最短距离。用Dijkstra所求的i、j的最短距离一定是i、j的直接距离(如果i,j连通),所以我们需要先将i、j的边从图中删除(若i,j不连通,则不用删除),再用Dijkstra求新图中i、j的最短距离即可。所以我们每次在图中选取一条边,把它从图中删掉.然后对删掉的那条边所对应的2点进行Dijkstra,也就是m次Dijkstra。

2.floyd求最小环:

        抛开Dijkstra算法,进而我们想到用Floyd算法。我们知道,Floyd算法在进行时会不断更新矩阵dist(k)。设dist[k,i,j]表示从结点i到结点j且满足所有中间结点,它们均属于集合{1,2,⋯ ,k}的一条最短路径的权。其中dist[0,i,j ]即为初始状态i到j的直接距离。对于一个给定的赋权有向图, 求出其中权值和最小的一个环。我们可以将任意一个环化成如下形式:u->k->v ->(x1-> x2-> ⋯ xm1)-> u(u与k、k与v都是直接相连的),其中v ->(x1-> 2-> ⋯ m)-> u是指v到u不经过k的一种路径。

        在u,k,v确定的情况下,要使环权值最小, 则要求 (x1一>x2->⋯一>xm)->u路径权值最小.即要求其为v到u不经过k的最短路径,则这个经过u,k,v的环的最短路径就是:[v到u不包含k的最短距离]+dist[O,u,k]+dist[O,k,v]。我们用Floyd只能求出任意2点间满足中间结点均属于集合{1,2,⋯ ,k}的最短路径,可是我们如何求出v到u不包含k的最短距离呢?
         现在我们给k加一个限制条件:k为当前环中的序号最大的节点(简称最大点)。因为k是最大点,所以当前环中没有任何一个点≥k,即所有点都<k。因为v->(x1->x2->......xm)->u属于当前环,所以x1,x2,⋯ ,xm<k,即x1,x2.⋯。xm≤k一1。这样,v到u的最短距离就可以表示成dist[k一1 ,u,v]。dist[k一1,v,u]表示的是从v到u且满足所有中间结点均属于集合{1,2,⋯ ,k一1}的一条最短路径的权。接下来,我们就可以求出v到u不包含k的最短距离了。这里只是要求不包含k,而上述方法用的是dist[k一1,v,u],求出的路径永远不会包含k+l,k+2,⋯ 。万一所求的最小环中包含k+1,k+2,⋯ 怎么办呢?的确,如果最小环中包含比k大的节点,在当前u,k,v所求出的环显然不是那个最小环。然而我们知道,这个最小环中必定有一个最大点kO,也就是说,虽然当前k没有求出我们所需要的最小环,但是当我们从k做到kO的时候,这个环上的所有点都小于kO了.也就是说在k=kO时一定能求出这个最小环。我们用一个实例来说明:假设最小环为1—3—4—5—6—2—1。的确,在u=l,v=4,k=3时,k<6,dist[3,4,1]的确求出的不是4—5—6—2—1这个环,但是,当u=4,v=6,k=5或u=5,v=2,k=6时,dist[k,v,u]表示的都是这条最短路径.所以我们在Floyd以后,只要枚举u.v,k三个变量即可求出最小环。时间复杂度为O(n3)。我们可以发现,Floyd和最后枚举u,v,k三个变量求最小环的过程都是u,v,k三个变量,所以我们可以将其合并。这样,我们在k变量变化的同时,也就是进行Floyd算法的同时,寻找最大点为k的最小环。

3.模板

#include<algorithm>
using namespace std;

const int MAXN=105;
const int INF=10000000;

int dist[MAXN][MAXN],g[MAXN][MAXN];
int fa[MAXN][MAXN],path[MAXN];

int n,m,num,minc;

void Floyd()
{
     int i,j,k,p,tmp;
     minc=INF;
     for(k=1;k<=n;k++)
     {
         for(i=1;i<k;i++)
          for(j=i+1;j<k;j++)
          {
              tmp=dist[i][j]+g[i][k]+g[k][j];
              if(tmp<minc) //找到更优解
              {
                  minc=tmp;
                  num=0;
                  p=j;
                  while(p!=i) //逆向寻找前驱结点直到找到最前面的i,i->…->j
                  {
                        path[num++]=p;
                        p=fa[i][p];//fa[i][j]保存的不是k,而是fa[k][j].
                  }
                  path[num++]=i;
                  path[num++]=k;
              }
          }
         for(i=1;i<=n;i++)
          for(j=1;j<=n;j++)
          {
             tmp=dist[i][k]+dist[k][j];
             if(dist[i][j]>tmp)
             {
                 dist[i][j]=tmp;
                 fa[i][j]=fa[k][j];
             }
          }
     }
}

int main()
{
    int i,j,u,v,w;
    while(scanf("%d%d",&n,&m)!=EOF)
    {
          for(i=1;i<=n;i++)
           for(j=1;j<=n;j++)
           {
               g[i][j]=INF;
               dist[i][j]=INF;
               fa[i][j]=i;
           }
          while(m--)
          {
               scanf("%d%d%d",&u,&v,&w);
               w=min(g[u][v],w);          //处理重边
               g[u][v]=g[v][u]=dist[u][v]=dist[v][u]=w;
          }
          Floyd();
          if(minc==INF)
               printf("No solution.\n");
          else
          {
               printf("%d",path[0]);
               for(i=1;i<num;i++)
                   printf(" %d",path[i]);
               printf("\n");
          }
    }
    system("pause");
    return 0;
}



<think>我们被要使用C++实现Floyd算法来最小环问题。最小环问题是指在一个图中,到一个环(至少包含3个顶点),使得环上所有边的权重之和最小。 Floyd算法通常用于解任意两点之间的最短路径。在最小环时,我们可以利用Floyd算法的中间结果。基本思路如下: 1. 使用Floyd算法计算任意两点间的最短路径,同时记录中间过程。 2. 在Floyd算法执行过程中,当更新最短路径时,检查是否存在一个经过顶点k的最小环。具体来说,当k作为中间节点时,我们考虑所有小于k的顶点i和j(为了不重复计算,通常要i,j<k),那么由i到j的最短路径(此时中间节点只经过小于k的节点)加上边j->k和k->i(即从j到k和k到i的边)就构成了一个环。这个环的大小为:dist[i][j] + graph[j][k] + graph[k][i](注意:这里graph是原始的邻接矩阵,而dist是经过k-1次迭代后的最短路径矩阵)。 因此,在Floyd算法的三重循环中,我们可以在最内层循环更新最短路径之前,先检查是否存在这样的环,并更新最小环的值。 注意:为了避免重复计算和确保环至少包含三个不同的点,我们要i, j, k互不相同。 具体步骤: 1. 初始化:使用邻接矩阵存储图,如果两点之间没有边,则初始化为一个很大的数(表示无穷大),注意自己到自己的距离为0。 2. 初始化最小环的值为一个很大的数(比如1e9)。 3. 使用三层循环,k从1到n(n为顶点数): a. 在更新最短路径之前,先对所有的i和j(i<j<k)计算:dist[i][j] + graph[j][k] + graph[k][i](即环的大小),并更新最小环。 b. 然后进行正常的Floyd更新:dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j])。 4. 最后,如果最小环的值还是初始化的那个很大的数,则说明图中不存在环;否则输出最小环的值。 注意:这里我们要i<j<k,是为了避免重复计算同一个环(因为环可以顺时针和逆时针,但这样计算会重复?实际上,由于我们只考虑无向图,所以环没有方向,但最小环问题通常针对无向图。另外,这样限制可以避免重复计算,并且确保三个顶点不同)。 但是,请注意:在无向图中,环至少需要三个不同的顶点,且不能有重复的边(除了起点和终点相同)。另外,在无向图中,graph[i][j]应该等于graph[j][i]。 因此,我们假设图是无向图。如果是有向图,方法类似,但要注意边的方向。 下面我们用C++代码实现。注意:顶点编号从1到n。 代码实现: ```cpp #include <iostream> #include <vector> #include <algorithm> using namespace std; const int INF = 1e9; // 定义无穷大 int main() { int n, m; // n为顶点数,m为边数 cin >> n >> m; // 初始化邻接矩阵和最短距离矩阵 vector<vector<int>> graph(n+1, vector<int>(n+1, INF)); vector<vector<int>> dist(n+1, vector<int>(n+1, INF)); // 自己到自己的距离为0 for (int i = 1; i <= n; i++) { graph[i][i] = 0; dist[i][i] = 0; } // 读入边 for (int i = 0; i < m; i++) { int u, v, w; cin >> u >> v >> w; // 无向图,双向边 graph[u][v] = graph[v][u] = min(graph[u][v], w); // 如果有重边,取最小 dist[u][v] = dist[v][u] = graph[u][v]; } int min_cycle = INF; // 最小环的权值和 // Floyd算法最小环 for (int k = 1; k <= n; k++) { // 在更新第k层之前,先检查以k为中间点的环 for (int i = 1; i < k; i++) { for (int j = i+1; j < k; j++) { // 注意:这里要确保graph[i][k]和graph[k][j]不是无穷大,否则会溢出 // 如果dist[i][j]还是无穷大,说明i和j之间没有路径,跳过 if (dist[i][j] != INF && graph[i][k] != INF && graph[k][j] != INF) { // 环的大小为:i->j(经过小于k的中间点)的最短路径 + j->k + k->i min_cycle = min(min_cycle, dist[i][j] + graph[i][k] + graph[k][j]); // 注意:在无向图中,graph[i][k]和graph[k][i]是一样的,所以这里用graph[i][k]和graph[k][j]表示从i到k和k到j的边 // 但实际上,我们这里应该用graph[j][k]?但是注意:graph[k][j]等于graph[j][k](无向图对称) } } } // 正常的Floyd更新 for (int i = 1; i <= n; i++) { for (int j = 1; j <= n; j++) { // 如果经过k中转可以缩短距离,则更新 if (dist[i][k] != INF && dist[k][j] != INF) { dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j]); } } } } if (min_cycle == INF) { cout << "No cycle found." << endl; } else { cout << min_cycle << endl; } return 0; } ``` 注意点: 1. 在循环中,我们只考虑i<j<k,这样可以避免重复计算同一个环(因为环的起点和终点选择不影响环的大小,但这样我们只计算一次)。 2. 在更新最小环时,我们使用的是原始邻接矩阵graph,而不是dist。这是因为在dist[i][j]中,我们不允许经过k(因为k还没有更新进去),所以dist[i][j]表示的是只经过小于k的节点作为中间节点时i到j的最短路径。而graph[i][k]和graph[k][j]是原始的直接边(或者说是经过0个中间节点的边),这样组合起来就是一个经过k的环。 3. 在更新最短路径dist时,我们使用dist[i][k]和dist[k][j],而这两个值在k循环的当前阶段已经更新过(使用小于k的中间节点),因此是可行的。 4. 由于是无向图,所以邻接矩阵是对称的,因此graph[i][k]等于graph[k][i]。 5. 重边处理:在输入边时,我们取重边的最小值。 这个算法的时间复杂度是$O(n^3)$,与Floyd算法相同。 §§相关问题§§ 1. 如何用Floyd算法有向图的最小环? 2. Floyd算法最小环的时间复杂度是多少?能否优化? 3. 除了Floyd算法,还有哪些算法可以最小环问题?</think>### Floyd算法最小环的C++实现 最小环问题是指在一个带权图中寻权重最小的环(至少包含3个顶点)。Floyd算法在解任意两点间最短路径的同时,可以通过扩展来检测最小环。核心思路是在Floyd算法迭代过程中,检查以顶点$k$为中间点的环:$dist[i][j]$(不经过$k$的最短路径)加上$graph[i][k]$和$graph[k][j]$构成环。 #### 算法实现 ```cpp #include <iostream> #include <vector> #include <climits> using namespace std; const int INF = INT_MAX / 3; // 避免加法溢出 int main() { int n, m; cin >> n >> m; // 顶点数, 边数 // 初始化邻接矩阵 vector<vector<int>> graph(n+1, vector<int>(n+1, INF)); vector<vector<int>> dist(n+1, vector<int>(n+1, INF)); // 对角线置零 for (int i = 1; i <= n; i++) graph[i][i] = dist[i][i] = 0; // 读入边 while (m--) { int u, v, w; cin >> u >> v >> w; graph[u][v] = graph[v][u] = min(graph[u][v], w); // 处理重边 dist[u][v] = dist[v][u] = graph[u][v]; } int min_cycle = INF; // 最小环权值和 // Floyd算法核心 for (int k = 1; k <= n; k++) { // 检测以k为中间点的环 for (int i = 1; i < k; i++) { // 避免重复 for (int j = i+1; j < k; j++) { // i < j < k if (dist[i][j] != INF && graph[i][k] != INF && graph[k][j] != INF) { min_cycle = min(min_cycle, dist[i][j] + graph[i][k] + graph[k][j]); } } } // 更新最短路径 for (int i = 1; i <= n; i++) { for (int j = 1; j <= n; j++) { if (dist[i][k] != INF && dist[k][j] != INF) { dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j]); } } } } if (min_cycle == INF) cout << "No cycle found" << endl; else cout << "Minimum cycle weight: " << min_cycle << endl; return 0; } ``` #### 关键点解析 1. **初始化**: - `graph`存储原始边权 - `dist`存储最短路径,初始与`graph`相同 - 对角线设为0($graph[i][i]=0$) 2. **环检测**(核心步骤): ```cpp min_cycle = min(min_cycle, dist[i][j] + graph[i][k] + graph[k][j]); ``` - `dist[i][j]`:不经过$k$的$i→j$最短路径 - `graph[i][k] + graph[k][j]`:$i→k→j$的直接路径 - 三者之和构成环$i→...→j→k→i$ 3. **迭代顺序**: - 先检测环(使用未更新$k$的旧`dist`) - 再更新包含$k$的最短路径 - $i,j$需满足$i<j<k$避免重复计算 4. **复杂度**:$O(n^3)$,与Floyd算法相同 #### 输入示例 ``` 3 3 1 2 1 2 3 2 3 1 4 ``` 输出: ``` Minimum cycle weight: 7 // 环1→2→3→1 ``` #### 应用场景 - 交通网络中的最短巡查路线 - 电路设计中的最小反馈环检测 - 化学反应路径分析[^5] ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值