图的最短路

  最短路:可以分为单起点终点最短路和多起点多终点最短路。它包含了很多算法Dijkstra,floyd等等。
当图中,边权全都是正数,一般采用Dijkstra。算法步骤如下:
1.选择当前距离起点,最短的点t。点t的最短路就已经确定。
2.标记点t。
3.用t更新其余所有点的最短路。
可以看出,每一次确定一个点,有n个点就需要迭代n次。
朴素dijkstra

朴素Dijkstra

  它的时间复杂度只和点有关,适用于稠密图(边较多)。一般而言,如果m与n^2是一个级别可以认为是稠密图。

//时间复杂度O(n^2),n是点数
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int maxn = 510;
int n,m;
//g是邻接矩阵用来存储稠密图,dis存储每个点到起点的距离
int g[maxn][maxn],dis[maxn]; 
bool st[maxn]; //st用来标记每个点是否已经确定最短路
int dijkstra(){
    for(int i = 1;i<=n;i++) //循环n次处理n个点
    {
        int t = -1;
        for(int j = 1;j<=n;j++)
        {
            if(!st[j] && (t == -1 || dis[t] > dis[j]))
            {
                t = j; //找到最近点t
            }
        }
        st[t] = true; //标记访问
        for(int j = 1;j<=n;j++)
        {
            dis[j] = min(dis[t] + g[t][j],dis[j]); //更新所有点距离
        }
    }
    if(dis[n] == 0x3f3f3f3f) return -1; //起点与终点不连通
    else return dis[n];
}
int main(){
    cin>>n>>m;
    memset(dis,0x3f,sizeof(dis));//初始化dis为无穷
    memset(g,0x3f,sizeof(g));//初始化邻接矩阵为无穷
    dis[1] = 0;
    while(m--){
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        g[a][b] = min(g[a][b],c);//处理平行边
    }
    cout << dijkstra() <<endl;
    return 0;
}

堆优化Dijkstra

  适用于稀疏图(边较少)。dijsktra算法第一步可以用最小堆优化,降为O(1)。但是,在第3步时,需要修改堆内元素,而STL不支持这种操作。因此,我们直接将更新后的点插入堆中,这会产生冗余但是影响不大。

//时间复杂度O(m*logn) m是边数
#include<iostream>
#include<algorithm>
#include<queue>
#include<cstring>
#include<map>
using namespace std;
const int maxn = 2 * 1e5 + 10;
int n,m,dis[maxn];
int head[maxn],val[maxn],ne[maxn],w[maxn],idx; //数组模拟邻接表效率高
bool st[maxn];
typedef pair<int,int> PII; //每个点既要存储距离,也要存储编号
priority_queue<PII,vector<PII>,greater<PII>> h;//定义小根堆
void add(int a,int b,int c){ //w用来存储边权
    val[idx] = b,ne[idx] = head[a],w[idx] = c,head[a] = idx++;
}
int dijkstra(){
    memset(dis,0x3f,sizeof(dis));
    dis[1] = 0;
    h.push({0,1});
    while(h.size())
    {
        auto t = h.top(); //找到最小
        h.pop();
        int d= t.first, tag = t.second;
        if(st[tag]) continue;
        st[tag] = true;//标记访问
        for(int i = head[tag];i != -1;i = ne[i])
        {
            int u = val[i];
            if(dis[u] > d + w[i])
            {
                dis[u] = d + w[i];//更新最短距离
                h.push({dis[u],u}); //加入堆
            }
        }
    }
    if(dis[n] == 0x3f3f3f3f) return -1;
    else return dis[n];
} 
int main(){
    cin>>n>>m;
    memset(head,-1,sizeof(head));
    while(m--){
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        add(a,b,c);
    }
    cout << dijkstra()<< endl;
    return 0;
}

  存在负权边,可以采用bellman-ford,spfa,Floyd。

bellman-ford

时间复杂度O(m*n),Bellman_ford

/*
   memeset(dis,0x3f,sizeof dis);
   dis[1] = 0;
   for 遍历1-k次 //第k次遍历表示,不超过k条边的最短路
      memcpy(backup,dis,sizeof dis); //backup保留上次更新结果,因为dis是动态更新的
      for 遍历所有边 e = {a,b,c}; //如果a到b右边,尝试利用(1->a距离与a->b距离)更新(1->b)
         dist[e.b] = min(dist[e.b],backup[e.a] + e.c);
*/
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int maxn = 510,maxm=10010;
int n,m,k;
int dis[maxm],backup[maxm];
struct Edge{
    int a,b,w;
}edges[maxm];
int bellman_ford(){
    memset(dis,0x3f,sizeof(dis));
    dis[1] = 0;
    for(int i=1;i<=k;i++){
        memcpy(backup,dis,sizeof(dis));
        for(int j=0;j<m;j++){
           int a = edges[j].a,b = edges[j].b,w = edges[j].w;
           dis[b] = min(dis[b],backup[a]+w);
        }
    }
}
int main(){
    cin>>n>>m>>k;
    for(int i=0;i<m;i++){
        int a,b,w;
        scanf("%d%d%d",&a,&b,&w);
        edges[i].a = a,edges[i].b = b,edges[i].w = w;
    }
    bellman_ford();
    if(dis[n]>= 0x3f3f3f3f/2) puts("impossible");
    else printf("%d\n",dis[n]);
    return 0;
}

spfa

  对bellman_ford算法进行优化,在更新最短路时,只有在a更新过后,b才有可能变小。因此,利用队列对更新过程进行优化。

//时间复杂度一般是O(m) 最坏是O(n*m)
#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;
queue<int> q; //q用来存储,被更新的点
const int maxn = 1e5 + 10;
int n,m,dis[maxn];
int head[maxn],val[maxn],ne[maxn],w[maxn],idx;
bool st[maxn];
void add(int a,int b,int c){
    val[idx] = b,w[idx] = c,ne[idx] = head[a],head[a] = idx++;
}
int spfa(){
    memset(dis,0x3f,sizeof(dis));
    dis[1] = 0;
    q.push(1);
    st[1] = true;
    while(q.size()){
        int u = q.front();
        q.pop();
        st[u] = false;
        for(int i = head[u];i != -1;i = ne[i]){ //更新队首的所有邻边,因为队首上一轮被更新。
            int j = val[i];
            if(dis[j]>dis[u] + w[i]){
                dis[j] = dis[u] + w[i];
                if(st[j] == false){
                    st[j] = true;
                    q.push(j);
                }
            }
        }
    }
    if(dis[n] > 0x3f3f3f3f/2) return -1;
    else return dis[n];
}
int main(){
    cin>>n>>m;
    memset(head,-1,sizeof(head));
    for(int i=0;i<m;i++){
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        add(a,b,c);
    }
    spfa();
    if(dis[n] > 0x3f3f3f3f/2) puts("impossible");
    else cout<<dis[n];
    return 0;
}

SPFA判断负环

#include<iostream>
#include<algorithm>
#include<queue>
#include<cstring>
using namespace std;
const int maxn = 2010,maxm = 1e5 + 10;
int n,m,dis[maxn],cnt[maxn];//增加数组cnt,表示从点1到点n所用的边数
int head[maxn],val[maxm],ne[maxm],w[maxm],idx;
bool st[maxn];
queue<int> q;
void add(int a,int b,int c){
    val[idx] = b,w[idx]=c,ne[idx]=head[a],head[a]=idx++;
}
int spfa(){
    memset(dis,0x3f,sizeof(dis));
    dis[1]=0;
    for(int i=1;i<=n;i++){
        q.push(i);
        st[i] = true;
    }
    while(q.size()){
        int u = q.front();
        q.pop();
        st[u] = false;
        for(int i=head[u];i != -1;i=ne[i]){
            int j =val[i];
            if(dis[j]>dis[u]+w[i]){
                dis[j]=dis[u] + w[i];
                cnt[j]=cnt[u] + 1;
                if(cnt[j]>=n) return true; //边数超过n,一定存在负环。
                if(st[j]==false){
                    q.push(j);
                    st[j]=true;
                }
            }
        }
    }
    return false;
}
int main(){
    cin>>n>>m;
    memset(head,-1,sizeof(head));
    for(int i=0;i<m;i++){
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        add(a,b,c);
    }
    bool t = spfa();
    if(t) puts("Yes");
    else puts("No");
    return 0;
}

  以上算法都是用于单起点,单终点最短路

Floyd

  适用于多起点,多终点最短路。

//时间复杂度一般是O(n^3)
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int maxn=210,inf=0x3f3f3f3f;
int g[maxn][maxn];
int n,m,q;
void floyd(){ //floyd算法
    for(int k=1;k<=n;k++){
        for(int i=1;i<=n;i++){
            for(int j=1;j<=n;j++){
                g[i][j] = min(g[i][j],g[i][k]+g[k][j]); //利用点k更新所有边
            }
        }
    }
}
int main(){
    cin>>n>>m>>q;
    for(int i= 1;i<=n;i++)
        for(int j = 1;j<=n;j++){
            if(i==j) g[i][j] = 0;
            else g[i][j] = inf;
        }
    for(int i=0;i<m;i++){
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        g[a][b] = min(g[a][b],c);
    }
    floyd();
    while(q--){
        int a,b;
        scanf("%d%d",&a,&b);
        if(g[a][b] > inf/2 ) puts("impossible");
        else printf("%d\n",g[a][b]);
    }
    return 0;
} 

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值