最短路算法

Bellman-ford

特点:单源最短路可判环

思路:

如果不存在负环,那么经过n-1轮松弛可以找到所有的最短路。如果第n轮还可以松弛,那么说明存在环。

算法的过程全部演示了一遍,可以加深影响https://www.bilibili.com/video/av43217121

证明:

参考:https://www.cnblogs.com/willaty/p/8243881.html

一。假设某点与源点不连通。

由于初始化时,除了源点距离初始为0之外,其他点都初始化为无穷大,如果不连通,则某点所在的连通图的任一条边都不会导致更新。

二。假设x点与源点连通。

每个点都存在自己的最短路,为(e0, e1, e2, ..., ek)。

显然,源点只要经过n - 1条边就可到达任一点;  (一)

现只需证明,对x点,每次迭代(松弛),至少有一条最短边ei的距离被找到,除非已经到达x点。  (二)

  对于第一次迭代,必定更新和源点相连的所有边(如果是有向图,则是指出去的),由于源点距离是0,和其相连的都是无穷大。

    而这些往外连的边中,必有一条是x点的最短路上起始的一条边。

  则设有点k,这个点是x最短路上的一个点,下一次松弛,必能找到下一个点,且也是x的最短路上的一个点:

    由于下一次松弛将更新与k相连的所有点。

有(一)(二)可得,上面两次迭代可以找出最短路。

代码:

int const inf = 0x7f7f7f7f;
struct Edge    //边集合
{
    int from,to,dist;
    Edge(){}
    Edge(int f,int t,int d):from(f),to(t),dist(d){}
};
vector<Edge>v[N];
v[from].push_back(Edge(from,to,dist));
v[to].push_back(Edge(to,from,dist));

​
bool bellman_ford(){   //判断是否存在负环
    for(int i=1;i<=n;i++)   d[i] = inf;
    d[1] = 0;
    for(int i=1;i<=n;i++){
        for(int j=0;j<edge.size();j++){
            Edge e = edge[j];
            if(d[e.v] > d[e.u] + e.d){
                d[e.v] = d[e.u] + e.d;
                if(i == n)  return true;    //如果第n轮还可以松弛,则直接判定存在负环
            }
        }
    }
    return false;
}

SPFA

特点:单源最短路可判环

思路:

SPFA是Bellman的扩展。其实Bellman有很多步骤都是做无用功,因为遍历到某一条边可能不会进行更新,可能不需要n-1轮就已经找到所有最短路。SPFA把更新的点加入队列,下一次找这些点更新。如果队列为空,即没有可以更新的,那么就找到了所有的最短路。如果某个点经过n-1此更新后还可以更新,那说明存在环,这和Bellman算法是一样的。因为如果不存在环,经过n-1次是一定能找到最短路的。

证明:

参考:https://www.cnblogs.com/scau20110726/archive/2012/11/18/2776124.html

每次将点放入队尾,都是经过松弛操作达到的。换言之,每次的优化将会有某个点v的最短路径估计值d[v]变小。所以算法的执行会使d越来越小。由于我们假定图中不存在负权回路,所以每个结点都有最短路径值。因此,算法不会无限执行下去,随着d值的逐渐变小,直到到达最短路径值时,算法结束,这时的最短路径估计值就是对应结点的最短路径值。(证毕)

代码:

bool spfa(int s){
    memset(cnt,0,sizeof(cnt));
    memset(done,false,sizeof(done));
    fill(d,d+N,inf);
    queue<int>q;
    q.push(s);
    done[s] = true;
    d[s] = 0;
    while(!q.empty()){
        int p = q.front();  q.pop();
        done[p] = false;
        for(int i=0;i<v[p].size();i++){
            Edge e = v[p][i];
            if(d[e.from] < inf && d[e.to] > d[e.from] + e.dist){
                d[e.to] = d[e.from] + e.dist;
                if(!done[e.to]){
                    done[e.to] = true;
                    q.push(e.to);
                    if(++cnt[e.to] >= n)    return true;   //判断负环
                }
            }
        }
    }
    return false;
}

Dijkstra

特点:单源最短路(边权都为正)

思路:

参考:https://www.cnblogs.com/jason2003/p/7222182.html

Dijkstra 算法是一种类似于贪心的算法,步骤如下:

1、当到一个时间点时,图上部分的点的最短距离已确定,部分点的最短距离未确定。

2、选一个所有未确定点中离源点最近的点,把他认为成最短距离。

3、再把这个点所有出边遍历一边,更新所有的点。

证明:

参考:https://blog.csdn.net/a19990412/article/details/80232810

代码:

朴素版本:

void dijkstra(int s)
{
    bool vis[N];
    memset(v,false,sizeof(v));
    for(int i=1;i<=n;i++)      
        d[i] = e[s][i];
    v[s] = true;      
    for(int k=1;k<=n-1;k++)      
    {
        int Min = inf,u;
        for(int i=1;i<=n;i++)   //找离s源点最近的点
        {
            if(!v[i] && d[i]<Min)
            {
                Min = d[i];
                u = i;
            }
        }
        v[u] = true;
        for(int i=1;i<=n;i++)  
        {
            if(e[u][i]<inf)
            d[i] = min(d[i],d[u] + e[u][i]);
        }
    }
}

FIFO优化:

    void dijkstra(int s)
    {
        for(int i=0;i<n;i++)    d[i] = (i == s ? 0 : inf);
        memset(done,0,sizeof(done));
        priority_queue<pii,vector<pii>,greater<pii> >q;
        q.push(make_pair(0,s));
        while(!q.empty())
        {
            pii u = q.top();     q.pop();
            int x = u.second;
            if(done[x] == true)     continue;
            done[x] = true;
            for(int i=0;i<G[x].size();i++)
            {
                Edge &e = edge[G[x][i]];
                if(d[e.to]>d[x] + e.dist)
                {
                    d[e.to] = d[x] + e.dist;
                    q.push(make_pair(d[e.to],e.to));
                }
            }
        }
    }

Floyd算法

特点:任意两点之间的最短路(可以存在负权边但不能负权环),可求最小环。

思路:动态规划:

证明:

floyd算法代码虽然是最简单的,但是我认为证明是最难的。

参考:https://blog.csdn.net/xianpingping/article/details/79947091

代码:

    for(int k=0;k<n;k++)    //中转站
        for(int i=0;i<n;i++)
            for(int j=0;j<n;j++)
                {
                    if(e[i][k]<inf && e[k][j]<inf)   //注意这里不要忘记了!
                        e[i][j] = min(e[i][j],e[i][k]+e[k][j]);
                }

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值