最短路算法汇总


以下为最短路总结:


最短路问题可分为:


一、单源最短路径算法,解决方案:Bellman-Ford算法,Dijkstra算法,SPFA

二、每对顶点间的最短路径算法:Floyd;


(1).Dijkstra算法:


(经典的算法,可以说是最短路问题的首选事例算法,但是不能处理带负权的边,因为该算法要遍历的点过多,效率低下,用时长,仅限于小数据,不常用)
  
   基本思想:
        Dijkstra算法,本质上是利用贪心思想来不停的来进行贪心选择,查找最优解,开辟一个s[],用来存放这些点,

  dis[]用来存放所能经过的每个点的最短距离,并进行dis[]的更新操作。注意:只能处理无负权的边


  
模版如下:
   int m,n,map[max][max],dis[max],s[max];
//int prev[max];//当前点的上一个节点
   
    void Dijkstra(int v0)
  {
    for(int i = 0;i<n;i++) //初始化dis[]
    {
        s[i] = 0;
        dis[i] = map[v0][i];
     /*   if(i!=v0 && map[v0][i]<inf)
        prev[i] = v0
         else 
        prev[i] = -1;  */
    }
    s[v0] = 1;
    dis[v0] = 0;
    for(int i = 2;i<=n;i++)
    {
        int u = v0,min = inf;
        for(int j = 0;j<n;j++)//贪心查找还未存储的最优解的点,并储存
        {
            if(!s[j] && dis[j] < min)//当前最短距离
            {
                u = j;//记录下标
                min  = dis[j];
            }
        }
        s[u] = 1;
        for(int j = 0;j<n;j++)//更新dis[]数组
        {
            if(!s[j] && map[u][j] < inf && dis[u] + map[u][j] < dis[j])


            {
                 dis[j] = map[u][j] + dis[u];
                 //prev[j] = u;
             }
                
        }
    }
  }


优先队列的Dijkstra

HDU 1874

#include <iostream>
#include <cstdlib>
#include <cstring>
#include <cstdio>
#include <queue>
#include <vector>
const int N = 10001;
const int INF = 1e7;
using namespace std;
struct node{
    int x,d;
    node(){}
    node(int a,int b)
    {
        x=a; d=b;
    }
    bool operator <(const node & a)const
    {
        if(d == a.d)
           return  x < a.x ;
        else
           return d > a.d;
    }
};
vector <node> ma[N];
int dis[N],n;
void Dijkstra(int s)
{
    for(int i=0;i<=n;i++)
        dis[i]=INF;
    dis[s]=0;
    priority_queue<node> q;
    q.push(node(s,dis[s]));
    while(!q.empty())
    {
        node p=q.top();
        q.pop();
        int s = ma[p.x].size();
        for(int i=0;i<s;i++)
        {
            node pp=ma[p.x][i];
            if(dis[pp.x]>p.d+pp.d)
            {
                dis[pp.x]=p.d+pp.d;
                q.push(node(pp.x,dis[pp.x]));
            }
        }
    }
}
int main()
{
    int a,b,c,m,s,e;
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        for(int i=0;i<=n;i++)
            ma[i].clear();
        while(m--)
        {
            scanf("%d%d%d",&a,&b,&c);
            ma[a].push_back(node(b,c));
            ma[b].push_back(node(a,c));
        }
        scanf("%d%d",&s,&e);
        Dijkstra(s);
        if(dis[e]!=INF)
            printf("%d\n",dis[e]);
        else
            puts("-1");
    }
    return 0;
}
// 题意: 有n*m矩阵,从起点(sx,sy)出发,可以上下左右四个方向移动,
// 若两个位置上是相同字符,则花费为0,否则为1,求到终点的最短距离
// 用Dijkstra算法解决,但会 TLE  ,需要用 优先队列 优化时间

#include <iostream>        // 邻接矩阵+优先队列实现Dijkstra算法
#include <stdio.h>
#include <queue>
#include <cstring>
using namespace std;                                
const int INF=300000;
const int MAXN=250000;
int n,m,distD[MAXN],done[MAXN];    
int sx,sy,tx,ty;    //起点(sx,sy)和终点(tx,ty)
int dx[4]={0,0,1,-1},dy[4]={1,-1,0,0};
char data[500][500];
typedef pair<int,int> pii;

void Dijkstra(int st)    //从源点st到其余各顶点的最短路径长度
{
    priority_queue<pii, vector<pii>, greater<pii> > q;
    fill(distD,distD+n*m,INF);        //结点下标从0开始,共有 m * n 个结点
    distD[st]=0;
    fill(done,done+n*m,0);
    q.push(make_pair(distD[st], st));
    while(!q.empty()) 
    {
        pii u = q.top();
        q.pop();
        int p = u.second;
        if(done[p])
            continue;
        done[p] = 1;
        if( p == tx * m + ty )    // 如果探测到终点即可退出
        {
            printf("%d\n",distD[p]);    
            break;
        }
        for(int d=0;d<4;++d)
        {
            int newx=p/m+dx[d],newy=p%m+dy[d];
            int newp=newx*m+newy;
            if(newx>=0&&newx<n&&newy>=0&&newy<m&& !done[newp] )
            {
                int w = ( data[p/m][p%m] == data[newx][newy] ) ? 0 : 1 ;
                if( distD[p] + w < distD[newp] )
                {
                    distD[newp]=distD[p]+w;
                    q.push(make_pair(distD[newp], newp));
                }
            }
        }
    }
}
int main()
{
    while(scanf("%d%d",&n,&m)!=EOF)        // n 行 m 列
    {
        for(int i=0;i<n;++i)
        {
            scanf("%s",data[i]);
        }
        scanf("%d%d%d%d",&sx,&sy,&tx,&ty);
        Dijkstra(sx*m+sy);    // 顶点坐标[x,y],则在队列对应的下标是 x * m + y
    }
    return 0;
}





(2).Bellman-Ford
     
     (用来判断是否有回环,可处理带负权边 时间复杂度O(V*E).)
  
     基本思想:(DP思想,查找最优解,并不断的进行松弛操作,但是算法浪费了许多时间做冗杂的松弛操作,效率降低) 无向图
void add()
{
    scanf("%d%d%d",&u,&v,&w);
    edge[l].u = u; edge[l].v = v; edge[l++].w = w;
    edge[l].u = v; edge[l].v = u; edge[l++].w = w;
}


int Bellman(int cnt)
{
    int i,j,flag;
    for(i = 1;i <= N - 1;i++)
    {
        flag = 0;
        for(j = 0;j <= count - 1;j++)
          {
              if(dis[edge[j].u]> dis[edge[j].v] + edge[j].w)
                {    //松弛计算
                dis[edge[j].u] = dis[edge[j].v] + edge[j].w;//更新dis[]数组,使其存储 源点->当前点的最短距离
                flag = 1;
                }
          }
        if(flag==0)//如果查找完,或者有些点达不到,跳出->优化
            break;
    }
    for(i = 0;i < count;i++)//判断是否有负环路
        {
            if(dis[edge[i].u] > dis[edge[i].v] + edge[i].w)//更新完dis[]后,如果还存在 该成立条件,则存在负环路
            return 1;                                     
        }                                              //  如1->2 权值2
    return 0;                                         //     2->3 权值5
                                                      //     3->1 权值-8
                                                    //       1->2->3->1 一次循环为-1 二次-2  三次....这就叫负环
}

(2)FLOYD
        这个实在没什么好说的,做题目时,只要确定的 时间不超,就可以用,前提是记下,时间复杂度O(n*n*n).

ma[u][v] 储存的就是u->v的距离
 
 void init() 
{  
         for(i=0;i<n;i++)
        for(j=0;j<n;j++)
        {
           map[i][j] = (i==j)?0:inf;
        } 
 }
void floyd()
{
         for(k=0;k<n;k++)
      {
          for(i=0;i<n;i++)
            {
                for(j=0;j<n;j++)
                {
                  if(map[i][k]!=inf && map[k][j]!=inf && map[i][j]>map[i][k] + map[k][j] )
                   {
                    map[i][j]=map[i][k]+map[k][j];
                   }
                 }
             }
      }
}

(4)SPFA


     书上说是在bellman-ford的基础上,添加一个队列操作,减少了其不必要的松弛操作。

我个人认为是在BFS搜索的基础上加了一步所谓的松弛操作,至于为什么叫松弛,不懂。

但是SPFA优化的很棒,以后最短路问题,尽量用它。无向图


矩阵式

void SPFA(int s,int e)   S点 到e点
{
    int l,r,i;
    l=r=0;
    memset(vt,0,sizeof(vt));
    for(i=s;i<e+1;i++)
    dis[i]=inf;
    dis[s]=0;
    q[r++]=s;//进队列
    vt[s]=1;//标记 进队列1
            //不在队列为0
    while(l<r)
    {
        int p=q[l++];//出队列
        for(i=0;i<n;i++)//枚举与p相连的点
        {
            if(dis[i]>dis[p] + map[p][i])//松弛
            {
                dis[i] = dis[p] + map[p][i];
                if(!vt[i])
                {
                    q[r++] = i;
                    vt[i] = 1;
                 }
            }
        }
        vt[p] = 0;
    }
    if(dis[e]!= inf)
    printf("%d\n",dis[e]);
}

前向星式

struct node{
    int u,v,w;
    int next;
}edge[40000];
int head[40000];
void init()
{
     memset(vis,0,sizeof(vis));
     memset(head,0,sizeof(head));
     for(int i = 0;i<n;i++)
     {
            dis[i] = INF;
     }
     t = 1;
}
int s,e;
void add(int a,int b,int c)
{
   edge[t].v = b;
   edge[t].w = c;
   edge[t].next = head[a];
   head[a] = t++;
}
void SPFA()
{
    queue<int>q;
    q.push(s);
    vis[s] = true;
    dis[s] = 0;
    while(!q.empty())
    {
        int p = q.front();
        q.pop();
        vis[p] = 0;
        if(head[p])
        {
            for(int pp = head[p];pp!=0;pp = edge[pp].next)
            {
                if(dis[p] + edge[pp].w < dis[edge[pp].v])
                {
                    dis[edge[pp].v] = dis[p] + edge[pp].w ;
                      if(!vis[edge[pp].v])
                   {
                    q.push(edge[pp].v);
                    vis[edge[pp].v] = true;
                  }
                }
             }
        }

    }
    if(dis[e]<INF)
        printf("%d\n",dis[e]);
    else
        printf("-1\n");
}


 邻接链表式

struct node{
    int u,v,w;
    node *next;
}*head[40000];
void init()
{
     memset(vis,0,sizeof(vis));
     memset(head,NULL,sizeof(head));
     for(int i = 0;i<n;i++)
     {
        dis[i] = INF;
     }
     t = 1;
}
int s,e;
void add(int a,int b,int c)
{
    node *p = new node;
    p->v = b;
    p->w = c;
    p->next = head[a];
    head[a] = p;
}
void SPFA()
{
    queue<int>q;
    q.push(s);
    vis[s] = true;
    dis[s] = 0;
    while(!q.empty())
    {
        int p = q.front();
        q.pop();
        vis[p] = 0;
        node *pp;
            for(pp = head[p];pp!=NULL;pp = pp->next)
            {
                if(dis[p] + pp->w < dis[pp->v])
                {
                    dis[pp->v] = dis[p] + pp->w;
                      if(!vis[pp->v])
                   {
                    q.push(pp->v);
                    vis[pp->v] = true;
                  }
                }
        }
    }
    if(dis[e]<INF)
        printf("%d\n",dis[e]);
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值