图-最短路径-BellmanFord算法

当负权存在时,连最短路都不一定存在了。尽管如此,还是有办法在最短路存在的情况下把它求出来。在介绍算法之前,请读者确认这样一个事实:如果最短路存在,一定存在一个不含环的最短路。

理由如下:在边权可正可负的图中,环有零环,正环和负环三种。如果包含零环或正环,去掉以后路径不会边长:如果包含负环,则意味着最短路不存在(为了取得最小值不断在环上走,权值不断减小,但是永远得不到最小的值,最后死循环)

既然不含环,最短路最多只经过(起点不算)n-1个结点,可以通过n-1轮松弛操作得到.起点默认为0.

void bellman_ford()
{
    int i, k, x, y;
    for(i = 1; i <= n; i++)
        d[i] = INFINITY;
    d[1] = 0;
    for(k = 0; k < n-1; k++)            //迭代n-1次 ,因为每次都有一个结点的最短路确定
        for(i = 0; i < m; i++)          //检查每条边
        {
            x = u[i];
            y = v[i];
            if(d[x] < INFINITY)          //松弛
                d[y] = min(d[y], d[x] + w[i]);
        }
}

不难看出它的时间复杂度为O(nm)。在实践中,常常用FIFO队列来代替上面的循环检查。

边的存储结构定义:

struct Edge
{
    int from, to, dist;                                   //起始结点,结束结点,距离
    Edge(int u, int v, int d):from(u), to(v), dist(d){}
};

首先给出存储结构:

int n, m;
vector edges;
vector G[maxn];   //边的编号
int d[maxn];           //s到各个点的距离
int p[maxn];           //最短路的上一条弧

初始化:

void init(int n)
{
    this->n = n;
    for(int i = 0; i < n; i++)
        G[i].clear();
    edges.clear();
}

增加边:

void AddEdge(int from, int to, int dist)
{
    edges.push_back(Edge(from, to, dist));
    m = edges.size();
    G[from].push_back(m-1);
}

含有负权的最短路bellman_ford算法 复杂度n*m

//含有负权的最短路bellman_ford算法 复杂度n*m
bool bellman_ford(int s)
{
    bool inp[maxn];    //是否在队列中
    int cnt[maxn];     //统计结点的松弛次数
    int u, i;
    queue Q;

    memset(inp, false, sizeof(inp)); //初始化
    memset(cnt, 0, sizeof(cnt));
    for(i = 1; i <= n; i++)
        d[i] = INFINITY;
    d[s] = 0;
    inp[s] = true;
    Q.push(s);

    while(!Q.empty())
    {
        u = Q.front();
        Q.pop();
        inp[u] = false;
        for(i = 0; i < G[u].size(); i++)
        {
           Edge &e = edges[G[u][i]];
           if(d[u] < INFINITY && d[e.to] > d[u] + e.dist)
           {
                d[e.to] = d[u] + e.dist;
                p[e.to] = G[u][i];
                if(!inp[e.to])        //队列中没有
                {
                    inp[e.to] = true;
                    Q.push(e.to);
                    if(++cnt[e.to] > n) //发现负圈及时退出 因为总共进行n-1次松弛,所以每个结点最多
                        return false;   //松弛n-1次 然而如果存在负圈,就不断的松弛无限循环,就超出了n
                }
           }
        }
    }

    return true;
}

该算法最坏情况下需要O(nm)时间,不过在实践中,往往只需要很短的时间就能求出最短路。

上面代码还有一个功能,在发现负圈时及时退出。注意,这只说明s可以到达一个负圈,并不代表s到每个点的最短路都不存在。另外,如果图中有其他负圈但是s无法到达这个负圈,则上面的算法也无法找到。

你可能感兴趣的:(数据结构)