最短距离

关于最短距离的算法又重新整理了一下,主要包括Dijstra算法,Bellman-Ford算法、SPFA算法、Floyd算法。
关于一些我认为比较好的想法和思路,就直接借鉴了,此处就不重复造轮子了(主要是发现自己水平比不上他们,恩,就是这样子 (T~T))。
此外,这里的建图是邻接表的形式,如有其它形式会有说明。


Dijkstra

从相对熟悉的算法开始,Dijkstra算法是比较常用的,也是比较悠久的算法。
我们先从顶点数较少的情况下思考这个问题。
3个点的最短路
无非就是每次都取最短的边,那么从P1到P3的距离必定是最短的。
那么推广到更多点的是时候,也不外乎如此形式。
这里写图片描述
也是每次从S的点集相连的边中,选取最短的边,以S为代表的点集到该点必定是最短的。
但是为了保证每次选取的边是最短的,就不得不执行上面图示中标红的两个操作——并入和更新。
并入是因为选取的最短边,所以S到 I 的距离就已经确定了,没必要进行更新;
更新是因为保证到 I 的最短距离,确定一条最短的路径,同时也是为了以后寻求最短距离做铺垫,如最上面的那张图中,将P1 - P3的无穷更新为3。
以下是古老的模板:

/*
 *此处使用的是邻接矩阵
*/
int Dijsktral(int s, int t, int n)
{
    for(int i = 1; i <= n; ++i)
        dis[i] = Map[s][i], vis[i] = false;
    vis[s] = true, dis[s] = 0;
    for(int i = 1; i < n; ++i) {
        int Min = INF, index = s;
        for(int j = 1; j <= n; ++j)
            if(!vis[j] && Min > dis[j]) Min = dis[j], index = j;

        if(Min == INF) break;
        vis[index] = true;
        for(int j = 1; j <= n; ++j)
            if(!vis[j] && dis[j] > dis[index] + Map[index][j])
                dis[j] = dis[index] + Map[index][j];
    }
    return dis[t];
}

以下是优化的模板:

/*
 *明白人一看就知道这不是我写的(我也很绝望呀)
 *以下代码引用于《挑战程序设计竞赛 第二版》
 *是采用了heap的思想,
 *也就是代替了上面模板中寻找Min和index的O(n)循环,
 *变为O(lgn)的复杂度。
*/
struct Edge{
    int to, cost;
};
typedef pair<int, int> P;//first存储距离,second存储顶点
vector<Edge> G[MAXN];
//以上为数据结构,下面是函数实现部分
void dijkstra(int s, int n) {
    priority_queue<P, vector<P>, greater<P> >PQ;
    fill(dist, dist + n, INF);
    dist[s] = 0;
    PQ.push((P) {0, s});
    while(!PQ.empty()) {
        P p PQ.top(); PQ.pop();
        int v = p.second;
        if(dist[v] < p.first) continue;
        for(int i = 0; i < (int)G[v].size(); ++i) {
            Edge &e = G[v][i];
            if(dist[e.to] > dist[v] + e.cost) {
                dist[e.to] = dist[v] + e.cost;
                PQ.push((P) {d[e.to], e.to});
            }
        }
    }
}

BellmanBellman-Ford

下面介绍一下,第一听说的算法(暴露自己的知识水平了)。
这里写图片描述
这是另外一个思路,就是放图里不断的枚举边,只要在边权值为非负的情况下,那么其结果必定会趋于所求结果,直到S到所有点的距离不会变为更小。
也就是无尽的枚举可能边,直到结果不改变结束。
基于这个思想,那么可想而知,是可以检验负环的存在的,也就是如果该算法不能再有限次终止,必定存在负环。
以下是模板:

/*
 *代码依旧来自《挑战程序设计竞赛 第二版》,但略有改动
 *因为还未熟练使用,直接贴书上代码了
*/
//以下是最短距离的模板,并不能判断是否存在负环
struct Edge{
    int from, to, cost;
}edge[MAXE];
void bellman(int s, int N, int E) {
    fill(dist, dist + N, INF);
    dist[s] = 0;
    while(true) {
        bool update = false;
        for(int i = 0; i < E; ++i) {
            Edge &e = edge[i];
            if(dist[e.from] != INF && dist[e.to] > dist[e.from] + e.cost)
                dist[e.to] = dist[e.from] + e.cost, update = true;
        }
        if(!update) break;
    }
}
//以下是判断负环的模板
bool bellman(int s, int N, int E) {
    fill(dist, dist + N, 0);
    for(int i = 0; i < N; ++i) {
        for(int j = 0; j < E; ++j) {
            Edge &e = edge[j];
            if(dist[e.to] > dist[e.from] + e.cost) {
                dist[e.to] = dist[e.from] + e.cost;
                if(i == N - 1) return true;
            }
        }
    }
    return false;
}

SPFA

先引个链接,SPFA,就像标题所示,图解还是不错的(至少对vis数组值的改变原因有所表现)
先来个模板:

/*
 *学了这么多,自己动手搞了一下。
*/
bool SPFA(int s, int t, int n) {
    for(int i = 0; i <= n; ++i)
      dist[i] = INF, cnt[i] = 0, vis[i] = false;
    queue<int> Q;
    Q.push(s);
    dist[s] = 0, cnt[s]++, vis[s] = true;
    while(!Q.empty()) {
        int u = Q.front(); Q.pop(); vis[u] = false;
        for(int i = 0; i < (int)G[u].size(); ++i) {
            Edge &e = G[u][i];
            if(dist[e.to] > dist[e.from] + e.cost) {
                dist[e.to] = dist[e.from] + e.cost;
                if(!vis[e.to]) {
                    Q.push(e.to);
                    vis[e.to] = true;
                    cnt[e.to]++;
                    if(cnt[e.to] > n) return false;
                }
            }
        }
    }
    return true;
}

听说这个算法是从Bellman算法改过来的,不管是不是真的,好像有点道理。
也是从边入手,如果这条边能更新 S 到下一个结点 u 的距离,那么以 u 的顶点所连的边也可能更新其他点的距离,所以 u 需要被推入队列中,(考虑到可能重复入队,所有要在出队时,标记为为入队vis[u] = false,入队是标记为vis[u] = true)。
如果不存在负环,该操作也会在有限次能完成,也就是每个顶点如果超过这个次数,那么可以判定为存在负环,即用cnt的计数值判断。

Floyd

同样的先给一个链接,Floyd,请看赵轩昂的回答(因为我比较赞同该看法,如果有更好的理解,那请随意)。
该算法就不上模板了,听说只要五行,不行你看这里,这里


相关的最短距离算法最近就学了这么多,就写到这里吧。
至于最短距离的变种(也就是算法思想的运用等),以后再说吧(能力有限呀…………)。
最后推荐一道水题,供大家练练手,水题

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值