最短路算法

Dijkstra

(1)解决问题:单源最短路,计算源点到其他点的最短路,要求边权非负

(2)核心操作:松弛 

 如a->b+b->c<a->c,故dis(a,c)=2

(3)算法思想:

  1. 将图中n个点分为两个集合S(已确定源点到其最短路的点的集合)和T(未确定源点到其最短路的点的集合),初始化S中只含有源点s,T=V-{s}。dis[s]=0;\forall u\in V,u\neq s,dis[u]=INF
  2. 从T中选取具有最小dis值的点u,将其加入u(此时s到u的最短路已算出,因为此时对于T中任意点v,有dis[v]>=dis[u],若v到u有边,由于图中边权非负,则dis[v]+w(v,u)>=dis[u],即dis[u]不会再变小),随后对所有从u出发的边进行松弛操作(由于最短路径的最优子结构,最短路s->t上的子路径u->v也必为u到v的最短路径,这由反证法可证。松弛操作相当于选择已经确定的点的最短路径去更新未确定的点)
  3. 显然每次2操作确定一个点的源点到它的最短路径,重复2步骤n次即计算出源点到其他点的最短路径。

(4)代码实现:

使用邻接表或链式前向星减少松弛操作不必要的遍历和堆优化查找T中具有最小dis值的点,复杂度O(ElogV)

struct E
{
    int to,next,w;
}edge[maxm];
int head[maxn],tol;
void Addedge(int u,int v,int w)
{
    edge[tol].to=v;edge[tol].w=w;edge[tol].next=head[u];head[u]=tol++;
}
struct Node
{
    int u,d;
    Node(int x,int y):u(x),d(y){}
    bool operator<(const Node&p)const
    {
        return d>p.d;//源点到其距离d小的优先
    }
};
priority_queue<Node>Q;
int dis[maxn];
bool vis[maxn];
void dijkstra(int s)
{
    fill(dis,dis+maxn,INF);
    memset(vis,0,sizeof(vis));
    Q.push(Node(s,dis[s]=0));
    while(!Q.empty())
    {
        int u=Q.top().u;Q.pop();
        if(vis[u]) continue;//若u已确定最短距离则跳过
        vis[u]=1;
        for(int i=head[u];i!=-1;i=edge[i].next){
            int v=edge[i].to;
            //松弛操作
            if(!vis[v]&&dis[v]>dis[u]+edge[i].w){
                dis[v]=dis[u]+edge[i].w;
                Q.push(Node(v,dis[v]));
            }
        }
    }
    return;
}

 

Spfa

(1)解决问题:单源最短路,图中边权可负(有负环则无解),可判负环

(2)算法思想同bellman-ford,不存在负环时,每个点的最短路不含环(去掉环即可),而由Dijkstra算法知图中无负环时,每轮松弛操作确定一个点的最短路径,根据最短路径的最优子结构,每次松弛经过已确定最短路的点去更新别的点,那么一个点最多被松弛n-1轮。如果n-1轮后某些点的最短路径还能更小(被松弛),说明图中存在负环。并且被松弛过的点dis变小了,才有可能去松弛别的点,因此可以用个容器存储这些被松弛过的点,不必每轮都考虑每条边能否松弛。

(3)代码实现:

使用队列优化,若对判负环的需求更大(如差分约束系统判定是否有解),用栈代替队列更优。

struct E
{
    int to,next,w;
}edge[maxm];
int head[maxn],tol;
void Addedge(int u,int v,int w)
{
    edge[tol].to=v;edge[tol].w=w;edge[tol].next=head[u];head[u]=tol++;
}
queue<int>Q;
int dis[maxn],cnt[maxn];
bool vis[maxn];
bool Spfa(int s,int n)
{
    fill(dis,dis+maxn,INF);
    memset(vis,0,sizeof(vis));//记录每个点是否已在队列中
    memset(cnt,0,sizeof(cnt));//记录每个点入队次数
    dis[s]=0;
    Q.push(s);
    while(!Q.empty())
    {
        int u=Q.front();Q.pop();
        vis[u]=0;//点出队
        for(int i=head[u];i!=-1;i=edge[i].next){
            int v=edge[i].to;
            //松弛操作
            if(dis[v]>dis[u]+edge[i].w){
                dis[v]=dis[u]+edge[i].w;
                if(!vis[v]){
                    Q.push(v);
                    vis[v]=1;
                    if(++cnt[v]>=n) return false;
                    //每轮松弛入一次队,入队超过n次说明有负环
                }
            }
        }
    }
    return true;
}

 

 

Floyed

(1)解决问题:多源最短路,任意两个点之间的最短路,图中可有负权边

(2)算法思想:动态规划,同样根据最短路径的最优子结构,每次考虑加入新的中间点k,观察dis(u,k)+dis(k,v)<dis(u,v)?如果是则松弛一下。

设状态dis[k][i][j]表示i到j的可经过0-k的点的最短路径(0表示不经中间点)。

考虑一个新中间点的影响,状态转移方程为:dis[k][i][j]=min(dis[k-1][i][j],dis[k-1][i][k]+dis[k-1][k][j])

由于每次只用到k和k-1的状态,故可状态压缩dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j])

(3)代码实现(O(V³)):

可以直接把边存入初始的dis矩阵,注意重边的处理。

const int maxn=110;
const int INF=0x3fffffff;
int dis[maxn][maxn];
void Floyed(int n)
{
    for(int k=1;k<=n;k++){
        //每次考虑新中间点k的影响
        for(int i=1;i<=n;i++){
            for(int j=1;j<=n;j++){
                dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
            }
        }
    }
    return;
}

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值