zoukankan      html  css  js  c++  java
  • 最短路径算法

    Floyed算法

    Floyed-Warshall 算法用来找出每对点之间的最短距离。它需要用邻接矩阵来储存边,这个算法通过考虑最佳子路径来得到最佳路径。 注意单独一条边的路径也不一定是最佳路径。

    思想:
    从任意一条单边路径开始。所有两点之间的距离是边的权,或者无穷大,如果两点之间没有边相连。
    对于每一对顶点 u 和 v,看看是否存在一个顶点 w 使得从 u 到 w 再到 v 比己知的路径更短。如果是更新它。

    特点:
    Floyed算法,是最简单的最短路径算法,可以计算图中任意两点间的最短路径。Floyed的时间复杂度是O (N3),适用于出现负边权的情况。

    算法描述:
    初始化:点u、v如果有边相连,则dis[u][v]=w[u][v]。
    如果不相连则dis[u][v]=0x7fffffff
    for(int k=1;k<=n;k++)
    for(int i=1;i<=n;i++)
    for(int j=1;j<=n;j++)
    if(dis[i][j]>dis[i][k]+dis[k][j])
    dis[i][j]=dis[i][k]+dis[k][j];
    算法结束:dis[i][j]得出的就是从i到j的最短路径。

    #include<iostream>
    using namespace std;
    const int maxn=9999999;
    int n,dis[101][101];
    int x,y,z;
    int main()
    {
        cin>>n;
        for(int i=1;i<=n;i++)
          for(int j=1;j<=n;j++)
          dis[i][j]=maxn;
        for(int i=1;i<=n;i++)
          for(int j=1;j<=n;j++)
          {
            cin>>x>>y>>z;
            dis[x][y]=dis[y][x]=z;
          }
        for(int k=1;k<=n;k++)
          for(int i=1;i<=n;i++)
            if(i!=k)
            for(int j=1;j<=n;j++)
            if(j!=k&&j!=i&&dis[i][j]>dis[i][k]+dis[k][j])
            dis[i][j]=dis[i][k]+dis[k][j];
        //根据题目输出答案 
        return 0;
    }

    算法分析:
    三层循环,第一层循环中间点k,第二第三层循环起点终点i、j,算法的思想很容易理解:如果点i到点k的距离加上点k到点j的距离小于原先点i到点j的距离,那么就用这个更短的路径长度来更新原先点i到点j的距离。
    我们在初始化时,把不相连的点之间的距离设为一个很大的数,不妨可以看作这两点相隔很远很远,如果两者之间有最短路径的话,就会更新成最短路径的长度。

    Dijkstra算法

    用来计算从一个点到其他所有点的最短路径的算法,是一种单源最短路径算法。也就是说,只能计算起点只有一个的情况。

    思想:
    Dijkstra的算法思想,就是一开始将起点到起点的距离标记为0,而后进行n次循环,每次找出一个到起点距离dis[u]最短的点u,将它标记为访问过的点(白点)。随后枚举所有的未访问过的点vi(蓝点),如果以此白点为中转到达蓝点vi的路径dis[u]+w[u][vi]更短的话,这将它作为vi的“更短路径”dis[vi](此时还不确定是不是vi的最短路径)。

    特点:
    Dijkstra的时间复杂度是O (N2),它不能处理存在负边权的情况。

    算法描述:
    设起点为s,dis[v]表示从s到v的最短路径,pre[v]为v的前驱节点,用来输出路径。
    a)初始化:dis[v]=∞(v≠s); dis[s]=0; pre[s]=0;
    b)For(i=1;i<= n i++)
    1.在没有被访问过的点中找一个顶点u使得dis[u]是最小的。
    2.u标记为已确定最短路径
    3.For与u相连的每个未确定最短路径的顶点v
    if(dis[u]+w[u][v]< dis[v])
    {
    dis[v]=dis[u]+w[u][v];
    pre[v]=u;
    }
    c)算法结束:dis[v]为s到v的最短距离;pre[v]为v的前驱节点,用来输出路径。

    #include<iostream>
    using namespace std;
    const int maxn=9999999;
    int n,x,y,z,k,minn;
    int dis[101],a[101][101];
    bool flag[101];
    int main()
    {
        cin>>n;
        for(int i=1;i<=n;i++)
        {
            cin>>x>>y>>z;
            a[x][y]=a[y][x]=z;
            dis[i]=maxn;
        }
        dis[1]=0;
        for(int i=1;i<=n;i++)
        {
            minn=maxn;
            k=0;
            for(int j=1;j<=n;j++)
            if(!flag[j]&&dis[j]<minn)
            {
                minn=dis[j];
                k=j;
            }
            if(k==0) break;
            flag[k]=true;
            for(int j=1;j<=n;j++)
            if(a[k][j]!=0&&!flag[j]&&dis[j]>dis[k]+a[k][j])
            dis[j]=dis[k]+a[k][j];
        }
        //根据题目输出答案  
        return 0;
    } 

    算法分析:
    从起点到一个点的最短路径一定会经过至少一个“中转点”(例如下图1到5的最短路径,中转点是2。特殊地,我们认为起点1也是一个“中转点”)。显而易见,如果我们想求出起点到一个点的最短路径,那我们必然要先求出中转点的最短路径(例如我们必须先求出点2 的最短路径后,才能求出从起点到5的最短路径)。
    换句话说,如果起点1到某一点V0的最短路径要经过中转点Vi,那么中转点Vi一定是先于V0被确定了最短路径的点。

    这里写图片描述 这里写图片描述

    我们把点分为两类,一类是已确定最短路径的点,称为“白点”,另一类是未确定最短路径的点,称为“蓝点”。如果我们要求出一个点的最短路径,就是把这个点由蓝点变为白点。从起点到蓝点的最短路径上的中转点在这个时刻只能是白点。

    让我们对以上这段枯燥的文字做一番模拟,加深理解。
    算法开始时,作为起点的dis[1]=0,其他的点dis[i]=9999999

    这里写图片描述

    这时dis[2],dis[3],dis[4]被它的最后一个中转点1修改为了最短路径。

    这里写图片描述

    这时dis[3],dis[5]被它们的最后一个中转点2修改为了最短路径。

    这里写图片描述

    这时dis[4]也被它的最后一个中转点3修改为了最短路径。

    接下来的两轮循环将4、5也变成白点。N轮循环结束后,所有的点的最短路径即能求出。

    Dijkstra无法处理边权为负的情况
    例如下图
    这里写图片描述

    2到3的边权值为-4,显然从起点1到3的最短路径是-2(1→2→3),但是dijskstra在第二轮循环开始时会找到当前dis[i]最小的点3,并标记它为白点。
    这时的dis[3]=1,然而1却不是从起点到点3的最短路径。因为3已被标记为白点,最短路径值dis[3]不会再被修改了,所以我们在边权存在负数的情况下得到了错误的答案!

    SPFA算法

    SPFA(Shortest Path Faster Algorithm)(队列优化)算法是求单源最短路径的一种算法,它还有一个重要的功能是判负环(在差分约束系统中会得以体现),在Bellman-ford算法的基础上加上一个队列优化,减少了冗余的松弛操作,是一种高效的最短路算法。

    思想:
    初始时将起点加入队列。每次从队列中取出一个元素,并对所有与它相邻的点进行修改,若某个相邻的点修改成功,则将其入队。直到队列为空时算法结束。

    特点:
    这个算法,简单的说就是队列优化的bellman-ford,利用了每个点不会更新次数太多的特点发明的此算法。
    SPFA 在形式上和广度优先搜索非常类似,不同的是广度优先搜索中一个点出了队列就不可能重新进入队列,但是SPFA中一个点可能在出队列之后再次被放入队列,也就是说一个点修改过其它的点之后,过了一段时间可能会获得更短的路径,于是再次用来修改其它的点,这样反复进行下去。
    算法时间复杂度:O(kE),E是边数。K是常数,平均值为2。

    算法描述:
    dis[i]记录从起点s到i的最短路径,w[i][j]记录连接i,j的边的长度。pre[v]记录前趋。
    team[1..n]为队列,头指针head,尾指针tail。
    布尔数组exist[1..n]记录一个点是否现在存在在队列中。
    初始化:dis[s]=0,dis[v]=∞(v≠s),memset(exist,false,sizeof(exist));
    起点入队team[1]=s; head=0; tail=1;exist[s]=true;
    do
    {
    1、头指针向下移一位,取出指向的点u。
    2、exist[u]=false;//已被取出了队列
    3、for与u相连的所有点v
    if (dis[v]>dis[u]+w[u][v])
    {
    dis[v]=dis[u]+w[u][v];
    pre[v]=u;
    if (!exist[v]) //队列中不存在v点,v入队。
    {
    //尾指针下移一位,v入队;
    exist[v]=true;
    }

    }
    while (head < tail);
    循环队列:
    采用循环队列能够降低队列大小,队列长度只需开到2*n+5即可。

    #include<iostream>
    using namespace std;
    const int maxn=9999999;
    int n,x,y,z,head,tail,u;
    int dis[101],a[101][101],pre[101],team[100001];
    bool exsit[101];
    int main()
    {
        cin>>n;
        //输入起点,终点
        for(int i=1;i<=n;i++)
        {
            cin>>x>>y>>z;
            a[x][y]=a[y][x]=z;
            dis[i]=maxn;
        }
        dis[1]=0;
        team[1]=1;//起点入队(此处默认起点为1)
        do
        {
            head++;
            u=team[head];
            exsit[u]=false;
            for(int j=1;j<=n;j++)
            if(dis[j]>dis[u]+a[u][j]&&a[u][j])
            {
                dis[j]=dis[u]+a[u][j];
                pre[j]=u;
                if(!exist[j])
                {
                    tail++;
                    team[tail]=j;
                    exsit[j]=true;
                }
            }
        }while(head<tail);
        //根据题目输出答案  
        return 0;
    } 
  • 相关阅读:
    flex布局
    redis持久化的四种方式
    list all index in elasticsearch
    Java Thread停止关闭
    关于线程的一些操作方法
    将redis key打印到文本
    spout和bolt
    java读取redis的timeout异常
    storm中,ack与fail
    好文要收藏(大数据)
  • 原文地址:https://www.cnblogs.com/cax1165/p/6071020.html
Copyright © 2011-2022 走看看