三种最常见最短路算法 及其 应用范围 // 兼板子

HDU – 2544 模板题
第一种: Dijkstra算法
//用于求单源最短路径

void dij(int u) {   //从u点开始找出距离所有的点最短距离. n 是点数.
    Fill(vis,0);
    for(int i = 1 ; i <= n ; i ++) near[i] = g[u][i];
    vis[u]=1;
    for(int i = 1 ; i <= n ; i ++) {
        int minn=inf, v=-1;
        for(int j = 1 ; j <= n ; j ++) {
            if(!vis[j] && near[j] < minn)
                v = j, minn = near[j];
        }
        if(v != -1){
            vis[v] = 1;
            for(int j = 1 ; j <= n ; j ++)
                near[j] = min(near[j], g[v][j] + near[v]);
            //就是通过当前我们选择了的这个点转折一下能否更短, 可以的话就更新它.
        }
    }
    printf("%d\n", near[n]);
}

带堆优化的Dij, 注意它与spfa的区别, 一个是优先队列, 首先推出与当前点边长最小的, 推进的也是边的长度. 而spfa是一个普通队列, 推出推进的都是点的标号. 以一个点不会多次入队为优化条件! 所以分清楚! 但是前者不适用有负权值边, 而spfa是可以判负环(正环)的 ! 各有千秋

const int maxn = 1e4+5;
int n, m;
struct node {
    int to, next, w;
    bool operator < (const node& a) const {
        return w > a.w;
    }
} e[maxn<<1];
int cnt, head[maxn];
void add(int u, int v, int w) {
    e[cnt] = node{v, head[u], w};
    head[u] = cnt++;
}
void init() {
    cnt = 0; Fill(head, -1);
}
bool vis[maxn];
int dis[maxn];
void dij(int st,int ed) {
    priority_queue q;
    Fill(dis,inf); Fill(vis,0);
    dis[st] = 0;
    q.push(node{st, 0, 0});
    while (!q.empty()) {
        node u = q.top();
        q.pop();
        if(vis[u.to]) continue;
        vis[u.to] = 1;  

        for (int i = head[u.to]; ~i; i = e[i].next) {
            int to = e[i].to;
            if (dis[to] > dis[u.to] + e[i].w) {
                dis[to] = dis[u.to] + e[i].w;
                q.push(node{to, 0, dis[to]});
            }
        }
    }
  //  cout << dis[ed] << endl;
}

注意: 该算法不适用于图中有负权值边. 比如这组数据(无向)
1 3 3
1 2 2
2 3 -2
那么输出应该是dis[2] = 2, dis[3] = 3. 显而易见是错的. 就是因为每次遍历时都是从起点可以向外拓展的边遍历的, 如果权值都为正, 这是正确的. 但是如果有负的边, 那么它可能会没有判到那条负权值边.

第二种 最暴力 的算法,floyd(适用于点数比较少的情况,允许有权值为负的边)(可以打印任意两点间的最短距离)(复杂度飞常高,不是一定要用,则尽量不要用)

const int maxn = 1e4+5;
int g[maxn][maxn]; // k的内外层就是决定了多次转折还是一次转折.
void floyd() {
    for (int k = 1 ; k <= n ; k ++)
        for (int i = 1 ; i <= n ; i ++)
            for (int j = 1 ; j <= n ; j ++)
                g[i][j] = min(g[i][j], g[i][k] + g[k][j]);
}

第三种,SPFA算法,这个算法是最好的,也可以适用于权值有负的边,时间复杂度也是最低的.(也是可以优化的+SLF,可以去网上搜搜模板)(求源点到其余点的最短距离)
附上有临接链表写的spfa, 写spfa都用这种方法.
这是水题 poj — 3013 题目在此的spfa写法.
(对于最短路中,dij要T的就一般用spfa了,即对于点和路比较多的那种用spfa,点比较少的用普通的dij和floyd就行.)
// 一般都是用来判环来用的. 在判的过程中求最短路.
判环模板题

const int maxn = 1e5+5;
int n, m;
struct node {
    int to, next, w;
}e[maxn];
int cnt, head[maxn];
void add(int u, int v, int w) {
    e[cnt] = (node){v, head[u], w};
    head[u] = cnt++;
}
void init() {
    cnt = 0; Fill(head, -1);
}
int dis[maxn];
int tim[maxn], vis[maxn];
bool spfa(int st) { // 这个判单独的联通块是没有任何问题的, 所以如果是非联通图, 那么就想我注释里这样写.
    queue<int >q; q.push(st); 
    Fill(dis, inf); Fill(vis, 0); Fill(tim, 0);
   // for (int i = 1 ; i <= n ; i ++) {
   //     q.push(i);
   //     dis[st] = 0; vis[st] = 1;
   // }  // 非联通图判环, 直接全部先推进队列
    dis[st] = 0; vis[st] = 1; tim[st]++; // 联通图判环
    while(!q.empty()){
        int u = q.front();
        q.pop(); vis[u]=0;
        for(int i = head[u] ; ~i ; i = e[i].next){
            int to = e[i].to;
            if(dis[to] > dis[u] + e[i].w) { //注意看题目中0是否为负环, 是则加个=, 否则不加!
                dis[to] = dis[u] + e[i].w;
                if (++tim[to] > n) return true;
                if(!vis[to]) {
                    vis[to] = 1;
                    q.push(to);
                }
            }
        }
    }
    return false;
}

所以题目要是要求求任意两点间的最短距离,则用floyd算法(注意三重循环的k,i,j的顺序!!!)

你可能感兴趣的:(最短路相关)