Floyd算法——AcWing 343. 排序

发布于:2024-07-11 ⋅ 阅读:(25) ⋅ 点赞:(0)

目录

Floyd算法

定义

运用情况

注意事项

解题思路

基本步骤

AcWing 343. 排序 

题目描述

运行代码

代码思路

改进思路

Floyd算法

定义

Floyd算法,全称Floyd-Warshall算法,是一种用于解决图中所有顶点对之间的最短路径问题的动态规划算法。它适用于带权有向图,且可以处理负权重边(但不能处理含有负权重循环的图),并能够检测出图中是否存在负权重循环。

运用情况

  1. 计算所有顶点对之间的最短路径:在诸如社交网络分析、交通网络规划、通信网络设计等领域,需要计算任意两点间的最短距离时,Floyd算法是一个很好的选择。
  2. 检测负权重循环:在某些应用场景下,需要确保图中不存在负权重循环,因为这可能表示系统中的某种异常或错误状态。

注意事项

  • 内存消耗:Floyd算法的空间复杂度为O(n^2),其中n是图中顶点的数量。对于大规模图,这可能成为一个问题。
  • 时间复杂度:Floyd算法的时间复杂度为O(n^3),在顶点数量很大的情况下,算法可能会变得非常慢。
  • 负权重循环:如果图中存在负权重循环,Floyd算法会陷入无限循环,因为从一个节点出发,通过该循环可以无限次减少路径长度。在实际应用中,需要先检测并处理这类情况。
  • 算法可以检测负权重循环:如果在算法执行过程中发现某个顶点到自身的最短路径小于0,即D[i][i] < 0,那么图中存在至少一个负权重循环,此时算法无法给出正确的最短路径结果。
  • 对于非常大的图,算法的高时间复杂度和空间复杂度可能成为瓶颈,这时可能需要考虑更高效的算法或者使用更专业的数据结构和优化技术。

解题思路

Floyd算法的核心思想是动态规划,其基本步骤如下:

  1. 初始化:将距离矩阵D设置为图的邻接矩阵,即D[i][j] = weight(i, j);对于i != j,如果(i, j)不连通,则D[i][j] = ∞;D[i][i] = 0。
  2. 动态规划:对于k = 1 到 n,更新D,使得D[i][j] = min(D[i][j], D[i][k] + D[k][j]),即检查是否可以通过中间节点k来缩短i到j的距离。
  3. 结果:最终的D矩阵包含了所有顶点对之间的最短路径长度。如果存在某个D[i][j] < 0且i != j,在更新过程中出现了D[i][i] < 0的情况,说明图中存在负权重循环。

Floyd算法的实现通常使用三重循环,外层循环遍历所有的中间顶点k,内两层循环遍历所有顶点对(i, j),检查是否存在更短路径。

基本步骤

假设我们有一个带权图G,其中包含n个顶点,我们可以用一个二维数组dist[][]来存储每对顶点之间的最短路径长度。初始时,dist[i][j]被设定为图中直接相连的边的权重,如果i和j之间没有直接相连的边,则dist[i][j]被设定为无穷大(表示不可达)。

  1. 初始化:创建一个n×n的矩阵D,其中D[i][j]初始化为图中从i到j的直接边的权重,如果i和j之间没有直接边,则设为无穷大。对角线元素D[i][i] = 0。

  2. 动态规划:对于每个顶点k(作为中间顶点),更新每对顶点i和j之间的最短路径,以k为中间点可能获得更短路径。更新公式为:D[i][j] = min(D[i][j], D[i][k] + D[k][j])。这意味着,如果通过k作为中间顶点,从i到j的路径变得更短了,就更新D[i][j]。

  3. 结果输出:经过n轮迭代后,矩阵D中的每个元素D[i][j]即为从i到j的最短路径长度。

AcWing 343. 排序 

题目描述

343. 排序 - AcWing题库

运行代码

#include <algorithm>
#include <bits/stdc++.h>
#include <iostream>
using namespace std;

#define PII pair<int, int>
#define fi first
#define se second
#define endl '\n'
map<int, int> mp;

const int N = 210, mod = 1e9 + 7;
int T, n, m, k;
int a[N], dist[N][N];
PII ans[N];

void floyd(int x) // 以 x 为中转点,更新其他点。
{
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= n; j++)
            dist[i][j] = min(dist[i][j], dist[i][x] + dist[x][j]);
}

bool pd() // 判断是否所有点都已确定
{
    for (int i = 1; i <= n; i++) {
        int cnt = 0;
        for (int j = 1; j <= n; j++)
            if (dist[i][j] != 1e9)
                cnt++; // 当前点能到达的点数
        ans[i] = {cnt, i};
        for (int j = 1; j <= n; j++)
            if (i != j && dist[j][i] != 1e9)
                cnt++; // 能到达当前点的点数
        if (cnt != n)
            return 0;
    }
    sort(ans + 1, ans + n + 1);
    return 1;
}

int main() {
    while (cin >> n >> m && n) {
        for (int i = 1; i <= n; i++)
            for (int j = 1; j <= n; j++)
                if (i != j)
                    dist[i][j] = 1e9;

        int flag = 0;
        for (int i = 1; i <= m; i++) {
            char a, t, b;
            cin >> a >> t >> b;
            int x = a - 'A' + 1, y = b - 'A' + 1; // 现在要加一条从 y 到 x 的边

            if (!flag && dist[x][y] != 1e9) // 发现已经从x能到y,出现矛盾
            {
                printf("Inconsistency found after %d relations.\n", i);
                flag = 1;
            }

            dist[y][x] = 1;
            floyd(x), floyd(y); // 分别以 x 和 y 为中转点更新其他点
            if (!flag && pd()) { // 发现所有点都已确定
                printf("Sorted sequence determined after %d relations: ", i);
                for (int i = 1; i <= n; i++)
                    cout << char(ans[i].se + 'A' - 1);
                printf(".\n");
                flag = 1;
            }
        }
        if (!flag)
            printf("Sorted sequence cannot be determined.\n"); // 无法确定
    }

    return 0;
}

代码思路

  1. 初始化: 使用一个dist二维数组初始化为无穷大,表示不等式间默认没有确定的顺序关系。
  2. 读取输入: 从标准输入读取关系,格式为“B > A”,表示B优于A。
  3. 关系处理:
    • 当读取到新的关系时,检查是否有矛盾(即之前已确定的关系与新关系冲突)。
    • 更新dist矩阵,使用Floyd算法更新所有不等式之间的最短路径,这里实际上是更新“优先级”或“排序”的关系。
  4. 检查排序确定性:
    • 检查是否所有不等式之间的关系都已经确定,即每个点都能到达其他所有点,且路径长度不是无穷大。
    • 如果在读取关系的过程中就发现了排序确定,或者读取完所有关系后仍然无法确定排序,输出相应信息。

改进思路

  • 效率提升: 当前的floyd调用在每次添加新关系后立即进行,这可能不是必要的。可以等到所有关系读取完毕后再进行一次Floyd算法,以减少不必要的计算。
  • 简化逻辑: 目前的代码在检测矛盾和确定排序完成的逻辑较为复杂。可以尝试简化逻辑,比如使用更直观的数据结构或算法步骤来检查这些条件。
  • 代码清晰性: 可以增加注释和代码结构的清晰度,使得代码更易于理解和维护。