图论最短路径

    xiaoxiao2022-07-07  196

    Floyed(弗洛伊德)算法:

    点u、v如果有边相连,则dis[u][v]=w[u][v]。w表示u-v点的距离

    如果不相连则dis[u][v]=0x7fffffff,表示为无穷

    For (k = 1; k <= n; k++)  

      For (i = 1; i <= n; i++)  

        For (j = 1; j <= n; j++)   

          If (dis[i][j] >dis[i][k] + dis[k][j])   

              dis[i][j] = dis[i][k] + dis[k][j];

    这是算法的核心代码,通过三重循环可以找到两点之间的最短距离,注意,是所有两个点之间的最短距离,但是时间复杂度有点高,是n的三次方。适合处理不确定起点,数据量较小的问题。

    变形:

    For (k = 1; k <= n; k++)

      For (i = 1; i <= n; i++)

        For (j = 1; j <= n; j++)

      dis[i][j] = dis[i][j] || (dis[i][k] && dis[k][j]);

    因为Floyed算法找到了所有的两点之间最短距离,故在没有边权的情况下,可以使边权是bool数,判断其是否相连。

     

    dijkstra迪杰斯特拉算法:(从我以前的博客复制粘贴的,补充图论的最短路径的方法):

    迪杰斯特拉算法不同于Floyed算法,迪杰斯特拉算法趋向于解决知道起点的问题,故不需要找到所有两点之间的最短距离,只需要找到起点距离终点的最短距离,故时间复杂度仅有n方。

    迪杰斯特拉算法是一个最短路径的方法,在这里简称dij,它在确立出发点的基础上,设出发点是start,你想到达的点是end,其中一共有n个点,我们可以先找一步到达的距离,然后与分两步到达的距离比较,取最小值,然后让得到的结果与三步到达的距离比较,取最小值,最终让得的的最小值与n-1部到达的距离比较,取最小值,这样的最小值就是我们要得到的距离最少的最优点了。

    但dij在c++中实现可不是那么好实现的,我们要把点之间的距离全部得到,在刚开始的几部中如果你要求start到end之间的距离,比如1,2,3点,1-2之间距离是1,2-3之间距离是1,1-3之间距离是1,这样1-3的距离最短就是1,为了避免类似1-2-3这样的情况,就是我上面一段说的那些了,我们要分步的比较,在c++中我们一般会用这样的方法:min是否大于1-3+3-4这样的形式,而1-3本身可能并不是最小值,因此我们在计算过程中不只把start到end的最小值算了出来,我们还把start到所有点的最小值算了出来。下面介绍一下dij在c++中实现的函数:

     

    int i,j,k,min,tmp;

    void dij(int start,int dist【】,flag【】) //dist【i】意是点start到点i的距离,flag【i】=0意思是点start到i的距离最小值未得到,=1意思是已经得到最短距离。我们确立flag的目的是防止重复与过多的循环。

    {

    for(i=1;i<=n;++i)     //详细的没有指出,n是指全部的点

    {flag[i]=0;

    dist[i]=long[1][i]     //long表示1-i之间的距离   }

    flag【start】=1;  //出发点与出发点的最短距离,默认为已知

    dist【start】=0;  //出发点与出发点的距离0;

    //这之后就是在从start1步到达end和n-1步到达end的最短距离的比较选择,也是dij中的核心

    for(i=1;i<n;++i){

    min=inf;   //inf是一个极大值,表示无穷大的数。

    for(j=1;j<=n;++j) if(flag[j]==0&&dist[j]<min)  {min=dist[j]; k=j;}

    flag[k]=1;  //因为一步里面到达的它是距离最少的,所以start到k点距离最小。

    //下面的循环就是求第二步到第n-1步最小值的。不太好掌握

    for(j=1;j<=n;++j){ tmp= long[k][j]==INF ? INF : (min + mMatrix[k][j]));  if(flag[j]==0&&dist[j]>temp) dist【j】=tmp;}//不仅如此,在这一步中我们得到了每一个分两步到达各点的距离的最小值,然后返回上一个循环找到两步到达的最小值的点,然后再进行这一个循环得到分三步到达的点的距离的最小值,以此类推。

    然后我们想要到哪个点,直接用dist【i】就是最小距离点了。

    }

     

     

    Bellman-Ford算法O(NE):

    这是一种可以解决有负边权的算法,但是不能够解决负边权回路的问题(至于为什么之后再补)。同迪杰斯特拉算法相似,这个算法有起点,是找起点离所有点的最短距离。

    dis[s]=0,dis[v]=∞(v≠s),pre[s]=0;//s是起始点无穷可以是0x7fffffff

    实现原理:

    For (i = 1; i <= n-1; i++)

    For (j = 1; j <= E; j++)         //枚举所有边,注意是边!!! 

     if (dis[u]+w[j]<dis[v])          //u、v是这条边连接的两个点。  

    {        dis[v] =dis[u] + w[j];      //求距离  

    pre[v] = u;   }  //pre在这里表示的是v点的前面走的是u

     

    SPFA算法O(kE):

    这个算法是Bellman-Ford算法的队列实现。

    开始时把起点放入队列,之后从队列取出一个元素并把它相邻的点修改,若相邻的点修改成功则入队,直到队列为空。其形式上与广度优先搜索非常相似,但是其队列中取出的元素还可以返回,这是其一大特点。

    算法实现:

     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),//依然无穷可以是0x7fffffff

    memset(exist,false,sizeof(exist));

     team[1]=s; head=0; tail=1;exist[s]=true;   //将第一个点入队

    do     {if (dis[v]>dis[u]+w[u][v])             //头指针要指向当前点的前驱方向,既u

      { dis[v]=dis[u]+w[u][v];

              pre[v]=u;

              if (!exist[v]) //如果未false,说明已经被取出 

    {//否则指针指向下一个,v入队            

    exist[v]=true;            }       }     }    

    while (head < tail);

     

    最新回复(0)