最短路问题的三种基本算法(模板)

了解了优先队列,本来想写一道题目练练手,结果就看到了8441,看着像是bfs求最短路,然而T了,并不知道怎么优化,然后又去找老师要了标程,结果神仙代码看不懂(主要是因为太菜..),看到里面用了dijstra,就干脆先从最短路问题入手。

最短路问题,一般有三种方法,dijstra,bellman-forward,floyed,三者个有特色,适合于不同的场合。

一。dijstra(迪杰斯特拉)

 

Dijkstra算法

1.定义概览

Dijkstra(迪杰斯特拉)算法是典型的单源最短路径算法,用于计算一个节点到其他所有节点的最短路径。主要特点是以起始点为中心向外层层扩展,直到扩展到终点为止。该算法无法处理负权边。

问题描述:在无向图 G=(V,E) 中,假设每条边 E[i] 的长度为 w[i],找到由顶点 V0 到其余各点的最短路径。(单源最短路径)

2.算法描述

1)算法思想:设G=(V,E)是一个带权有向图,把图中顶点集合V分成两组,第一组为已求出最短路径的顶点集合(用S表示,初始时S中只有一个源点,以后每求得一条最短路径 , 就将加入到集合S中,直到全部顶点都加入到S中,算法就结束了),第二组为其余未确定最短路径的顶点集合(用U表示),按最短路径长度的递增次序依次把第二组的顶点加入S中。在加入的过程中,总保持从源点v到S中各顶点的最短路径长度不大于从源点v到U中任何顶点的最短路径长度。此外,每个顶点对应一个距离,S中的顶点的距离就是从v到此顶点的最短路径长度,U中的顶点的距离,是从v到此顶点只包括S中的顶点为中间顶点的当前最短路径长度。

2)算法步骤:

a.初始时,S只包含源点,即S={v},v的距离为0。U包含除v外的其他顶点,即:U={其余顶点},若v与U中顶点u有边,则<u,v>正常有权值,若u不是v的出边邻接点,则<u,v>权值为∞。

b.从U中选取一个距离v最小的顶点k,把k,加入S中(该选定的距离就是v到k的最短路径长度)。

c.以k为新考虑的中间点,修改U中各顶点的距离;若从源点v到顶点u的距离(经过顶点k)比原来距离(不经过顶点k)短,则修改顶点u的距离值,修改后的距离值的顶点k的距离加上边上的权。

d.重复步骤b和c直到所有顶点都包含在S中。

3.执行动画过程如下图

4.例题:http://icpc.upc.edu.cn/problem.php?id=2716

算法实现:

#include <iostream>
#include <bits/stdc++.h>
// dijstra n^2 TLE
using namespace std;
const int maxn=5e6+10;
const int inf=1e9+7;
struct E
{
    int v,w;
};
vector <E> edge[maxn];
int in[maxn],dis[maxn];//in 数组表示在集合S内,dis表示到个点的最短距离
int dijstra(int s,int e,int n)//s 出发点 e 终止点 n 点数
{
    for (int i=0; i<=n; i++)
        dis[i]=inf;
    dis[s]=0,in[s]=1;
    for (int i=0; i<edge[s].size();i++)
    {
        dis[edge[s][i].v]=edge[s][i].w;
        //printf("to%d=%d\n",edge[s][i].v,dis[edge[s][i].v]);
    }
    //初始化
    for (int i=0; i<=n; i++)
    {
        int mi=inf,k=s;//找到s点最短距离的点
        for (int j=1; j<=n; j++)
        {
            if (!in[j] && dis[j]<mi)
            {
                mi=dis[j];
                k=j;
            }
        }
        in[k]=1;//将最短的新点加入集合S
        int num=edge[k].size();//用新点k去扩展新点
        for (int j=0; j<num; j++)
        {
            int v=edge[k][j].v,w=edge[k][j].w;
            if (!in[v])
            {
                if (dis[k]+w<dis[v]) //relax
                {
                    dis[v]=dis[k]+w;
                }
            }
        }
    }
    return dis[e];
}
int main()
{
    int n,m,t;
    scanf("%d%d%d",&n,&m,&t);
    for (int i=1; i<=m; i++)
    {
        int u,v,w;
        scanf("%d%d%d",&u,&v,&w);
        edge[u].push_back({v,w});
        edge[v].push_back({u,w});
    }
    int ans=dijstra(1,t,n);
    printf("%d\n",ans);
    return 0;
}

其实dj算法就是BFS+贪心,它每次选一个点,然后扩散(bfs)到它的邻点之后,再从所有点中,选出离起点最近的点,继续扩散出去。这样总共n-1次之后,图上所有点离起点的距离必然是最小的。时间复杂度为n^2,n>1000一般稳稳地TLE。

6.优化:

  考虑到每次都是用最近的那一个结点更新,暴力跑需要n的时间,太慢了。

  怎么样能gkd呢?我们自然可以想到优先队列,因为每次要找的点有鲜明的特征,是距离s最近的点。这样优化后,n变成了logn,所以总的复杂度变为nlogn,瞬间快乐。

代码实现:

#include <iostream>
#include <bits/stdc++.h>
using namespace std;
const int maxn=5e6+10;
const int inf=INT_MAX/2;
/*struct E
{
    int v,w;
    bool operator< (const E& b) const
    {
        return w > b.w;
    }
};*/
struct E
{
    int v,w;
    friend bool operator< (E x,E y)
    {
        return x.w>y.w;
    }
    //重载<运算符,使得距离小的优先级大
};
/*struct cmp
{
    bool operator() (const E &x,const E &y) const
    {
        return x.w>y.w;
    }
};*/
int vis[maxn],dis[maxn];
vector <E> edge[maxn];
int dijheap(int s,int e,int n)
{
    priority_queue <E> Q;
    for (int i=0; i<=n; i++)
        dis[i]=inf;
    Q.push({s,0});
    dis[s]=0;
    while(!Q.empty())
    {
        E cur=Q.top();//保证取出的队首元素就是距离s最近的
        Q.pop();
        int cv=cur.v;
        if (vis[cv]) continue;
        vis[cv]=1;
        int num=edge[cv].size();
        for (int i=0;i<num;i++)//用这个点去扩展relax
        {
            int v=edge[cv][i].v,w=edge[cv][i].w;
            if (!vis[v])
            {
                if (dis[v]>dis[cv]+w)
                {
                    dis[v]=dis[cv]+w;
                    Q.push({v,dis[v]});
                }
            }
        }
    }
    return dis[e];
}

int main()
{
    int n,m,t;
    scanf("%d%d%d",&n,&m,&t);
    for (int i=1; i<=m; i++)
    {
        int u,v,w;
        scanf("%d%d%d",&u,&v,&w);
        edge[u].push_back({v,w});
        edge[v].push_back({u,w});
    }
    int ans=dijheap(1,t,n);
    printf("%d\n",ans);
    return 0;
}

7.小结

Dijstra算法十分优秀,在使用堆(优先队列)优化的情况下,时间复杂度为nlogn,如果题目中不是单源的最短路,那么可以每个点都作为起点跑一下dj算法,n^2logn。

缺点:

dj算法是无法处理负权边的!为什么呢,因为dj算法是贪心BFS,而BFS有一个特点,就是短视! 它只能看到与自己相邻的点的情况,但是对于远方,它就一脸蒙蔽了。如果有两种走法,一种是直接走边长5到达,一种是先走10,再走-20到达,显然,我们的dijstra算法会直接走第一种。

8.扩展

其实,dijstra算法,还可以输出最短路的路径,只需要用一个pre数组记录一下每个节点的前驱结点,递归输出就可以了。

代码实现:

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
const int maxn=1e3+20;
const int inf=1e9+7;
int dis[maxn],vis[maxn],pre[maxn];
struct E
{
    int v,w;
    bool friend operator< (E x,E y)
    {
        return x.w>y.w;
    }
};
vector <E> edge[maxn];
void dij(int s,int n)
{
    priority_queue <E> Q;
    while(Q.size()) Q.pop();
    for (int i=1; i<=n; i++)
        dis[i]=inf;
    dis[s]=0;pre[s]=s;
    Q.push({s,0});
    while(!Q.empty())
    {
        E cur=Q.top();
        Q.pop();
        int cv=cur.v;
        int num=edge[cv].size();
        if (vis[cv]) continue;
        vis[cv]=1;
        for (int i=0; i<num; i++)
        {
            int v=edge[cv][i].v,w=edge[cv][i].w;
            if (dis[v]>dis[cv]+w)
            {
                dis[v]=dis[cv]+w;
                Q.push({v,dis[v]});
                pre[v]=cv;
            }
        }
    }
}
void outway(int i)
{
    if (pre[i]!=i)
    {
        printf("%d-->",i);
        outway(pre[i]);
    }
    else printf("1\n");
    return ;
}
int main()
{
    int n,m;
    freopen("out2.txt","w",stdout);
    while(~scanf("%d%d",&n,&m))
    {
        if (n==0&&m==0) break;
        memset(vis,0,sizeof(vis));
        memset(edge,0,sizeof(edge));
        memset(pre,0,sizeof(pre));
        for (int i=1; i<=m; i++)
        {
            int u,v,w,flag=0;
            scanf("%d%d%d",&u,&v,&w);
            edge[u].push_back({v,w});
        }
        dij(1,n);
        for (int i=2; i<=n; i++)
            i==n ? printf("%d\n",dis[i]) :printf("%d ",dis[i]);
        for (int i=2; i<=n; i++)
            outway(i);
    }
    return 0;
}

需要正序输出的话,其实也可以,记录 

猜你喜欢

转载自www.cnblogs.com/ztdf123/p/10891720.html