Tian Jiale's Blog

Floyd 最小环算法

思路一

首先,理解 Floyd 算法是按照点的编号增加的顺序更新最短路径的。

因为 Floyd 算法基于动态规划思想,动态规划过程必定存在一个顺序,这里的顺序就是节点编号增加的顺序。那么如果存在这个最小环,会在这个环中的编号最大的那个点 u 更新之前发现这个环。

也就是说,当 u 点被拿来更新从 i 到 j 的最短路径时,我们就能够发现这个闭合环路,发现的方法也不难理解,**if(dis[i][j]+map[j][u]+map[u][i]<INF)**那么就说明以 i 到 j 的一条路径加上 j 到 u 这条边,加上 u 到 i 这条边,就能够组成一个环路。那么我们就在 floyd 实现的代码上稍加改变就可以求得这个最小环。

思路二

我们可以逆向考虑这个问题,假设现在我们已经进行完 floyd 的过程了,dis[i][j]已经是从 i 到 j 的最短路径值了,那么如果我们设定一个数组:pre[i][j]来记录其路径的话,有这样一个方法:

首先我们能够理解,无论对 dis[i][j]这条路径上一共用多少个点更新过这个值,最后一步更新一定是这样的:dis[i][j]=dis[i][k]+dis[k][j],我们不妨拿出一个栗子来说:

对于 dis[1][5],假如其最后一次更新是这样的:

dis[1][5]=dis[1][4]+dis[4][5],

其 dis[1][4]的最后一步更新为:

dis[1][4]=dis[1][2]+dis[2][4];

我们不难写出其路径:1 2 4 5。

我们这里注意,起点终点就是 dis[i][j]中的 i,j,那么 2 和 4 要如何记录呢?我们可以对其 dis[i][j]最后一次更新的点进行记录,也就是说:pre[1][5]=4;pre[1][4]=2;这个时候,我们发现 5 4 2 之间有相关联:2=pre[1][pre[1][5]];那么我们不难想出,对于记录路径,我们可以回溯找各个节点。

我们将 pre[i][j]初始化为 i,当其被更新的时候,我们都记录下来那个节点,回溯即可。

代码示例

#include <cstdio>
#include <cstring>
int const MAX = 105;
int const INF = 0xfffffff;
int distMAX, mapMAX, preMAX;
int n, m, ans[MAX], min_loop, cnt;

void init()
{
  for (int i = 1; i <= n; i++)
  {
    for (int j = 1; j <= n; j++)
    {
      disti = INF;
      mapi = INF;
      prei = i; //开始时j的上一节点为i
    }
  }
}

void Floyd()
{

  min_loop = INF; //最小环的大小
  for (int k = 1; k <= n; k++)
  {

    //求最小环,最短路不变
    for (int i = 1; i < k; i++)
    { //通过k对每一个节点进行优化
      for (int j = i + 1; j < k; j++)
      {
        if (disti + mapi + mapk < min_loop)
        { //出现了更小的环
          min_loop = disti + mapi + mapk;
          int tmp = j;
          cnt = 0;
          while (tmp != i)
          {
            ans[cnt++] = tmp;
            tmp = prei;
          }
          ans[cnt++] = i; //至此已求出i到j的上一个最短路的路径,路径是倒着存的
          ans[cnt++] = k; //因为通过k松弛,所以要将k加入环
        }
      }
    }

    //求最短路及路径
    for (int i = 1; i <= n; i++)
    {
      for (int j = 1; j <= n; j++)
      {
        if (disti + distk < disti)
        {
          disti = disti + distk;
          prei = prek;
        }
      }
    }
  }
}

int main()
{
  int u, v, w;
  scanf("%d%d", &n, &m);
  init();
  for (int i = 0; i < m; i++)
  {
    scanf("%d%d%d", &u, &v, &w);
    if (distu > w)
    { //找最小环遇到平行边时取其最小边权
      distu = distv = w;
      mapu = mapv = w;
    }
  }

  Floyd();

  if (min_loop == INF)
    printf("No solution");
  else
  {
    for (int i = 0; i < cnt; i++)
      printf("%d ", ans[i]);
    printf("\n");
  }
  return 0;
}