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

来源:互联网 发布:dota2数据分析网站 编辑:程序博客网 时间:2024/05/30 20:08

一.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 0
原创粉丝点击