最短路的四种算法总结

标题 ##最短路的四种算法

1、floyd
核心代码只有五行
for(int k=1; k<=n; k++)
for(int i=1; i<=n; i++)
for(int j=1; j<=n; j++)
{
if(map1[i][j]>map1[i][k]+map1[k][j])
map1[i][j]=map1[i][k]+map1[k][j];
}
这种算法可以找多源最短路,想知道a点到b点最短路,只能加入中间点来缩短路径,比如a到b 加入中间点k a到k到b
那么我们可以这样判断,要知道i到j的最短路,我们只要判断e[i][j]是否大于e[i][1]+e[1][j]即可,而中间值1则要用for循环从1到n遍历一个遍,就是查找所有中间值
以下为完整代码

//floyd
#include
#include
#include
using namespace std;
#define inf 0x3f3f3f3f
int main()
{
    int n,m,a,b,c;
    int map1[100][100];
    scanf("%d%d",&n,&m);
    for(int i=1; i<=n; i++)
        for(int j=1;j<=n;j++)
        if(i==j) map1[i][j]=0;
        else map1[i][j]=inf;
    for(int i=1; i<=m; i++)
    {
        scanf("%d%d%d",&a,&b,&c);
        map1[a][b]=c;
    }
    for(int k=1; k<=n; k++)
        for(int i=1; i<=n; i++)
            for(int j=1; j<=n; j++)
            {
                if(map1[i][j]>map1[i][k]+map1[k][j])
                    map1[i][j]=map1[i][k]+map1[k][j];
            }
    for(int i=1; i<=n; i++)
    {
        for(int j=1;j<=n;j++)
        printf("%d ",map1[i][j]);
    printf("\n");
    }
    return 0;
}

2、dijkstra
这个算法只能计算单元最短路,而且不能计算负权值,这个算法是贪心的思想,
dis数组用来储存起始点到其他点的最短路,但开始时却是存的起始点到其他点的初始路程。通过n-1遍的遍历找最短。
比如1到3的最短路就是比较dis[3]与dis[2]+e[2][3],如果大于的话就更新dis[3]位dis[2]+e[2][3],这个专业术语叫松弛,这种算法的核心思想就是通过边来松弛一号顶点到其他定点的路程,这也就能解释为什么要遍历n-1遍了。
book数组用来标记,被标记的是已经找过的最短路,没被标记的没有被找过的最短路,当全部找过以后算法结束,也就是说dis数组中的数是起始点到其他所有点的最短路
以下为完整代码

#include
#include
#include
using namespace std;
#define inf 0x3f3f3f3f
int e[100][100],dis[100],book[100];//e用来记录数组,dis用来记录初始点到各个点的位置,book用来标记,被标记的表示已经连接成最短路
int main()
{
    int n,m,t1,t2,t3,u,v,min1;
    scanf("%d%d",&n,&m);
    for(int i=1; i<=n; i++)
        for(int j=1; j<=n; j++)
            if(i==j) e[i][j]=0;
            else e[i][j]=inf;
    for(int i=1; i<=m; i++)
    {
        scanf("%d%d%d",&t1,&t2,&t3);
        e[t1][t2]=t3;
    }
    for(int i=1; i<=n; i++)
        dis[i]=e[1][i];
    for(int i=1; i<=n; i++)
        book[i]=0;
    book[1]=1;
    for(int i=1; i<=n-1; i++) //更新dis数组
    {
        min1=inf;
        for(int j=1; j<=n; j++)
        {
            if(book[j]==0&&dis[j]1;
            for(int v=1; v<=n; v++)
            {
                if(e[u][v]if(dis[v]>dis[u]+e[u][v])//如果从起始点到j的距离大于起始点到u的距离加上u到j的距离就更新,专业术语松弛操作
                        dis[v]=dis[u]+e[u][v];
            }
    }
    for(int i=1; i<=n; i++)
        printf("%d ",dis[i]);
    return 0;
}
下边仍为dijkstra算法,不过存地图的方式从邻接矩阵换成了邻接表,邻接表的时间复杂度更低,而且可以存两点之间的多组数据,比如1点到2点之间可能存在2条路,每条路的权值不同
先介绍邻接表,这里给出啊哈磊的博客,简单易懂,链接
http://ahalei.blog.51cto.com/4767671/1391988
个人理解first数组用来存以u[i]为顶点的最后一条边的编号,next数组存编号为i的边的上一条边,如果这个边是以u[i]为顶点的第一条边,则next[i]为-1。
创立
for(i=1;i<=n;i++)
    first[i]=-1;
for(i=1;i<=m;i++)
{
    scanf("%d %d %d",&u[i],&v[i],&w[i]);
    next[i]=first[u[i]];//next等于编号i的上一条边
    first[u[i]]=i;
}
遍历
for(i=1;i<=n;i++)
{
    k=first[i];
    while(k!=-1)
    {
        printf("%d %d %d\n",u[k],v[k],w[k]);
        k=next[k];
    }
}

这里给到题poj1724,dijs的变形 用了优先队列和邻接表
链接:
http://poj.org/problem?id=1724
直接上代码

#include
#include
#include
#include
using namespace std;
#define inf 0x3f3f3f3f
const int maxn=10005;
int num,first[maxn];
struct node
{
    int id;
    int len;
    int val;
}node1;
struct edge
{
    int id;
    int len;
    int val;
    int next;//作用跟next数组一样
}e[maxn];
priority_queueq;
bool operator <(node a,node b)
{
    return a.len>b.len;
}
void add(int u,int v,int len,int val)
{
    e[num].id=v;//存下一个点,以边找点
    e[num].val=val;
    e[num].len=len;
    e[num].next=first[u];
    first[u]=num;
    num++;
}
int main()
{
    node cur;
    int ans=inf;
    int k,n,r,s,d,l,t;
    scanf("%d%d%d",&k,&n,&r);
    memset(first,-1,sizeof(first));
    while(!q.empty()) q.pop();
    num=0;
    for(int i=0;iscanf("%d%d%d%d",&s,&d,&l,&t);
        add(s,d,l,t);
    }
    node1.id=1;//起始点
    node1.len=0;
    node1.val=0;
    q.push(node1);
    while(!q.empty())
    {
        cur=q.top();
        q.pop();
        if(cur.id==n)
        {
            ans=cur.len;
            break;
        }
        for(int i=first[cur.id];i!=-1;i=e[i].next)
        {
            if(k>=cur.val+e[i].val)//只有符合要求的点才会进入队列
            {
                node1.id=e[i].id;
                node1.len=e[i].len+cur.len;
                node1.val=e[i].val+cur.val;
                q.push(node1);
            }
        }
    }
    if(ans==inf)
        printf("-1\n");
    else
    printf("%d\n",ans);
}

4、bellman(可以计算负权值)
那么先介绍一下负权回路,如果是单向图,那么所有权值之和为负数,这是负权回路。如果是无向图只要有一个负权值,就不会存在最短路,跟负权回路一个意思。
这个算法一般用于有负权值的最短路,但时间复杂度也不高。
有负权值的最短路
有了负权值dijs这种算法就不能用了,为什么呢?
因为这种算法是贪心的思想,每次松弛的的前提是用来松弛这条边的最短路肯定是最短的。然而有负权值的时候这个前提不能得到保证,所以dijs这种算法不能成立。
这里给出一个博客,看这个很清晰。
链接:http://blog.csdn.net/baidu_31818237/article/details/50611592
bellman的核心代码只有4行
for(int k=1;k<=n-1;k++)//进行n-1次松弛
for(int i=1;i<=m;i++)//枚举每一条边
if(dis[v[i]]>dis[u[i]]+w[i])//尝试松弛每一条边
dis[v[i]]=dis[u[i]]+w[i];
这个算法也是遍历n-1遍找过所有的点,至于为什么是n-1呢。dijs算法n-1次遍历是因为有n-1个点需要遍历,这个也是因为最短路是一个不包含回路的路径,无论正负权回路都不能有,那么去掉回路,n个点任意两点之间就最多有n-1条边。但是程序可能在不到n-1次循环就已经找到了所有最短路,说明这个是最坏情况下是n-1次遍历。
dis同样是存在起始点到各个顶点的最短路,这个与dijs不同的是,dijs每次找到最近的点进行松弛操作,而这个bellman则是只要路程更短我就松弛。也是因为这样才能用来解决负权值问题。
那么怎么来看有负权值回路呢,如果有负权值回路,那最短路就不会存在,因为最短路会越来与小。那么在n-1轮松弛后,要是还能松弛就代表有负权值回路。
下边是完整代码

#include
#include
#include
using namespace std;
#define inf 0x3f3f3f3f
int dis[100],u[100],v[100],w[100];
int main()
{
    int n,m,flag1,flag2;
    while(~scanf("%d%d",&n,&m))
    {
        for(int i=1; i<=m; i++)
            scanf("%d%d%d",&u[i],&v[i],&w[i]);
        for(int i=1; i<=n; i++)
            dis[i]=inf;
        dis[1]=0;
        for(int k=1; k<=n-1; k++)
        {
            flag1=0;//用于标记本轮松弛是否发生
            for(int i=1; i<=m; i++)
            {
                if(dis[v[i]]>dis[u[i]]+w[i])
                    dis[v[i]]=dis[u[i]]+w[i];
                flag1=1;//有更新
            }
            if(flag1==0)
                break;//dis数组没有更新,即没有松弛,结束算法
        }
        flag2=0;
        for(int i=1; i<=m; i++)
            if(dis[v[i]]>dis[u[i]]+w[i])
                flag2=1;
        if(flag2==1)
            printf("有负权回路\n");
        else
        {
            for(int i=1; i<=n; i++)
                printf("%d ",dis[i]);
        }
        printf("\n");
    }
    return 0;
}

其实这个算法可以在优化,因为有可能在n-1轮松弛还没结束就已经是最短路,那么用队列进行优化。
4、spfa(队列优化的bellman)
我没找到两者有什么区别,本来spfa就是队列优化而来的bellman, 我用了邻接表,反正是要优化就优化到底,下边还会给出优先队列的spfa
代码
数组模拟邻接表

#include
#include
#include
#include
using namespace std;
int dis[100],u[100],v[100],w[100],vis[100],first[100],next[100],c[100];
int flag;
int spfa(int s,int n)
{
    queue<int>q;
    memset(dis,0x3f,sizeof(dis));
    dis[s]=0;
    memset(vis,0,sizeof(vis));
    memset(c,0,sizeof(c));
    q.push(s);
    vis[s]=1;
    flag=0;
    while(!q.empty())
    {
        int x;
        x=q.front();
        q.pop();
        vis[x]=0;
        //队头元素出队,并且消除标记
        for(int k=first[x]; k!=0; k=next[k]) //遍历顶点x的邻接表
        {
            int y=v[k];
            if(dis[x]+w[k]//松弛
                if(!vis[y])  //顶点y不在队内
                {
                    vis[y]=1;    //标记
                    c[y]++;      //统计次数
                    q.push(y);   //入队
                    if(c[y]>n)  //超过入队次数上限,说明有负环
                        return flag=0;
                }
            }
        }

    }
}
int main()
{
    int n,m;
    while(~scanf("%d%d",&n,&m))
    {
        memset(first,-1,sizeof(first));
        for(int i=1;i<=m;i++)
        {
            scanf("%d%d%d",&u[i],&v[i],&w[i]);
            next[i]=first[u[i]];
            first[u[i]]=i;
        }
        spfa(1,n);
        if(flag)
            printf("有负权回路\n");
        else
        {
            for(int i=1;i<=n;i++)
            printf("%d ",dis[i]);
        }
    }
}

这个是vector模拟邻接表

#include
#include
#include
#include
#include
#include
#include
#include
#define inf 0x3f3f3f3f
#define ll long long
#define N 100000+10
using namespace std;
int n,m;
int x,y,z;
bool v[100];
int d[100];
struct node
{
    int y,z;
};
vectore[100];
void spfa(int x)
{
    memset(v,0,sizeof(v));
    d[x]=0;
    queue<int>q;
    q.push(x);
    v[x]=1;
    while(!q.empty())
    {
        int st=q.front();
        q.pop();
        v[st]=0;
        for(int i=0;iif(d[st]+e[st][i].zif(!v[e[st][i].y])
                {
                    q.push(e[st][i].y);
                    v[e[st][i].y]=1;
                }
            }
        }
    }
}
int main()
{
    scanf("%d%d",&n,&m);
   memset(d,inf,sizeof(d));
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d%d",&x,&y,&z);
       e[x].push_back((node){y,z});
       e[y].push_back((node){x,z});
    //存入无向图
    }
    spfa(1);
    for(int i=1;i<=n;i++)
    {
        printf("%d ",d[i]);
    }
}

最后着重说一下邻接表可以用数组也可以用vector
实现
e[]这个数组,e[i][j]i为起点,j为终止点的标号,e[i][j].y就是终止点
例如
j:0 1 2 j只是标号第几个
i:1 (2,3 3,2 4,4) 括号内的东西就是终止点和权值,e[1][0].y=2(终止点),e[1][0].z=3(权值)
i:2 (1,2 3,5 4,6)
i:3 (1,2 2,4 4,5)
这样也可以实现链表,并且可以存无向图,数组实现的邻接表存无向图很麻烦

你可能感兴趣的:(最短路)