图论:最短路径

图论部分经常讨论的问题,寻找图中两个特定节点之间最短路径的长度。要解决这类问题需要根据实际情况选择合适的算法

Floyd算法,时间复杂度为O(n3),空间复杂度O(n2),配合邻接矩阵使用。当算法完成后,所有节点对之间的最短路径都会被确定,因此适用于全源最短路问题。由于时间复杂度的限制,一般不太能使用......主要的思想是:dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j])。示例代码如下

#include<bits/stdc++.h>
using namespace std;
int n,m,s;
int ans[10005][10005];

int main()
{
    scanf("%d%d%d",&n,&m,&s);
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            ans[i][j]=-1;
        }
        ans[i][i]=0;
    }
    int a,b,c;
    while(m--){
        scanf("%d%d%d",&a,&b,&c);
        ans[a][b]=c;
    }
    for(int k=1;k<=n;k++){
        for(int i=1;i<=n;i++){
            for(int j=1;j<=n;j++){
                if(ans[i][k]==-1||ans[k][j]==-1){
                    continue;
                }
                if(ans[i][j]==-1||ans[i][j]>ans[i][k]+ans[k][j]){
                    ans[i][j]=ans[i][k]+ans[k][j];
                }
            }
        }
    }
    for(int i=1;i<=n;i++){
        if(i!=1){
            printf(" ");
        }
        printf("%d",ans[s][i]);
    }
    printf("\n");
    return 0;
}

Dijkstra算法,时间复杂度O(n2),空间复杂度O(n)。当使用堆来替换算法中寻找最小权值边的过程,时间复杂度可以优化到O(nlgn)。可能比较适合使用邻接矩阵(邻接矩阵借助vector来记录)。与Floyd算法不同的是,它适用于求解从一个指定节点出发,到其他节点最短路径的问题,即单源最短路问题。需要注意的是不能处理负环。主要思想的话,将节点分为两堆,每次查询与新确定的节点相连的边,从中选出权值最小的一条边在下一回加入。首先是未经优化的代码:

#include<bits/stdc++.h>
using namespace std;
int n,m,s;
struct edge
{
    int next,cost;
};
vector<edge> edges[100005];
bool mark[100005];
int dis[100005];

int main()
{
    scanf("%d%d%d",&n,&m,&s);
    //memset(dis,0x7fffffff,n+1);
    int a,b,c;
    for(int i=1;i<=n;i++){
        dis[i]=0x7fffffff;
        edges[i].clear();
    }
    for(int i=1;i<=m;i++){
        scanf("%d%d%d",&a,&b,&c);
        edge temp;
        temp.next=b;
        temp.cost=c;
        edges[a].push_back(temp);
    }
    dis[s]=0;
    mark[s]=true;
    int newP=s;
    for(int i=2;i<=n;i++){
        for(int j=0;j<edges[newP].size();j++){
            int next=edges[newP][j].next;
            if(mark[next]){
                continue;
            }
            int cost=edges[newP][j].cost;
            if(dis[next]>dis[newP]+cost){
                dis[next]=dis[newP]+cost;
            }
        }
        int minn=0x7fffffff;
        for(int j=1;j<=n;j++){
            if(mark[j]){
                continue;
            }
            if(dis[j]<minn){
                minn=dis[j];
                newP=j;
            }
        }
        mark[newP]=true;
    }
    for(int i=1;i<=n;i++){
        if(i!=1){
            printf(" ");
        }
        printf("%d",dis[i]);
    }
    printf("\n");
    return 0;
}

然后是经过堆优化的方案,复杂度降到O(n*logn),平时做题也一般是用这种:

#include<bits/stdc++.h>
using namespace std;
int n,m,s;
struct edge
{
    int next,cost;
};
struct node
{
    int index;
    int dis;
    bool operator <(const node &x) const
    {
        return x.dis<dis;
    }
};
vector<edge> edges[100005];
bool mark[100005];
int dis[100005];
priority_queue<node> q;

int main()
{
    scanf("%d%d%d",&n,&m,&s);
    int a,b,c;
    for(int i=1;i<=n;i++){
        dis[i]=0x7fffffff;
        edges[i].clear();
    }
    for(int i=1;i<=m;i++){
        scanf("%d%d%d",&a,&b,&c);
        edge temp;
        temp.next=b;
        temp.cost=c;
        edges[a].push_back(temp);
    }

    dis[s]=0;
    q.push((node){s,0});
    while(!q.empty()){
        node temp=q.top();
        q.pop();
        if(mark[temp.index]){
            continue;
        }
        mark[temp.index]=true;
        for(int j=0;j<edges[temp.index].size();j++){
            int next=edges[temp.index][j].next;
            if(mark[next]){
                continue;
            }
            int cost=edges[temp.index][j].cost;
            if(dis[next]>dis[temp.index]+cost){
                dis[next]=dis[temp.index]+cost;
                q.push((node){next,dis[next]});
            }
        }
    }
    for(int i=1;i<=n;i++){
        if(i!=1){
            printf(" ");
        }
        printf("%d",dis[i]);
    }
    printf("\n");
    return 0;
}

最后是SPFA算法,主要是用来处理负环最短路问题。没有负权边绝对不要用SPFA!!!堆优化的dij就很香。SPFA是Bellman-Ford算法的队列实现,最坏情况下和Bellman-Ford算法复杂度一样,O(V*E)。其实代码实现和dij有相似之处,不同点在于对入队次数进行了统计以应对负权边。而且不能重复入队,队列一次只能有一个该点,但是可以修改dis。

bool spfa()
{
    dis[s]=0;
    mark[s]=true;
    q.push(s);
    cnt[s]++;
    while(!que.empty()){
        int now=que.front();
        mark[now]=false;
        for(int i=0;i<edges[now].size();i++){
            int next=edges[now][i].next;
            int cost=edges[now][i].cost;
            if(dis[next]>dis[now]+cost){
                dis[next]=dis[now]+cost;
                if(!mark[next]){
                    mark[next]=true;
                    q.push(next);
                    cnt[next]++;
                    if(cnt[next]>=n){
                        return true;
                    }
                }
            }
        }
    }
    return false;
}

 

posted @ 2020-10-07 18:59  太山多桢  阅读(220)  评论(0)    收藏  举报