最短路径

Floyd

​ Floyd算法可以处理带有负权边的图,但不能处理带有负权回路的图

​ 算法思想:通过引入中间边来松弛两点之间的距离

​ 算法核心语句:

//floyd算法:求图上任意两点之间的最短路径
for(int k =1;k<=n;++k){
    for(int i=1;i<=n;++i){
        for(int j=1;j<=n;++j){
            if(e[i][j]>e[i][k]+e[k][j]){
                e[i][j] = e[i][k]+e[k][j];
            }
        }
    }
}

​ 示例代码:

#include<cstdio>
#include<iostream>
using namespace std;
int e[10][10];
#define inf 99999
int main()
{
    int m,n;  //m:边的条数  n:顶点个数
    cin>>m>>n;
    //初始化边
    for(int i=1;i<=n;++i){
        for(int j=1;j<=n;++j){
            if(i==j){
                e[i][j]=0;
            }else{
                e[i][j]=inf;
            }
        }
    }
    //设置边
    int t1,t2,val;
    for(int i=0;i<m;++i){
        cin>>t1>>t2>>val;
        e[t1][t2] = val;
    }
    //floyd算法:求图上任意两点之间的最短路径
    for(int k =1;k<=n;++k){
        for(int i=1;i<=n;++i){
            for(int j=1;j<=n;++j){
                if(e[i][j]>e[i][k]+e[k][j]){
                    e[i][j] = e[i][k]+e[k][j];
                }
            }
        }
    }
    //输出
    for(int i=1;i<=n;++i){
        for(int j=1;j<=n;++j){
            printf("%10d",e[i][j]);
        }
        printf("\n");
    }
    return 0;
}

Dijkstra

​ Dijkstra算法:单源最短路径(不能处理带有负权边的图)

​ 算法思想:维护一个dis数组,记录着源点到其他顶点的路径。每一次从顶点中选出到源点最短的点,然后更新dis数 组里面的值。直到所有顶点被选完。(贪婪算法)

​ 算法核心语句:

//算法核心语句
for(int i=1;i<n;++i){
    int min = inf;
    int u;
    //寻找剩余顶点中值最小的顶点
    for(int j=1;j<=n;++j){
        if(book[j]==0&&dis[j]<inf){
            if(dis[j]<min){
                min = dis[j];
                u = j;
            }

        }
    }
    book[u] = 1;
    for(int i=1;i<=n;++i){
        if(e[u][i]<inf){
            if(dis[i]>dis[u]+e[u][i]){
                dis[i] = dis[u]+e[u][i];
            }
        }
    }
}

​ 示例程序:

#include<iostream>
#include<cstdio>
using namespace std;
int e[10][10],book[10],dis[10];
#define inf 99999
int main(){
    int n,m; //n:顶点个数  m:边的条数
    cin>>n>>m;
    //初始化
    for(int i=1;i<=n;++i){
        for(int j=1;j<=n;++j){
            if(i==j){
                e[i][j] = 0;
            }
            else{
                e[i][j] = inf;
            }
        }
    }
    //读入边
    for(int i=0;i<m;++i){
        int t1,t2,val;
        cin>>t1>>t2>>val;
        e[t1][t2] = val;
    }
    int source;
    cout<<"请输入源点:"<<endl;
    cin>>source;
    //初始化dis数组和book数组
    for(int i=1;i<=n;++i)
        dis[i] = e[source][i];
    for(int i=1;i<=n;++i)
        book[i]=0;
    book[source]=1;
    
    //算法核心语句
    for(int i=1;i<n;++i){
        int min = inf;
        int u;
        //寻找剩余顶点中值最小的顶点
        for(int j=1;j<=n;++j){
            if(book[j]==0&&dis[j]<inf){
                if(dis[j]<min){
                    min = dis[j];
                    u = j;
                }
                    
            }
        }
        book[u] = 1;
        for(int i=1;i<=n;++i){
            if(e[u][i]<inf){
                if(dis[i]>dis[u]+e[u][i]){
                    dis[i] = dis[u]+e[u][i];
                }
            }
        }
    }
    
    
    
    //输出
    for(int i=1;i<=n;++i){
        printf("%5d",dis[i]);
    }
    return 0;
}
// 6 9
// 1 2 1
// 1 3 12
// 2 3 9
// 2 4 3
// 3 5 5
// 4 3 4
// 4 5 13
// 4 6 15
// 5 6 4
//数组实现邻接表存储图:适用于稀疏图
int n,m;

int u[6],v[6],w[6]; //用于存储起点、终点、权值,大小要比边数m大

int first[5],next[6]; //first用来存储起始点相同的边的第一条边 ,next用来保存起点相同的边的下一条边的编号

for(int i=1;i<=n;++i)
    first[i] = -1;
for(int i=1;i<=m;++i){
    scanf("%d %d %d",&u[i],&v[i],&w[i]);  //读入每一条边
    next[i] = first[u[i]];
    first[u[i]] = i;
}

//遍历邻接表
for(int i=1;i<=n;++i){
    int k = first[i];
    while(k!=-1){
        printf("%d %d %d\n",u[k],v[k],w[k]);
        k = next[k];
    }
}

Bellman—Ford

​ Bellman-Ford算法:可以处理带有负权边的图

​ 算法思想:对所有边遍历k次,得到的是源点最多经过k条边到达其他路径的最短路径长度。因此总的只需要进行k-1 轮

​ 算法核心语句:

//算法核心语句
for(int k=1;k<=n-1;++k){
    for(int i=1;i<=m;++i){
        if(dis[v[i]]>dis[u[i]]+w[i])
            dis[v[i]] = dis[u[i]]+w[i];
    }
 }

​ 示例程序:

#include<iostream>
#include<cstdio>
#define inf 99999
using namespace std;
int main(){
    int dis[10],u[10],v[10],w[10];  //u[i]➡v[i]:w[i] 
    int n,m; //n个顶点m条边
    int source;
    cin>>n>>m;
    //读入边
    for(int i=1;i<=m;++i){
        cin>>u[i]>>v[i]>>w[i];
    }
    //初始化dis数组
    for(int i=1;i<=n;++i){
        dis[i] = inf;
    }
    cout<<"请输入源点:"<<endl;
    cin>>source;
    dis[source] = 0;

    //算法核心语句
    for(int k=1;k<=n-1;++k){
        for(int i=1;i<=m;++i){
            if(dis[v[i]]>dis[u[i]]+w[i])
                dis[v[i]] = dis[u[i]]+w[i];
        }
    }

    for(int i=1;i<=n;++i){
        printf("%d ",dis[i]);
    }
    return 0;
}

// 5 5
// 2 3 2
// 1 2 -3
// 1 5 5
// 4 5 2
// 3 4 3

​ 说明:

  1. 判断是否有负权回路:在执行n-1次遍历后,再进行一次遍历,如果有值发生改变,则有负权回路

  2. 大多数时候,再n-1次循环之前就已经得到了最终结果,可以通过引入标志位提前终止循环。

  3. 在每一次实施松弛操作后,会有一些顶点的最短路径的值将不再改变,可以对此进行优化,优化的思路:

    采用队列,每次选取队首顶点u,对顶点u的所有出边进行松弛,例如有一条u➡v的边,如果这条边使得源点 到v的最短路程变短,且顶点v不在队列中,就将v放入队尾。如果顶点v已经在队列中,就不在放入(故需要 一个标志数组加以判断),当对顶点的所有出边松弛完毕后,顶点u出队。循环直到队列为空。

posted @ 2020-10-02 19:39  pony.ma  阅读(31)  评论(0编辑  收藏  举报