整理《挑战程序设计竞赛》中最短路的代码

一.Bellman-Ford
核心公式:
d[i]=min(d[j]+e.cost,d[i])
释:
d[i]:i节点到源点的当前最短距离
e.cost:相邻两节点的距离(公式中的为j节点指向i节点的距离)
原因:
这里写图片描述(图片来自百度百科)
这里假设B1为i节点,则A123可以为j节点。d[j]为确定的 j到源点最短距离。此时肯定是比较A123的距离加上i,j的距离之和的最短距离。这个最短距离就是最终确定的d[i]。
代码流程:
遍历所有边,当发现边的出发点被更新过时,对边进行松弛操作,更新边的终点。直至某次遍历所有边时没有可以更新边的情况。
松弛操作:d[e.to]>d[e.from]+e.cost;
样例代码:

#include<iostream>
#include<stdio.h>
#include<cstring>
#include<algorithm>
#include<map>
#include<vector>
#include<cmath>
using namespace std;
#define N 1000+5
#define MAXN 1000000
#define mem(arr,a) memset(arr,a,sizeof(arr))
#define INF 0x3f3f3f3f
#define LL long long int 
#define pow(a) (a)*(a)
int d[N];       
int cost[N][N];
int vis[N];
int e, v;
struct edge{ int from; int to; int cost; };
edge es[N];
void dijkstra(){
    mem(vis, 0);
    mem(d, INF);
    d[1] = 0;
    while (1){
        int flag = -1;
        for (int i = 1; i <= e; i++){
            edge etemp = es[i];
            if (d[etemp.from] != INF&&d[etemp.to] > d[etemp.from] + etemp.cost){
                d[etemp.to] = d[etemp.from] + etemp.cost;
                flag = 0;
            }
        }
        if (flag)break;

    }
    cout << d[v] << endl;
}
int main(){
    cin >> e >> v;
    mem(cost, INF);
    for (int i = 1; i <= v; i++){
        int a, b, c; cin >> a >> b >> c;
        es[i].from = a, es[i].to = b, es[i].cost = c;
    }
    dijkstra();
}

注意,这个算法没办法处理从源点可达负圈的图。当图中有负圈时,每次遍历都会进行松弛操作,也就是程序进入了死循环。
复杂度:O(VE)
松弛操作的循环需要E次,while的循环需要进行V-1次。因为每进行一次while循环,至少确定一个点的最短距离d[i]。V-1次确定了除源点外所有点的最短距离。
判断是否存在负圈:
对while循环进行计数,当循环第V次时可退出程序,并声明图中存在负圈。


二:dijkstra算法
dijkstra算法是对Bellman-Ford算法的优化。在Bellman-Ford算法中,d[j]若不是最短距离 ,那么得到的d[i]也不会是最短距离,从d[i]出发进行的松弛操作也是多余的。因此dijkstra对其进行改进。
1.以最短距离d[i]“刚刚”确定的i点出发进行松弛更新
2.不再以之前最短距离确定的点进行松弛。
下面从代码中解释这两点。

#define N 2000+5
#define MAXN 1000000
#define mem(arr,a) memset(arr,a,sizeof(arr))
#define INF 0x3f3f3f3f
#define LL long long int 
int d[N];
int vis[N];
int cost[N][N];
int T, n;
int dijkstra(){
    int v = -1;
    mem(d, INF);
    mem(vis, 0);
    d[1] = 0;
    while (1){
        v = -1;
        for (int i = 1; i <= n; i++){
            if (!vis[i] && (v == -1 || (d[i] < d[v])))v = i;
        }
        if (v == -1)break;
        vis[v] = 1;
        for (int i = 1; i <= n; i++){
            d[i] = min(d[i], d[v] + cost[v][i]);//松弛操作
        }
    }
    return d[n];
}

这里vis数组是记录最短距离已经确定的点。进行一次while循环,在松弛操作的循环中,是以刚刚确定最短距离的点为出发点进行松弛。在每次确定这样的点后都会用vis数组进行记录,在以后挑选新的点时就不会用到已经记录的点。

给我的感觉是,所有的点分为三部分,一部分是确定最短距离的点,一部分是用刚刚确定最短路的点松弛过的点,一部分是未访问点。在松弛过得点中挑出最短距离的点,则这个点变为已经确定最短距离的点,归到第一部分并进行松弛。被松弛到的点从第三部分变为第二部分。直到所有点被归为第一部分。

复杂度:O(V^2)这种写法其实跟Bellman-Ford差不多,松弛V次,while循环V

注意事项:算法不适用于存在负边的图。当图中存在负边时,在进行松弛操作时会造成结果的错误。比如对某条负边的起点a进行松弛后,假设该负边的终点为b,再次进行while循环时以b为起点进行松弛,会更新a的最短距离值,违背了原本算法在进行确定最短距离后就不会改变的初衷。存在负边时算符不适用。

优化的dijkstra,复杂度O(ElogV)

struct edge{ int to, cost; };
typedef pair<int, int> P;
int V;
vector<edge>G[N];
int d[N];
void dijkstra(int s){
    priority_queue<P, vector<P>, greater<P> >que;
    fill(d, d + V, INF);
    d[s] = 0;
    que.push(P(0, s));
    while (!que.empty()){
        P p = que.top();
        que.pop();
        int v = p.second;
        if (d[v] < p.first)continue;
        for (int i = 0; i < G[v].size(); i++){
            edge e = G[v][i];
            if (d[e.to]>d[v] + e.cost){
                d[e.to] = d[v] + e.cost;
                que.push(P(d[e.to], e.to));
            }
        }
    }
}

代码采用了优先队列的数据结构,队列中的元素为图中的点,用堆维护最短距离,操作复杂度为O(logV)。而松弛操作的所需的次数为E,所以总体复杂度为O(ElogV).

三,Floyd-Warshall
dp思想,复杂度O(v^3),没有需要说明的。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值