图论(迪杰斯特拉,Floyd,bellman,spfa)

Posted 芜湖之肌肉金轮

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了图论(迪杰斯特拉,Floyd,bellman,spfa)相关的知识,希望对你有一定的参考价值。

对图论和搜索的学习感想

 

Dijkstra

迪杰斯特拉求最短路的暴力的思路是三重循环去更新所有点到起点的最短距离。

首先先初始化让第一个点到自己的距离是0即:

dist[1]=0;

然后在省下的点中找到距离起点最近的点(该点必须是没有被确定的点),然后再用该点到起点的距离更新所有点到起点的最短距离,每次更新一个最短,最短更新全部,所以第一重循环循环n次,从而达到寻找最短路的效果。

for(int i=0;i<n;i++)
    {
        //找出距离原点最短的点
        int t=-1;
        for(int j=1;j<=n;j++)
        {
            if(!str[j]&&(t==-1||dist[t]>dist[j]))t=j;
        }
        //加入集合
        str[t]=true;
        //更新距离
        for(int j=1;j<=n;j++)
        {
            dist[j]=min(dist[j],dist[t]+g[t][j]);
        }
    }

但是不是循环太多次了?如果遇到大的数据,时间肯定会超时,那么应该怎么办呢。其实迪杰斯特拉算法是可以用堆优化的。具体可以用到priority_queue优先队列去优化,为什么呢,首先我们在我们优化前,我们找到最短边,然后跟新所有点,这个时候会发生什么呢,有些点不一定会被更新,这样子我们效率就会比较低,当数据很大的时候,很容易爆掉。那么有什么是可以全面检索并且可以保证检索一次呢?这不是和宽搜很类似吗。所以我们可以用宽搜进行优化,当你更新成功时你才入队

 while(!pq.empty())
    {
        auto t=pq.top();
        pq.pop();
        
        int var = t.second, distance=t.first;
        
        if(str[var])continue;//如果已经更新过了就直接跳过,否则就开始更新,然后该点加入已更新的集合
        str[var]=true;
        
        
        for(int i=h[var];i!=-1;i=ne[i])
        {
            int j=e[i];
            if(dist[j]>distance+w[i])//如果你成功更新,则入队
            {
                dist[j]=distance+w[i];
                pq.push({dist[j],j});
            }
        }
        
    }

这样的时间复杂度就是o(nlogm)的。

bellman-ford和spfa

  bellman-ford和spfa一般是用于可能存在负权边的时候(spfa还可以用于判断负环)。bellman算法比较方便,那些点不需要什么特殊的方式去存,只要能存就可以了,仔细想想真的暴力,bellman算法的时间复杂度是o(nm)的然后他是遍历所有边和一步一步更新最后达到最优解。

struct{
    int p,o,l;
}e[N];
int dist[N],baccom[N];
int bellman()//这是用bellman-ford算法做有边数限制的最短路
{
    memset(dist,1000000,sizeof(dist));
    dist[1]=0;
    for(int i=0;i<k;i++)
    {
        memcpy(baccom,dist,sizeof(dist));
        for(int j=0;j<m;j++)//表示遍历m条边
        {
            int a=e[j].p,b=e[j].o,c=e[j].l;
            dist[b]=min(dist[b],baccom[a]+c);
        }
        
    }
    if(dist[n]>1000000/2)return -1;
    return dist[n];
}

那么为什么需要一个dist数组还要一个baccom数组呢,因为如果只用一个dist数组去更新的话,有可能让更新的点去更新别的点从而发生串联,这是我们不希望看到的,就是在一次迭代中,可能发生a->b更新完之后,立马又被拿来更新c了,这样可能会导致到终点的边的个数无法被限制,这是我们不想看到的,所以我们定义一个新的数组,每次更新都用这个新的数组去更新,保证每一次更新都是用的上一次更新好的值,从而避免串联。当然,如果只是单纯的做最短路不考虑串联也是可以的。

  spfa算法,可以说是bellman的优化,最好的情况是o(n),最坏也是和bellman一致:

void add(int a,int b,int c)//邻接表存图
{
    e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}
int spfa()
{
    memset(dist,0x3f,sizeof(dist));
    queue<int> q;
    q.push(1);
    dist[1]=0;
    stl[1]=true;
     while(!q.empty())
    {
        int cur=q.front();
        q.pop();
        stl[cur]=false;

        for(int i=h[cur];i!=-1;i=ne[i])
        {
            int j = e[i];
            if(dist[j]>dist[cur]+w[i])
            {

                dist[j]=dist[cur]+w[i];
                if(!stl[j])
                {
                    stl[j]=true;
                    q.push(j);
                }

            }
        }
    }
    
    if(dist[n]==0x3f3f3f3f)return -1;
    else return dist[n];
}

bellman算法是不管三七二十一我全部更新一遍,所以我们可以用宽搜的思路去优化———如果你更新成功,那么我才拿更新成功的距离去更新所有的点,stl这个bool数组是为了防止我们宽搜的时候队列里面存了重复的点,因为我们是拿队列里面的点去更新所有的点的(换句话说,如果除队列以外的点如果也能更新成功,那么就让该点也入队,去更新其他的点,所以我们才要防止重复的点进入队列)。最短路的话根据抽屉原理,另设一个cnt数组去存点的边的个数就好了。

Floyd

这个方法是o(n^3)的有点像是dp里面的记忆化,可以有效解决多源的最短路问题。

void floyd()
{
    for(int k=1;k<=n;k++)\\
     for(int i=1;i<=n;i++)
      for(int j=1;j<=n;j++)
       d[i][j]=min(d[i][j],d[i][k]+d[k][j]);
     
}

状态表示:从i这个点,到j这个点的最短距离。

 

以上来自我对于acwing中算法的学习见解。www.acwing.com

以上是关于图论(迪杰斯特拉,Floyd,bellman,spfa)的主要内容,如果未能解决你的问题,请参考以下文章

图论应用 floyd(弗洛伊德)算法dijkstra(迪杰斯特拉)算法

迪杰斯特拉算法为啥不能有负权边

图论——迪杰斯特拉算法和最小生成树

图论,最短路,堆优化迪杰斯特拉

数学建模暑期集训22:图论最短路径问题——Dijkstra算法和Floyd算法

Dijkstra算法和Floyd算法