最短路径

最短路

最短路径问题可以算是图论中相当重要的基础知识了,特别是其中的思想。求解最短路径的方法有很多,下面只给大家说明Dijkstra算法,Dijkstra算法的堆优化版本,Bellman Ford算法和Bellman Ford的队列优化版本,也就是SPFA算法,还有就是Floyd算法和我用的比较少的Johnson算法。

Dijkstra算法

Dijkstra算法的核心思想就是贪心加暴力,也就是将符合要求的且在路径集合当中的路径筛选出来充当最短路的一部分,当然,Dijkstra算法是单源的,下面我们直接上代码,我在代码中做了一些注释供大家参考。(以下代码均采用邻接表存放图)
如果考虑的内存空间存放过大的话,可以考虑链式前向星来存放
代码如下:

点击查看代码
#include <bits/stdc++.h>
#define inf 0x7fffffff
using namespace std;
const int N=1000;
struct edge{
    int v;
    int weight;
};
vector<edge> e[N];
int dis[N];//距离源点的距离
int visited[N];//判重数组
int n,m,s;//节点数和边数和源点
void dijkstra(int s){
    for(int i=0;i<=n;i++){
        dis[i]=inf;
    }
    dis[s]=0;//源点距离自己为0
    for(int i=1;i<n;i++){
        int curPoint = 0;
        for(int j=1;j<=n;j++){
            if(!visited[j]&&dis[j]<dis[curPoint])   curPoint=j;
        }
        visited[curPoint]=1;
        for(auto ed:e[curPoint]){
            int v=ed.v;
            int weight=ed.weight;
            if(dis[v]>dis[curPoint]+weight){
                dis[v]=dis[curPoint]+weight;
            }
        }
    }
}

inline int read(){
    int x=0;
    int f=1;
    char ch=getchar();
    while(!isdigit(ch)){
        if(ch=='-'){
            f=-1;
        }
        ch=getchar();
    }
    while(isdigit(ch)){
        x=(x<<1)+(x<<3)+ch-'0';
        ch=getchar();
    }
}
int main()
{
    n=read(),m=read(),s=read();
    for(int i=0;i<m;i++){
        int a=read(),b=read(),c=read();
        e[a].push_back({b,c});//存放的是有向边
    }
    dijkstra(s);
    system("pause");
	return 0;
}

Dijkstra算法的堆优化版本

从上面的代码可以看出,枚举图中最短的距离显然是这个算法的上限,如果我们能够将这个上限去除的话,这个算法的效率将会大大增加,所以我们引入大根堆来实现这种优化。
代码如下

点击查看代码
#include <bits/stdc++.h>
#define inf 0x7fffffff
using namespace std;
const int N=1000;
struct edge{
    int v;
    int weight;
};
vector<edge> e[N];
int dis[N];
int visited[N];
int n,m,s;
priority_queue<pair<int,int>> qu;
void dijkstraHeap(int s){
    for(int i=1;i<=n;i++){
        dis[i]=inf;
    }
    dis[s]=0;
    qu.push({0,s});
    while(qu.size()){
        auto t=qu.top();
        qu.pop();
        auto u=t.second;
        if(visited[u])  continue;
        visited[u]=1;
        for(auto ed: e[u]){
            int v=ed.v;
            int weight=ed.weight;
            if(dis[v]>dis[u]+weight){
                dis[v]=dis[u]+weight;
                qu.push({-dis[v],v});//使用负距离值可以避免不必要的操作
            }
        }
    }
}
inline int read(){
    int x=0;
    int f=1;
    char ch=getchar();
    while(!isdigit(ch)){
        if(ch=='-'){
            f=-1;
        }
        ch=getchar();
    }
    while(isdigit(ch)){
        x=(x<<1)+(x<<3)+ch-'0';
        ch=getchar();
    }
}
int main()
{
    n=read(),m=read(),s=read();
    for(int i=0;i<m;i++){
        int a=read(),b=read(),c=read();
        e[a].push_back({b,c});//存放的是有向边
    }
    dijkstraHeap(s);
    system("pause");
	return 0;
}

Bellman Ford算法

Bellman算法相对于Dijkstra算法来说处理的路径权值要广泛一些,也就是可以处理负权值的边和判断途中是否存在负环的问题。
代码如下:

点击查看代码
#include <bits/stdc++.h>
#define inf 0x7fffffff
using namespace std;
const int N=1000;
struct edge{
    int v;
    int weight;
};
vector<edge> e[N];
int dis[N];
int n,m,s;
bool Bellman(int s){
    for(int i=1;i<=n;i++){
        dis[i]=inf; 
    }
    dis[s]=0;
    bool flag;
    for(int i=1;i<=n;i++){
        flag=false;
        for(int j=1;j<=n;j++){
            if(dis[j]==inf) continue;//没有被选入的资格
            for(auto ed:e[j]){
                int v=ed.v;
                int weight=ed.weight;
                if(dis[v]>dis[j]+weight){
                    dis[v]=weight+dis[j];
                    flag=true;
                }
            }
        }
        if(!flag)   break;//无最短路入选
    }
    return flag;//如果为真表示有负环存在
}
inline int read(){
    int x=0;
    int f=1;
    char ch=getchar();
    while(!isdigit(ch)){
        if(ch=='-'){
            f=-1;
        }
        ch=getchar();
    }
    while(isdigit(ch)){
        x=(x<<1)+(x<<3)+ch-'0';
        ch=getchar();
    }
}
int main()
{
    n=read(),m=read(),s=read();
    for(int i=0;i<m;i++){
        int a=read(),b=read(),c=read();
        e[a].push_back({b,c});//存放的是有向边
    }
    Bellman(s);
    system("pause");
	return 0;
}

Bellman算法的队列优化

此处优化和Dijktsra优化有着类似的思想
代码如下:

点击查看代码
#include <bits/stdc++.h>
#define inf 0x7fffffff
using namespace std;
const int N=1000;
struct edge{
    int v;
    int weight;
};
vector<edge> e[N];
int dis[N];
int visited[N];//判断节点是否在队列中
int cnt[N];
int n,m,s;
queue<int> q;
bool spfa(int s){
    for(int i=1;i<=n;i++){
        dis[i]=inf;
    }
    dis[s]=0;
    q.push(s);
    visited[s]=1;
    while(q.size()){
        int u=q.front();
        q.pop();
        visited[u]=0;
        for(auto ed:e[u]){
            int v=ed.v;
            int weight=ed.weight;
            if(dis[v]>dis[u]+weight){
                dis[v]=dis[u]+weight;
                cnt[v]=cnt[u]+1;
                if(cnt[v]>=n)   return true;
                if(!visited[v]){
                    visited[v]=1;
                    q.push(v);
                }
            }
        }
    }
    return false;
}
inline int read(){
    int x=0;
    int f=1;
    char ch=getchar();
    while(!isdigit(ch)){
        if(ch=='-'){
            f=-1;
        }
        ch=getchar();
    }
    while(isdigit(ch)){
        x=(x<<1)+(x<<3)+ch-'0';
        ch=getchar();
    }
}
int main()
{
    n=read(),m=read(),s=read();
    for(int i=0;i<m;i++){
        int a=read(),b=read(),c=read();
        e[a].push_back({b,c});//存放的是有向边
    }
    spfa(s);
    system("pause");
	return 0;
}

Floyd算法(全源)

相较于前面四种算法,floyd算法处理的数据相对较少,毕竟有着三套循环,并且存储图的方式是邻接矩阵。
代码如下:

点击查看代码
#include <bits/stdc++.h>
#define inf 0x7fffffff
using namespace std;
const int N=1000;
int dis[N][N];
int weight[N][N];
int n,m;
void floyd (){
    for(int k=1;k<=n;k++){
        for(int i=1;i<=n;i++){
            for(int j=1;j<=n;j++){
                dis[i][j]=max(dis[i][k]+dis[k][j],dis[i][j]);
            }
        }
    }
}
inline int read(){
    int x=0;
    int f=1;
    char ch=getchar();
    while(!isdigit(ch)){
        if(ch=='-'){
            f=-1;
        }
        ch=getchar();
    }
    while(isdigit(ch)){
        x=(x<<1)+(x<<3)+ch-'0';
        ch=getchar();
    }
}
int main()
{
   n=read(),m=read();
   for(int i=1;i<=n;i++){
    for(int j=1;j<=n;j++){
        weight[i][j]=inf;
    }
   }
   for(int i=0;i<m;i++){
    int a=read(),b=read(),c=read();
    weight[a][b]=c;
    weight[b][a]=c;
   }
   memcpy(dis,weight,sizeof dis);
   floyd();
    system("pause");
	return 0;
}

Johnson算法

Johnson算法相对于其他最短路径算法要复杂一点点,因为这个算法会涉及建立虚拟节点和调整每条路的权值。以后会单独出一篇文章讲解。

好了,这就是这篇博文的全部内容,感谢大家观看!

posted @ 2022-11-19 16:36  chattingJhon  阅读(101)  评论(0)    收藏  举报