dijkstra:求单源最短路

算法步骤:
1. 找离起点x最近的未讨论过的点k。
2. 判断经过k点,起点x到其他点的距离是否缩短,如缩短
则更新。将k点标记为已讨论。
3. 重复进行第1步,直到所有点都被讨论过为止。

于是我们可以得出:

dis[i]=min{dis[k]+Map[k][i]}

1<=i<=n  k是未讨论过的,离起点最近的点

dis[i]表示从起点到i点最短距离

:为了方便,此篇文章部分采用邻接矩阵存图,建议各位换成更高效的存图方法。

:

void dijkstra(int x){
    for(int i=1;i<=n;i++){        初始化
        mark[i]=false;         mark记录第i号节点是否被讨论过
        dis[i]=Map[x][i];     初始化dis数组,dis[i]用于记录起点x到i的最短距离
    }
    mark[x]=true;           将起点标记为已被讨论,防止走回头路
    do{                            算法核心部分:
        minn=inf;
        k=0;            k记录离x最近的点的编号
        for(int i=1;i<=n;i++)     寻找当前离x最近且未被讨论过的节点
            if((mark[i]==false) && (dis[i]<minn)){
                minn=dis[i];
                k=i;
            }
               讨论经过k点,有没有其他点到x的距离缩短
        if(k>0){
            mark[k]=true;将k号点设置为已被讨论过
            for(int i=1;i<=n;i++)若经过k号点,起点x到i的距离缩短,则更新dis[i]
                if(dis[i]>dis[k]+Map[k][i])
                    dis[i]=dis[k]+Map[k][i];
        }
    }while(k>0);
}

时间复杂度 这肯定不行,得想办法优化!

可以发现,每次我们都是要找出离x最近的点的编号,于是我们便可以用强大的stl里的优先队列来完成这个操作,将其优化至

:dij+堆优化+链式前向星

struct node{
    int num,dis;       num记录节点编号,dis记录起点到该店的最短距离
    bool operator < (const int &a) const{
        return dis>a.dis;    重载< 以dis从小到大排序 
    }
};
void dijkstra(int s){
    for(int i=1;i<=n;i++){初始化
        dis[i]=inf;
        mark[i]=false;
    }
    dis[s]=0;
    priority_queue<node> q;以距离为关键字的小根堆,方便取出目前离起点最近的点
    q.push((node){s,0});
    while(q.size()){
        int x=q.top().num;
        q.pop();
        if(mark[x])  若离起点最近的点x已被讨论过,就跳过
            continue;
        mark[x]=true;
        for(int i=Last[x];i;i=Next[i]){  链式前向星
            int y=End[i];
            if(!mark[y] && dis[y]>dis[x]+len[i]){更新dis[]
                dis[y]=dis[x]+len[i];
                q.push((node){y,dis[y]});
            }
        }
    }
}

SPFA (他死了)

SPFA是Bellman-Ford算法的一种队列实现,减少了不必要的运算。

算法流程:

用一个队列来进行维护。初始时将起点加入队列。每次从队列中取出一个元素,并对他所连接的点进行松弛,若某个点松弛成功(则通过那个点到起点距离缩短),则将其入队。直到队列为空

简单的说就是队列优化的bellman-ford,利用了每个点不会更新次数太多的特点

SPFA的时间复杂度是O(kE),k一般取2左右(k是增长很快的
函数ackermann的反函数,2^65536次方也就5以下 ),可以处
理负边。

SPFA的实现甚至比Dijkstra或者 Bellman_Ford还要简单

queue<int> q;
void spfa(int s){    s为起点,求s到图中所有点的距离
    for(int i=1;i<=n;i++)
        dis[i]=inf;
    q.push(s);
    mark[s]=true;
    dis[s]=0;
    while(q.size()){
        int x=q.front();
        q.pop();
        mark[x]=false;
        for(int i=1;i<=n;i++)   讨论与x相连的点
            if(dis[x]+Map[x][i]<dis[i]){  若x通过i点到起点的距离缩短则更新
                dis[i]=dis[x]+Map[x][i];
                if(mark[i]==false){    若不在队列中,入队
                    q.push(i);
                    makr[i]=true;
                }
            }
    }
}

那么,上文说了SPFA可以处理负权边,那么遇到了负环(负权回路)该怎么处理呢?

用SPFA判断负权回路

让我们来想一想:
在SPFA算法中,每个点最多进队多少次?

很显然是n-1次,因为对于一个点x,最坏情况下是其余n-1个点都可以将其松弛。

所以如果有一个点进队次数超过了n次,我们就可以认定,该图中存在负权回路,可以果断结束SPFA了。

:

queue<int> q;
int cnt[N]    用于统计每个节点进队次数 
void spfa(int s){
    for(int i=1;i<=n;i++)
        dis[i]=inf;
    q.push(s);
    mark[s]=true;
    dis[s]=0;
    while(q.size()){
        int x=q.front();
        q.pop();
        mark[x]=false;
        for(int i=1;i<=n;i++)
            if(dis[x]+Map[x][i]<dis[i]){
                dis[i]=dis[x]+Map[x][i];
                if(mark[i]==false){
                    q.push(i);
                    cnt[i]++
                    if(cnt[i]==n){
                        cout<<"有负权回路";
                        return; 
                    }
                    makr[i]=true;
                }
            }
    }
}

floyd:计算每一对顶点的最短距离 (其实跟dp差不多)

算法思想:
枚举图中每一个顶点k,判断图中其他节点间的距离在经过k后是否缩短,如果是,则用新的更短距离取代原距离

:

floyd的数学表达式:
f[i][j]=min{f[i][k]+f[k][j]}
1<=i,j,k<=n

for(k=1;k<=n;k++)
    for(i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            if(Map[i][j]>Map[i][k]+Map[k][j])
                Map[i][j]=Map[i][k]+Map[k][j];               

的时间复杂度为

总结:

  1. SPFA很容易被卡,但是在判断负权回路时很好写
  2. 在做最短路问题时,优先考虑dijkstra
  3. floyd纯属娱乐,很少有题用到