初识最短路算法-Bellman-Ford, Djistera & Floyd

学习最短路中>..

好吧,真相是:发现图论学的一团糟,所以下决心从最基础的开始一步一步走结实一些~~~

在此总结一下三种单源最短路的算法:

1.Bellman-Ford算法

适用范围:DAG,即无环有向图,当然,只要不是负权环就可以,正权环还是可以用的.复杂度O(|V|*|E|).

主要代码:

const int MAX = 1024;

struct edge { int from, to, cost };

edge es[MAX];

int d[MAX]; //更新

int V, E;

void shortest_path(int s) {

    fill(d, d+V, INF); //INF 习惯用INT_MAX>>1,fill在algorithm头文件,昨天猜看到有这么一个神器…

    d[s] = 0; //起点初始化

    while (true) {

        bool update = false;

        for (int i = 0; i < E; ++i) {

            edge e = es[i];

            if (d[e.from] != INF) {

                d[e.to] = min(d[e.to], d[e.from]+e.cost);

                update = true;

            }

        }

        if (!update) break;

    }

    return; //

}

此外,Bellman-Ford算法还可以用来判断DAG图中是否存在负环

因为d[i] = min{d[j] + cost[j->i] | edge[j->i]属于es};

刨去起始点,所以在经过至多V-1次更新就可以得出所有点到起点

的最短路径,由此导出,如果更新到第V次还有可以继续更新的点,

就说明一定存在负环,代码如下:

bool find_negative_loop(void) {

    memset(d, 0, sizeof(d));

    for (int i = 0; i < V; ++i) {

        for (int j = 0; j < E; ++j) {

            edge e = es[j];

            if (d[e.to] > d[e.from] + e.cost) {

                d[e.to] = d[e.from] + e.cost;

                if (i == V-1) return true;

            }

        }

    }

    return false;

}

2.Djistera算法

适用范围:只要不含负权边就可以鸟,无向图也适用

算法从起点开始,每次选取离起点最近的点,

然后用这个点去更新其他点,如此下去,可以省去Bellman算法中的重复计算(比如一个点被更新了多次,即重复计算).但是由于每次找下一个更新点时要遍历查找,这就导致它最后的时间复杂度依然达到了O(|V|^2)

主要代码如下

const int INF = INT_MAX>>1;

const int MAX_V = 1024;

int cost[MAX_V][MAX_V];//构图建边,不相连的边设为INF

int d[MAX_V];//d[i]记录第i个点到起点的最短路径,初始化INF

bool used[MAX_V]; //标记某个点是否已经被更新过

int V, E; //算法不需要边参数,因为cost数组事实上已经记录了所需信息

int djstera(int s) {

    fill(d, d+V, INF); //algorithm

    memset(used, false ,sizeof(used));

    d[s] = 0; //起点到起点的最短路径自然是0

    while (true) {

        //选点

        int v = -1;

        for (int u = 0; u < V; ++u) {

            if (!used[u] && (v == -1 || d[u] < d[v]) v = u;

        }

        if (v == -1) { //所有点都被更新过了

            break;

        }

        used[v] = true; //标记为已更新

        for (int u = 0; u < V; ++u) {

            d[u] = min(d[u], d[v] + cost[v][u]);//更新

        }

    }

    return d[V-1]; //这里认为终点标号为V-1,其它标号返回相应值就可以了

}

Djistera的优化:

因为每次找下一个更新点时,都需要O(|V|)的时间,而我们其实只需要找"距离"最短的点,以及在更新了其他点后,插入以后选更新点的"候选列表"就可以了,这显然可以用堆来完成,可以用一个数组,然后维护它,当然也可以直接用STL的make_heap, push_heap, pop_heap来直接维护数组.其实,我们可以直接用优先队列来优化,priority_queue,STL都准备好辣~~~看代码:

struct edge { int to, cost; };

vector<edge> G[MAX_V];

int d[MAX_V];

typedef pair<int, int> P; //first是距起点的距离,second是点编号

priority_queue<P, vector<P>, greater<P> > Q; //因为需要的是最短距离,所以需要小顶堆,从而要greater<P>(默认是less<P>,大顶堆)

int V, E;

int djistera(int s = 0) { //默认起点是0号点

    while (!Q.empty()) Q.pop(); //为了复用性,这里考虑处理多组数据

    fill(d, d+V, INF); //INF = INT_MAX>>1

    d[s] = 0;

    Q.push(P(0,s)); //起点加进去

    while (!Q.empty()) {

        P p = Q.top(); Q.pop(); //取出最短的点(元素)

        int v = p.second; //看看对应的编号是否已经被更新过,因为我们每次更新过的点都丢进来了,所以势必有些点会重复

        if (d[v] < p.first) continue; //如果这个点当前的d已经更小的,说明已经通过其它更近的点更新过,就跳过

        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;

                Q.push(P(d[e.to], e.to);

            }

        }

    }

    return d[V-1]; //默认终点编号是V-1

}

上述优化不仅优化了时间(O(|E|*log|V|),比原来优秀了很多),存图时用了动态数组在空间上也好一些,但是不管怎样,只要图中有负权边,Djistera算法就不适用,还得用复杂度大一些的Bellman-Ford算法哩

3.Floyd-Warshall算法

用途:都可以(负权边,无向图,有向图神马的通杀),但是复杂度非常大O(|V|^3),在点数多于210时时间代价已经达到千万(10^7)级别,所以在点少时可以用,但是多了就不好了.

Floyd算法是基于dp在最短路中的应用.定义状态d[k][i][j]:只使用顶点0~k和i,j的情况下,记d[k+1][i][j]为i到j的最短路长度.k=-1时,认为只使用i,j,也即d[0][i][j]=cost[i][j].接下来把只使用0~k的问题规约到只使用0~k-1的问题上~~~

只是用0~k时,有两种请况,其一是i到j的最短路只经过k一次,为d[k-1][i][k]+d[k-1][k][j],其二是不经过k,为d[k-1][i][j].从而得到状态转移方程d[k][i][j] = min{d[k-1][i][j], d[k-1][i][k]+d[k-1][k][j]}.为了节省,我们省去一维的空间,不断进行d[i][j] = min{d[i][j], d[i][k]+d[k][j]}的更新即可.

代码如下(非常简短,在可能的情况下,用这个最好了,简洁,低效,但是出错几率很小)

int d[MAX_V][MAX_V];

int V;

int floyd(void) {

for (int k = 0; k < V; ++k) {

    for (int i = 0; i < V; ++i) {

        for (int j = 0; j < V; ++j) {

            d[i][j] = min{d[i][j], d[i][k]+d[k][j];

        }

    }

}

return d[0][V-1]; //这里默认求0到V-1的最短路长度

本节完

    原文作者:Bellman - ford算法
    原文地址: https://blog.csdn.net/bit_Line/article/details/38112521
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞