最短路之 dijkstra & floyed &SPFA

最短路径问题:

       寻找图中两个结点之间的最短路径。

求最短路算法:

a:dijkstra算法

      解决指定起点和终点的最短路径,是以最短路径长度递增,逐次生成最短路径的算法,以起始点为中心向外层层扩展,直到扩展到终点为止。但不能处理负权边。

b:floyed算法

      解决任意两点间的最短路径,可处理有向图或负权的最短路径,寻找任意中间点使得两点间的距离最短。

c:SFPA算法

      是求单源最短路径的一种算法,可以判断是否存在负环(但无法处理带负环的图)


dijkstra 算法:

方法:一个带权图,从顶点出发,将顶点分成两组,第一组为已求出最短路径的顶点集合(用S表示,初始时S中只有一个源点,之后每求出一条从源点出发的最短路径,就将顶点放到S数组中,这样S数组中从源点到每一个顶点都是最短路,直到将全部顶点都加入到S中,最短路已找到)第二组为其他未确定最短路径的顶点集合(用U表示)在加入点的过程中,要保持从源点到S中各顶点的最短路径长度不大于U数组中的点做中间节点到达顶点的长度。U数组中顶点的距离,是从源点以S数组中的点为中间点到顶点的距离

1,开始时,S只包括源点,其他点都在U中,若源点到U中的顶点可达,则初始化为距离,否则初始化为正无穷。

2,从U中选择当前距离源点最近的顶点k,并将其放到S中(该距离为源点到k的最短路径)

3,以k为中间点,比较修改源点到U中各顶点的距离,若源点v经过k到大顶点u的距离小于原来距离(之前不经过k时的距离),则修改源点到顶点u的距离,否则不修改。

4,重复2和3直到所有顶点都包含在S中。

例如:一个简单的图,源点为1,其他顶点有2,3  ;1到2的距离为3,1 到3 的距离为8,2到3 的距离为3;则开始时只有1在S数组里,2和3在U数组里,之后在U里选择顶点2,因为1到2的距离小于1到3的距离,然后以2为中间点,比较1经过2到大3的距离与1直接到达3的距离,发现1经国到达3的距离为6小于直接到3的距离,则修改1到3 的距离为6;当然这里如果1到3的距离为5的话就不修改咯。


 void dijkstra(int start,int n)
 {
     int next,i,j,mindis;
     for(i=1;i<=n;++i)
     {
         dis[i]=map[start][i];
         visit[i]=0;
     }
     visit[start]=1;
     for(i=1;i<=n;++i)
     {
         mindis=INF;
         for(j=1;j<=n;++j)
         {
             if(!visit[j]&&dis[j]<mindis)
             {
                 mindis=dis[j];
                 next=j;
             }
         }
         visit[next]=1;
         for(j=1;j<=n;++j)
         {
             if(!visit[j]&&dis[j]>dis[next]+map[next][j])
             dis[j]=dis[next]+map[next][j];
         }
     }
 }


floyed 算法:

方法:一个带权的图,从顶点u到顶点v,不是直接到达就是通过若干个结点到达。假设Dis ( i , j )为当前顶点 i 到顶点 v 的距离,对于每一个顶点k ,若Dis ( u ,v ) > Dis (u , k )+Dis( k , v )成立,则修改Dis ( u , v ),如此,遍历完所有的k 也就找到了从u 到v 的最短路径了。

1,初始化距离,若两顶点之间可达则为实际距离,不可达初始化为无穷大。

2,让 k 从1开始遍历,对于任意的从i 到j 是否满足从i 到 k 在到 j 的距离小于从i 到 j 的距离(i j 为任意顶点,相当于遍历所有顶点),若满足,则修改,之后,Dis ( i , j )所储存的值就是从i 到j 的最短路径距离了。(所以算法结束后得到的是任意两点间的最短距离)


 void floyed(int n)
 {
     int i,j,k;
     for(k=1;k<=n;++k)
     {
         for(i=1;i<=n;++i)
         {
             if(map[k][i]==INF)
                continue;
             for(j=1;j<=n;++j)
             {
                 if(map[i][j]>map[i][k]+map[k][j])
                 map[i][j]=map[i][k]+map[k][j];
             }
         }
     }
 }


SPFA 算法:

方法:用邻接表来储存图,设立一个队列来保存待优化的结点,初始时将源点入队。每从队列中取出队头顶点u (消除标记)将与u 相连的所有点 v 进行松弛操作,如果能更新估计值,则更新,更新之后,若v 没有在队列中,要将点v 入队(标记),若已经在队列中,则不用处理。循环操作,直到队列为空。

判断有无负环:若某个点进入队列的次数超过n 次,则存在负环(SPFA无法处理带负环的图)

1,初始化,dis 数组初始化为无穷大,vis 标记数组初始化为0,源点的vis 数组标记为1,dis 数组距离为0 ,used 数组初始化为0,记录顶点入队的次数(判断负环是否存在)源点used 数组为1,已使用一次。之后将源点入队。

2,取队头元素u 并消除标记之后要删除,对与u 相连的所有顶点v 进行松弛操作,即比较dis[v] > dis[ u ]+edge[ i ].val ,若满足则更像dis[ v] 的值,若v 并不在队列中,则入队,标记并used[ v ] ++表示入队次数加1

3,循环2,直到队列为空为止。

邻接表建图

struct node
{
    int from,to,val,next;
};
node edge[maxm];
void initialize()
{
    cnt=0;
    mark=0;
    memset(head,-1,sizeof(head));
}
void add(int a,int b,int c)
{
    edge[cnt].from=a;
    edge[cnt].to=b;
    edge[cnt].val=c;
    edge[cnt].next=head[a];
    head[a]=cnt++;
}

SPFA 代码实现

void spfa(int s)
{
    int flag=0;
    queue<int>q;
    int vis[maxn],dis[maxn],used[maxn];
    memset(dis,inf,sizeof(dis));
    memset(vis,0,sizeof(vis));
    memset(used,0,sizeof(used));
    vis[s]=1;
    dis[s]=0;
    used[s]++;
    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].val)
            {
                dis[v]=dis[u]+edge[i].val;
                if(!vis[v])
                {
                    vis[v]=1;
                    used[v]++;
                    if(used[v]>n)
                    {
                       flag=1;
                       break;
                    }
                    q.push(v);
                }
            }
        }
    }
    if(flag)
        printf("YES\n");
    else
        printf("NO\n");
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值