poj 2449Remmarguts' Date uvaoj 10740 not the best dijkstra或spfa或bellman-ford k短路 A*

一,基础知识
图的前向星和链式前向星表示(参考这里
前向星是一种特殊的边集数组,我们把边集数组中每一条边的起点按照从小到大排序,如果起点相同就按照终点从小到大排序,并记录下以某个点为起点的所有边在数组中的起始位置和存储长度,那么前向星就构造好了。用len[i]和head[i]分别表示以点i为起点的边的个数和起始位置。因为前向星有排序动作,所以最快是nlogn级别的。但是用链式前向星就可以避免排序。
可以建立边结构体:

struct Edge {
    int to, w, next;
}edge[M];

其中edge[i].to是第i条边的终点,edge[i].next表示与第i条边起点相同的边的下一个存储位置,edge[i].w为这条边的权值。
这样,添加边的函数就可以写成:

void addEdge(int u, int v, int w)
{
    edge[ecnt].to = v;
    edge[ecnt].w = w;
    edge[ecnt].next = head[u];
    head[u] = ecnt++;
}

其中ecnt是边的个数,这个函数将这一条边加入到之前的起点相同边的开头,head数组一般初始化为-1,ecnt初始化为0。这样加入的和读出来的顺序是相反的,但是不影响结果的正确性。

下边的例子全部都使用链式前向星。
二,最短路算法
最短路算法一般都分为初始化和松弛两个步骤。
a. Dijkstra算法
dijkstra适用于边权为正的情况,在边权为负的情况下,不能正确求出最短路。算法同时适用于有向图和无向图。下边是伪代码:

清除所有点的标号
设d[start] = 0,其他d[i] = INF 循环n次 { 在所有未标号的结点中选出d值最小的结点x 给结点x标记 对于从x出发的所有边(x,y),更新d[y] = min(d[y], d[x] + w[x][y]) }

如果需要记录路径,那么加上一个指向父节点的数组,每次松弛的时候,更新父节点就行了。
实际的代码如下:

void dijkstra(int start)
{
    clr(vis, 0);
    for (int i = 1; i <= n; ++i) {
        dist[i] = INF;
    }
    dist[start] = 0;
    for (int i = 1; i <= n; ++i) {
        int x = 0;
        int mnx = INF;
        for (int j = 1; j <= n; ++j) {
            if (!vis[j] && mnx >= dist[j]) {    // 保证可以选一个点
                x = j;
                mnx = dist[j];
            }
        }
        vis[x] = true;
        int p = rhead[x];
        while (p >= 0) {
            int v = redge[p].to;
            int w = redge[p].value;
            dist[v] = min(dist[x] + w, dist[v]);
            p = redge[p].next;
        }
    }
}

上边的代码复杂度是O(n^2),在实际应用中可以使用优先队列每次选取没有标号的d值最小的点,来进行松弛,复杂度为O(mlogn),每次出队列的时候,如果已经标记了的话,直接忽略这个结点不再扩展了,代码如下:

void heap_dijkstra(int start)
{
    clr(vis, 0);
    for (int i = 1; i <= n; ++i) {
        dist[i] = INF;
    }
    dist[start] = 0;
    priority_queue<Node> pq;
    pq.push(Node(start, 0, 0));
    while (!pq.empty()) {
        Node x = pq.top();
        pq.pop();
        if (vis[x.u]) {//松弛过就不再松弛了
            continue;
        }
        vis[x.u] = true;
        int p = rhead[x.u];
        while (p >= 0) {
            int a = redge[p].from;
            int b = redge[p].to;
            int w = redge[p].value;
            if (dist[b] > dist[a] + w) {
                dist[b] = dist[a] + w;
                pq.push(Node(b, dist[b], 0));
            }
            p = redge[p].next;
        }
    }
}

b. Bellman-ford算法
dijkstra不能处理带有负权的图,需要注意,当负权存在时,连最短路都不一定存在了,但是还是有办法在最短路存在的情况下把它求出来。如果最短路存在,一定存在一个不含环的最短路。既然不含环,最短路最多只经过n-1个结点(不含起点),可以通过n-1轮松弛操作得到。代码如下:

void bellman_ford(int start)
{
    for (int i = 1; i <= n; ++i) {
        dist[i] = INF;
    }
    dist[start] = 0;
    for (int i = 0; i < n - 1; ++i) {//松弛n-1次
        for (int j = 0; j < m; ++j) {//每次对m条边松弛
            int u = redge[j].from;
            int v = redge[j].to;
            if (dist[u] < INF) {
                dist[v] = min(dist[v], dist[u] + redge[j].value);
            }
        }
    }
}

可见上述算法的复杂度是O(mn),spfa(Shortest Path Faster Algorithm)算法可以讲其优化到O(kn),一般来说k小于等于2。

需要注意,在每次入队时将标记置为真,出队时将标记置为假,只有能更新的时候才更新,当不在队列中加入队列,同时记录出队的次数,如果出队的次数大于等于n(每次出队都要松弛与他相连的点,那么大于等于n次就说明有了环),那么表明有负环。同样bellman-ford算法如果松弛n-1次之后,还可以再松弛,说明有负环。

下面代码在发现负环时及时退出,但只是说明s可以到达一个负环,并不代表s到每个点的最短路都不存在。另外如果图中有一个负环但是s无法到达,这个负环,那么上面的算法也无法找到。这时可以增加一个虚拟的点,这个点到所有的点的长度都是0,从这个点开始搜索一次,就可以找到图中是不是有负环了。这需要在具体的题目中实践。

代码如下:

void spfa(int start)
{
    for (int i = 1; i <= n; ++i) {
        dist[i] = INF;
    }
    dist[start] = 0;
    clr(c, 0);
    clr(vis, 0);
    queue<int> q;
    q.push(start);
    vis[start] = true;
    while (!q.empty()) {
        int x = q.front();
        q.pop();
        int p = rhead[x];
        vis[x] = false;;
        while (p >= 0) {
            int u = redge[p].from;
            int v = redge[p].to;
            int w = redge[p].value;
            if (dist[u] + w < dist[v]) {
                dist[v] = dist[u] + w;
                if (!vis[v]) {
                    vis[v] = true;
                    ++c[v];
                    if (c[v] >= n) {
                        return;
                    }
                    q.push(v);
                }
            }
            p = redge[p].next;
        }
    }
}

三,k短路算法(参考这里)
求k短路其实最容易想到的方法是利用广度优先搜索的思想,扩展点,当这个点第k次出队列的时候,当前找到的长度就是第k短的路,但是,这时会产生比较多的状态,当图比较小,k值比较小的时候还可以接受。

目前使用比较多的是单源最短路径配合A*算法,其中A*所使用的启发函数h是当前结点到目标结点的最短路,这时,就需要预先反向求出从目标结点到其他结点的最短路长度,总体的启发函数f(u)=g(u)+h(u),g(u)是从起始结点到当前结点的实际路径长度,h(u)如前所述。

使用一个优先队列来处理结点,先将起始结点加入优先队列,从优先队列中取出f值最小的结点,如果是目标结点那么计算已经到达的次数,如果是k,那么就退出,当前的g值就是答案,否则,将以当前结点为起点的边的终点计算f值,加入队列。这里需要注意的是当s与t相等的时候,需要求k+1短路,因为第1短路是0,算作没有走。

代码如下:

int bfs(void)
{
    priority_queue<Node> pq;
    pq.push(Node(s, 0, dist[s]));
    if (dist[s] == INF) {   // 不可达
        return -1;
    }
    int cnt = 0;
    while (!pq.empty()) {
        Node u = pq.top();
        pq.pop();
        if (u.u == t) {
            ++cnt;
        }
        if (u.u == t && cnt == k) {
            return u.g;
        }
        int p = head[u.u];
        while (p >= 0) {
            pq.push(Node(edge[p].to, u.g + edge[p].value, dist[edge[p].to]));
            p = edge[p].next;
        }
    }
    return -1;
}

下边是poj 2449和uvaoj10740的题目代码,求最短路使用了多种方法,就是裸的求第k短路:

/************************************************************************* > File Name: 2449.cpp > Author: gwq > Mail: gwq5210@qq.com > Created Time: 2015年08月28日 星期五 09时53分34秒 ************************************************************************/

#include <cmath>
#include <ctime>
#include <cctype>
#include <climits>
#include <cstdio>
#include <cstdlib>
#include <cstring>

#include <map>
#include <set>
#include <queue>
#include <stack>
#include <string>
#include <vector>
#include <sstream>
#include <iostream>
#include <algorithm>

#define INF (INT_MAX / 10)
#define clr(arr, val) memset(arr, val, sizeof(arr))
#define pb push_back
#define sz(a) ((int)(a).size())

using namespace std;
typedef set<int> si;
typedef vector<int> vi;
typedef map<int, int> mii;
typedef pair<int, int> pii;
typedef long long ll;

const double esp = 1e-5;

#define N 1010
#define M 100010

int n, m, s, t, k;
int head[N], ecnt, rhead[N], recnt, dist[N], c[N];
bool vis[N];

struct Edge {
    int from, to, value, next;
}edge[M], redge[M];

struct Node {
    int u, g, h;
    Node() {}
    Node(int uu, int gg, int hh): u(uu), g(gg), h(hh) {}
};

bool operator <(Node u, Node v)
{
    return u.g + u.h > v.g + v.h;
}

void addEdge(int u, int v, int x)
{
    edge[ecnt].from = u;
    edge[ecnt].to = v;
    edge[ecnt].value = x;
    edge[ecnt].next = head[u];
    head[u] = ecnt++;
}

void addREdge(int u, int v, int x)
{
    redge[recnt].from = u;
    redge[recnt].to = v;
    redge[recnt].value = x;
    redge[recnt].next = rhead[u];
    rhead[u] = recnt++;
}

void dijkstra(int start)
{
    clr(vis, 0);
    for (int i = 1; i <= n; ++i) {
        dist[i] = INF;
    }
    dist[start] = 0;
    for (int i = 1; i <= n; ++i) {
        int x = 0;
        int mnx = INF;
        for (int j = 1; j <= n; ++j) {
            if (!vis[j] && mnx >= dist[j]) {    // 保证可以选一个点
                x = j;
                mnx = dist[j];
            }
        }
        vis[x] = true;
        int p = rhead[x];
        while (p >= 0) {
            int v = redge[p].to;
            int w = redge[p].value;
            dist[v] = min(dist[x] + w, dist[v]);
            p = redge[p].next;
        }
    }
}

void heap_dijkstra(int start)
{
    clr(vis, 0);
    for (int i = 1; i <= n; ++i) {
        dist[i] = INF;
    }
    dist[start] = 0;
    priority_queue<Node> pq;
    pq.push(Node(start, 0, 0));
    while (!pq.empty()) {
        Node x = pq.top();
        pq.pop();
        if (vis[x.u]) {
            continue;
        }
        vis[x.u] = true;
        int p = rhead[x.u];
        while (p >= 0) {
            int a = redge[p].from;
            int b = redge[p].to;
            int w = redge[p].value;
            if (dist[b] > dist[a] + w) {
                dist[b] = dist[a] + w;
                pq.push(Node(b, dist[b], 0));
            }
            p = redge[p].next;
        }
    }
}

void bellman_ford(int start)
{
    for (int i = 1; i <= n; ++i) {
        dist[i] = INF;
    }
    dist[start] = 0;
    for (int i = 0; i < n - 1; ++i) {
        for (int j = 0; j < m; ++j) {
            int u = redge[j].from;
            int v = redge[j].to;
            if (dist[u] < INF) {
                dist[v] = min(dist[v], dist[u] + redge[j].value);
            }
        }
    }
}

void spfa(int start)
{
    for (int i = 1; i <= n; ++i) {
        dist[i] = INF;
    }
    dist[start] = 0;
    clr(c, 0);
    clr(vis, 0);
    queue<int> q;
    q.push(start);
    vis[start] = true;
    while (!q.empty()) {
        int x = q.front();
        q.pop();
        int p = rhead[x];
        vis[x] = false;;
        while (p >= 0) {
            int u = redge[p].from;
            int v = redge[p].to;
            int w = redge[p].value;
            if (dist[u] + w < dist[v]) {
                dist[v] = dist[u] + w;
                if (!vis[v]) {
                    vis[v] = true;
                    ++c[v];
                    if (c[v] >= n) {
                        return;
                    }
                    q.push(v);
                }
            }
            p = redge[p].next;
        }
    }
}

int bfs(void)
{
    priority_queue<Node> pq;
    pq.push(Node(s, 0, dist[s]));
    if (dist[s] == INF) {   // 不可达
        return -1;
    }
    int cnt = 0;
    while (!pq.empty()) {
        Node u = pq.top();
        pq.pop();
        if (u.u == t) {
            ++cnt;
        }
        if (u.u == t && cnt == k) {
            return u.g;
        }
        int p = head[u.u];
        while (p >= 0) {
            pq.push(Node(edge[p].to, u.g + edge[p].value, dist[edge[p].to]));
            p = edge[p].next;
        }
    }
    return -1;
}

int main(int argc, char *argv[])
{
    while (scanf("%d%d", &n, &m) != EOF) {
        int u, v, x;
        clr(head, -1);
        clr(rhead, -1);
        ecnt = 0;
        recnt = 0;
        for (int i = 0; i < m; ++i) {
            scanf("%d%d%d", &u, &v, &x);
            addEdge(u, v, x);
            addREdge(v, u, x);
        }
        scanf("%d%d%d", &s, &t, &k);
        if (s == t) {
            ++k;
        }
        //dijkstra(t);
        //bellman_ford(t);
        //spfa(t);
        heap_dijkstra(t);
        printf("%d\n", bfs());
    }
    return 0;
}
    原文作者:Bellman - ford算法
    原文地址: https://blog.csdn.net/gwq5210/article/details/48091923
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞