最短路之Dijkstra算法、Floyd算法、SPFA算法

本文介绍了最短路问题中的三种算法:Dijkstra算法、Floyd算法和SPFA算法。Dijkstra算法通过不断筛选最近点进行距离修正,Floyd算法利用动态规划遍历所有点寻找最短路径,而SPFA算法则是Dijkstra的优化版,通过队列实现多次进入的点更新。文章包含每种算法的基本思想和代码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言:刚接触图,很多概念都比较模糊。学最短路的过程中,参考了很多博客,写这篇博客主要是想好好整理一下近期的学习。

 

在求最短路前,先用邻接矩阵A[MAXN][MAXN]来表示图中点和边的关系。其中MAXN表示无向图中点的个数,A[i][j]表示i和j两个点之间边的权值。如果i和j之间没有直接相连的边,那么权值为无穷大。

 

Dijkstra算法:


算法分析:

1、将图中的点分到两个集合S和S’, 其中集合S中的点均已经求得与终点V0的最短路。(初始S中只有V0点)

2、从S’中筛选出距离V0距离最近的点i。

3、借助点i对所有的点到V0的距离进行修正。

4、集合S’不为空时,继续执行第二步。

5、算法结束。

 

代码如下:

#define INF 65535
#define MAXN 10

int prev[MAXN];     //记录最短路前驱,方便路径输出
int dist[MAXN];     //记录最短路

int A[MAXN][MAXN];  //邻接表

void Dijkstra(int V0)
{
    int n = MAXN;
    int s[MAXN];

    //初始化
    for(int i = 1; i <= n; i ++)
    {
        dist[i] = A[V0][i];
        s[i] = 0;

        if(dist[i] != INF)
        {
            prev[i] = V0;
        }
        else
        {
            prev[i] = -1;
        }
    }

    //以V0为s集合中第一个元素,执行步骤二。
    dist[V0] = 0;
    s[V0] = 1;

    for(int i = 2; i <= n; i ++)
    {
        int Min = INF;
        int u = V0;

        for(int j = 1; j <= n; j ++)
        {
            if(!s[j] && dist[j] < Min)
            {
                Min = dist[j];
                u = j;
            }
        }

        //将u加入新的集合中后,进行所有点的修正。
        s[u] = 1;

        for(int j = 1; j <= n; j ++)
        {
            if(!s[j] && A[u][j] != INF)
            {
                if(dist[u] + A[u][j] < dist[j])
                {
                    dist[j] = dist[u] + A[u][j];
                    pre[j] = u;
                }
            }

        }
    }
}



Floyd算法:

时间复杂度0(n^3)、动态规划、比较经典。


算法分析:

首先要明确的是从任意节点i到达任意节点j,有两种方式。

第一种是直接到达。

第二种是通过中间的某种路径,间接到达。

那么要求a到b的最短路,必定是这两种方式中的一种。

如果是第一种情况,那么dist[a][b] = A[a][b]。

如果是第二种情况,我们理解如果最短路经过k点,那么必定有dist[i][j] > dist[i][k] + dist[k][j]。

Floyd算法就是借助这一个想法来实现。通过把所有点全部遍历一遍,在遍历每个点u的时候。对所有的dist[][]进行修正,修正的前提就是dist[i][j] > dist[i][u] + dist[u][j],这样在所有点都遍历一遍后,得到的结果就是最短路。


代码如下:

#define INF 65535
#define MAXN 10

int path[MAXN][MAXN];     //记录路径。如path[i][j] = k,那么路径i……k……j。具体怎么输出,给读者思考。
int dist[MAXN];           //记录最短路

int A[MAXN][MAXN];  //邻接表

void Floyd()
{
    int n = MAXN;
    for(int i = 0; i < n; i ++)
    {
        for(int j = 0; j < n; j ++)
        {
            dist[i][j] = A[MAXN][MAXN];
        }
    }
    for (int k = 0; k < n; k ++)
    {
        for (int i = 0; i < n; i ++)
        {
            for (int j = 0; j < n; j ++)
            {
                if(dist[i][k] + dist[k][j] < dist[i][j])
                {
                    dist[i][j] = dist[i][k] + dist[k][j];
                    path[i][j] = k;
                }
            }
        }
    }
}


SPFA算法:

算法理解:

这个算法是Bellman-Ford的优化版,是通过队列来实现的。相比前两个算法,我对这一算法的理解比较模糊。但是通过对BFS(队列)的学习,我觉得比较相似。唯一不同的是,BFS的点只能进一次,而这个算法中,点可以多次进入队列,内部的松弛操作就和Dijkstra算法一样了。

至于这个算法本身为什么能够实现最短路,其实可以考虑和Dijkstra算法进行比较,Dijkstra首先是找到距离V0最近的点后进行松弛操作(就是前面说的修正),而SPFA是找到“松弛”后受到影响的点,然后存入队列。直到队列为空,松弛就全部完成。最短路就得到了,似乎有种贪心的意味在里面。如果深入追究其原因,我恐怕也说不出所以然。先大概这么理解。另外SPFA其实还有一种DFS的实现,这里我就先不涉及了,有时间再好好看看。


代码如下:

<pre name="code" class="cpp">#define INF 65535
#define MAXN 10

int vis[MAXN];            //判断是否在队列中。
int dist[MAXN];           //记录最短路

int A[MAXN][MAXN];  //邻接表
int Cnt[MAXN];      //统计每个点进队的次数

bool Spfa(int V0)
{
    int n = MAXN;
    for (int i = 0; i < n;i ++)
    {
        dist[i] = INF;
        vis[i] = 0;
    }
    queue<int> Q;
    dist[V0] = 0;
    vis[V0] = 1;
    Q.push(V0);
    cnt[V0] ++;
    while (!Q.empty())
    {
        int tmp = Q.front();
        Q.pop();
        vis[tmp] = 0;
        if(Cnt[tmp] > n)    //存在负边
            return 0;
        for (int i = 0; i < n; i ++)
        {
            if(dist[tmp] + A[tmp][i] < dist[i])
            {
                dist[i] = dist[tmp] + A[tmp][i];
                if(!vis[i])
                {
                    Q.push(i);
                    vis[i] = 1;
                }
            }
        }
        vis[tmp] = 0;
    }
    return 1;
}


 

参考文章: http://www.cnblogs.com/biyeymyhjob/archive/2012/07/31/2615833.html  

    http://blog.youkuaiyun.com/v_july_v/article/details/6181485

http://www.cnblogs.com/scau20110726/archive/2012/11/18/2776124.html

http://www.cnblogs.com/devtang/archive/2011/08/25/spfa.html




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值