常用四大最短路径算法——Floyd、Dijkstra、Bellman-Ford及其变种

目录

Floyd算法

Flody-Warshall算法

Dijkstra最短路径算法——计算单源最短路径

Bellman-Ford——解决负数权重的图

四大算法比较

小试牛刀——一道例题


Floyd算法

核心思想:找到第三个点代替使两点间的距离更短

核心代码就五行:

    //flody核心
    for(int k=0;k<n;k++)
        for(int i=0;i<n;i++)
            for(int j=0;j<n;j++)
                if(M[i][j]>M[i][k]+M[k][j])
                    M[i][j] = M[i][k]+M[k][j];

缺点显而易见,算法复杂度太高

//Floyd最短路径
/*
5 8
1 2 2
1 5 10
2 3 3
2 5 7
3 1 4
3 4 4
4 5 5
5 3 3
*/
#include <iostream>
using namespace std;
static const int INF = (1<<21);

int main(){

    int M[101][101];
    int n,m;
    cin>>n>>m;
    for(int i=0;i<n;i++){
        for(int j=0;j<n;j++){
            if(i==j){
                M[i][j]=0;
            }else{
                M[i][j]=INF;
            }
        }
    }
    int k,t,l;
    for(int i=0;i<m;i++){
        cin>>k>>t>>l;
        k--;
        t--;
        M[k][t]=l;
    }
    cout<<"originnal"<<endl;
    for(int i=0;i<n;i++){
        for(int j=0;j<n;j++){
            cout<<M[i][j]<<" ";
        }
        cout<<endl;
    }
    //flody核心
    for(int k=0;k<n;k++){
        for(int i=0;i<n;i++){
            for(int j=0;j<n;j++){
                if(M[i][j]>M[i][k]+M[k][j]){
                    M[i][j] = M[i][k]+M[k][j];
                }
            }
        }
    }
    cout<<"after floyd"<<endl;
    //输出最短路径的邻接矩阵
    for(int i=0;i<n;i++){
        for(int j=0;j<n;j++){
            cout<<M[i][j]<<" ";
        }
        cout<<endl;
    }


    return 0;
}

Flody-Warshall算法

就比floyd多了一个判断第三点到零两点之间的距离是不是无穷大,核心思想不变

核心代码

    for(int k=0;k<n;k++)
        for(int i=0;i<n;i++)
            for(int j=0;j<n;j++)
                if((M[k][j]<INF)&&(M[i][k]<INF)&&(M[i][j]>M[i][k]+M[k][j])){
                    M[i][j] = M[i][k]+M[k][j];
 
//Floyd最短路径
/*
5 8
1 2 2
1 5 10
2 3 3
2 5 7
3 1 4
3 4 4
4 5 5
5 3 3
*/
#include <iostream>
using namespace std;
static const int INF = (1<<21);

int main(){

    int M[101][101];
    int n,m;
    cin>>n>>m;
    for(int i=0;i<n;i++){
        for(int j=0;j<n;j++){
            if(i==j){
                M[i][j]=0;
            }else{
                M[i][j]=INF;
            }
        }
    }
    int k,t,l;
    for(int i=0;i<m;i++){
        cin>>k>>t>>l;
        k--;
        t--;
        M[k][t]=l;
    }
    cout<<"originnal"<<endl;
    for(int i=0;i<n;i++){
        for(int j=0;j<n;j++){
            cout<<M[i][j]<<" ";
        }
        cout<<endl;
    }
    //flody-Warshall核心
    for(int k=0;k<n;k++){
        for(int i=0;i<n;i++){
            for(int j=0;j<n;j++){
                if((M[k][j]<INF)&&(M[i][k]<INF)&&(M[i][j]>M[i][k]+M[k][j])){
                    M[i][j] = M[i][k]+M[k][j];
                }
            }
        }
    }
    cout<<"after floyd-warshall"<<endl;
    //输出最短路径的邻接矩阵
    for(int i=0;i<n;i++){
        for(int j=0;j<n;j++){
            cout<<M[i][j]<<" ";
        }
        cout<<endl;
    }


    return 0;
}

Dijkstra最短路径算法——计算单源最短路径

核心思想:通过边实现松弛

核心代码:

  • 通过dis数组存储距离目标结点距离;book记录已知最短路程的顶点;
  • 遍历一遍找到距离目标节点最短的节点;
  • 以这个节点为拓展,遍历一边找到最短路路径;
  • 将上述步骤重复n-1次可以找到左右最短路径;
    为什么要重复n-1次呢??
    因为上述三个步骤只能找出两点之间有一个中介点的最短路径的情况,重复n-1边就可以找出中间有1~n-1个中介点的情况!
    for(int i=0;i<n-1;i++){//重复n-1次可以找到所有最短路径
        minnum = INF;//初始化最小值为无穷大
        for(int j=0;j<n;j++){
            if(book[j]==0&&dis[j]<minnum){
                minnum = dis[j];//更新最小值,找到距离0节点最近的节点
                u=j;
            }
        }
        book[u]=1;//节点u已经找到最短路径
        //以距离0节点最近的节点u进行拓展;遍历一遍寻找最短路径
        for(int v=0;v<n;v++){
            if(M[u][v]<INF){
                if(dis[v]>dis[u]+M[u][v]){
                    dis[v]=dis[u]+M[u][v];
                }
            }
        }
    }
//Dijkstra最短路径
/*
5 8
1 2 2
1 5 10
2 3 3
2 5 7
3 1 4
3 4 4
4 5 5
5 3 3
*/
#include <iostream>
using namespace std;
static const int INF = (1<<21);

int main(){

    int dis[101],book[101];//dis存储目标节点到其他节点的单元最短路径;book记录已知最短路程的顶点
    int M[101][101];
    int n,m;
    cin>>n>>m;
    //初始化邻接矩阵
    for(int i=0;i<n;i++){
        for(int j=0;j<n;j++){
            if(i==j){
                M[i][j]=0;
            }else{
                M[i][j]=INF;
            }
        }
    }
    int k,t,l;
    //读入邻接矩阵
    for(int i=0;i<m;i++){
        cin>>k>>t>>l;
        k--;
        t--;
        M[k][t]=l;
    }
    //初始化dis数组
    for(int i=0;i<n;i++){
        dis[i]=M[0][i];//因为是求0节点的单源最短路径
    }
    for(int i=0;i<n;i++){
        book[i]=0;//初始化book数组
    }
    int minnum;
    int u;
    for(int i=0;i<n-1;i++){//重复n-1次可以找到所有最短路径
        minnum = INF;//初始化最小值为无穷大
        for(int j=0;j<n;j++){
            if(book[j]==0&&dis[j]<minnum){
                minnum = dis[j];//更新最小值,找到距离0节点最近的节点
                u=j;
            }
        }
        book[u]=1;//节点u已经找到最短路径
        //以距离0节点最近的节点u进行拓展;遍历一遍寻找最短路径
        for(int v=0;v<n;v++){
            if(M[u][v]<INF){
                if(dis[v]>dis[u]+M[u][v]){
                    dis[v]=dis[u]+M[u][v];
                }
            }
        }
    }

    for(int i=0;i<n;i++){
        cout<<dis[i]<<" "; 
    }

    return 0;
}

Bellman-Ford——解决负数权重的图

核心思想:对边的松弛

核心代码:

  • dis数组存储到目标节点的最短距离
  • 用邻接矩阵表示
  • u[i]、v[i]、w[i]表示从节点u到v权重为w的边
  • 能否通过结点v[i]到结点u[i]权重为w[i]的边使得1号结点到v[i]的顶点的距离变短
    // Bellman-Ford核心
    for(int k=0;k<n-1;k++){//进行n-1轮重复遍历可以考虑完所有情况
        for(int i=0;i<m;i++){
            //能否通过结点v[i]到结点u[i]权重为w[i]的边使得1号结点到v[i]的顶点的距离变短
            if(dis[v[i]]>dis[u[i]]+w[i])
                dis[v[i]] = dis[u[i]] + w[i];
        }
    }
    
//Bellman-Ford
/*
4 5
1 4 9
2 4 6
1 2 5
4 3 8
1 3 7
*/
#include <iostream>
using namespace std;
static const int INF = (1<<21);

int main(){

    int dis[101];//dis存储目标节点到其他节点的单元最短路径
    int u[101],v[101],w[101];//表示顶点u[i]到v[i]这条边的权值为w[i]
    int n,m;
    cin>>n>>m;
    for(int i=0;i<m;i++){
        cin>>u[i]>>v[i]>>w[i];
    }
    for(int i=1;i<n;i++){
        dis[i]=INF;
    }
    dis[0]=0;
    // Bellman-Ford核心
    for(int k=0;k<n-1;k++){//进行n-1轮重复遍历可以考虑完所有情况
        for(int i=0;i<m;i++){
            //能否通过结点v[i]到结点u[i]权重为w[i]的边使得1号结点到v[i]的顶点的距离变短
            if(dis[v[i]]>dis[u[i]]+w[i])
                dis[v[i]] = dis[u[i]] + w[i];
        }
    }
    
    for(int i=0;i<n;i++){
        cout<<dis[i]<<" "; 
    }
    
    return 0;
}

Bellman-Ford拓展——判断是否有负数权重的边

核心思想:

判断是否仍然存在通过结点v[i]到结点u[i]权重为w[i]的边使得1号结点到v[i]的顶点的距离变短的情况,说明存在负数权重的边、

核心代码:

    for(int i=0;i<m;i++){
        //仍然存在通过结点v[i]到结点u[i]权重为w[i]的边使得1号结点到v[i]的顶点的距离变短的情况
        //说明存在负数权重的边
        if(dis[v[i]]>dis[u[i]]+w[i]){
            dis[v[i]] = dis[u[i]] + w[i];
            cout<<"yes!"<<endl;
            break;
        }
           
    }
//Bellman-Ford负数权重的边
/*
5 5
1 2 2
0 1 -3
0 4 5
3 4 2
2 4 3
*/
#include <iostream>
using namespace std;
static const int INF = (1<<21);

int main(){

    int dis[101];//dis存储目标节点到其他节点的单元最短路径
    int u[101],v[101],w[101];//表示顶点u[i]到v[i]这条边的权值为w[i]
    int n,m;
    cin>>n>>m;
    for(int i=0;i<m;i++){
        cin>>u[i]>>v[i]>>w[i];
    }
    for(int i=0;i<n;i++){
        dis[i]=INF;
    }
    dis[0]=0;
    // Bellman-Ford核心
    for(int k=0;k<n-1;k++){//进行n-1轮重复遍历可以考虑完所有情况
        for(int i=0;i<m;i++){
            //能否通过结点v[i]到结点u[i]权重为w[i]的边使得1号结点到v[i]的顶点的距离变短
            if(dis[v[i]]>dis[u[i]]+w[i]){
                dis[v[i]] = dis[u[i]] + w[i];
                cout<<dis[v[i]]<<"i"<<i<<" "<<"k"<<k<<endl;
            }
                
        }
    }
    for(int i=0;i<n;i++){
        cout<<dis[i]<<" "; 
    }

    for(int i=0;i<m;i++){
        //仍然存在通过结点v[i]到结点u[i]权重为w[i]的边使得1号结点到v[i]的顶点的距离变短的情况
        //说明存在负数权重的边
        if(dis[v[i]]>dis[u[i]]+w[i]){
            dis[v[i]] = dis[u[i]] + w[i];
            cout<<"yes!"<<endl;
            break;
        }     
    }
    
    return 0;
}

四大算法比较

小试牛刀——一道例题

from AOJ

Single Source Shortest Path

For a given weighted graph G=(V,E)G=(V,E), find the shortest path from a source to each vertex. For each vertex uu, print the total weight of edges on the shortest path from vertex 00 to uu.

Input

In the first line, an integer nn denoting the number of vertices in GG is given. In the following nn lines, adjacency lists for each vertex uu are respectively given in the following format:

uu kk v1v1 c1c1 v2v2 c2c2 ... vkvk ckck

Vertices in GG are named with IDs 0,1,...,n−10,1,...,n−1. uu is ID of the target vertex and kk denotes its degree. vi(i=1,2,...k)vi(i=1,2,...k) denote IDs of vertices adjacent to uu and cici denotes the weight of a directed edge connecting uu and vivi (from uu to vivi).

Output

For each vertex, print its ID and the distance separated by a space character in a line respectively. Print in order of vertex IDs.

Constraints

  • 1≤≤n≤100
  • 0≤ci≤100,0000≤ci≤100,000
  • |E|≤10,000|E|≤10,000
  • All vertices are reachable from vertex 00

Sample Input 1

5
0 3 2 3 3 1 1 2
1 2 0 2 3 4
2 3 0 3 3 1 4 1
3 4 2 1 0 1 1 4 4 3
4 2 2 1 3 3

Sample Output 1

0 0
1 2
2 2
3 1
4 3

Reference

Introduction to Algorithms, Thomas H. Cormen, Charles E. Leiserson, Ronald L. Rivest, and Clifford Stein. The MIT Press.


Source: https://onlinejudge.u-aizu.ac.jp/problems/ALDS1_12_B

这里n很小,100以内,所以Floyd可以解决;

如果n变大,要换用Dijkstra或者Berllan!

//Single_Source_Shortest_Path_I
/*
5
0 3 2 3 3 1 1 2
1 2 0 2 3 4
2 3 0 3 3 1 4 1
3 4 2 1 0 1 1 4 4 3
4 2 2 1 3 3
*/
#include <iostream>
using namespace std;
static const int INF = (1<<21);

int main(){

    int n;
    cin>>n;
    int M[100][100];
    for(int i=0;i<100;i++){
        for(int j=0;j<100;j++){
            if(i==j)
                M[i][j]=0;
            else
                M[i][j]=INF;
        }
    }
    int w,m,v,u;
    for(int i=0;i<n;i++){
        cin>>u>>m;
        for(int j=0;j<m;j++){
            cin>>v>>w;
            M[u][v]=w;
        }
    }
    for(int k=0;k<n;k++){
        for(int i=0;i<n;i++){
            for(int j=0;j<n;j++){
                if(M[i][j]>M[i][k]+M[k][j])
                    M[i][j]=M[i][k]+M[k][j];
            }
        }
    }
    for(int i=0;i<n;i++){
        cout<<i<<" "<<M[0][i]<<endl;
    }

    return 0;
}
发布了287 篇原创文章 · 获赞 297 · 访问量 12万+

猜你喜欢

转载自blog.csdn.net/qq_41895747/article/details/104291833