单源最短路径问题-迪杰斯特拉算法(Dijsktra)

一、问题描述:

给定一个带权有向图 G=(V,E) ,其中每条边的权是一个非负实数。再给定 V 中的一个顶点,称为源,计算源到所有顶点的最短距离。

实例:单源最短路径(弱化版)https://www.luogu.org/problemnew/show/P3371

二、 求取过程:

1. 从起点开始,查找所有与起点直接相连的点,并把连接它们的边权值作为起点到它们的距离(有平行边取最小值)。起点到起点的距离为0,其它不与起点直接相连的点距离为无穷大,存入上图所示的表中。

2. 遍历所有点,查找得到的结果中距离最小的那个点,然后再看所有与该点直接相连的点,计算该点到它们的最短距离(即从起点开始,经由该点到达的点的距离),根据结果更新上图。

3. 不断重复2中过程,直到每一个点都像2中那样计算过一遍。

觉得抽象的话可以看视频:https://www.bilibili.com/video/av2156069

三、合理性:

假设A到C经过B,如果最短路径经过B,那么此时A到B的路径必定最短。

对于第一次操作,我们已经确定了从起点直接到所有点的最短距离。

此时取距离的最小值,有两种情况,一种是最小值来自跳过中间点的直接路径,那么我们可知从起点到该点的最短距离一定来自该路径,因为所有中间点的距离已经比其大,标记该点,已经使用过,接下来该点连接的点也可以根据该点的距离来处理了,因为此时A到B的距离已经最短,且中间点也不会对再对该点产生影响。另一种是最小值来自某一中间路径,那么经过该点的操作,完全有可能使第一次操作得到的距离改变,这就是取最小值的意义。若在未确定某一点最短距离的情况下对该点进行处理,由于该点已经标记使用,那么后续即使该点最短距离改变了,也无法改变该点已经确定的点的情况了。

如图所示的情况,第一次操作后我们知道直接与1相连的点有2, 3, 4。

如果此时不取最小值,例如对4进行处理,那么5的距离直接被确定为10000,此时4被标记使用,后续我们即使通过2,3改变了4的最短距离,5的距离也无法进行改变了。

 四、算法实现:

1. 单源最短路径(弱化版)https://www.luogu.org/problemnew/show/P3371

首先要知道怎么建图:

#include<bits/stdc++.h>

using namespace std;

const int N=1e6+5;
const int MAX=2147483647;

struct node{        //建图(链式向前星,二维矩阵会炸),储存边
    int from;
    int to;
    int value;
    int next;
}edge[200005];
int head[N];        

int dis[N];        //记录每一个顶点的距离
int visited[N];    //记录是否访问过某一个顶点

int main()
{
    int n,m,s;
    int cnt=0;
    scanf("%d%d%d",&n,&m,&s);

    for(int i=1;i<=n;i++) dis[i]=MAX;    //距离初始化为无穷大

    memset(head,0,sizeof(head));         //这两行其实可以不要
    memset(visited,0,sizeof(visited));

    for(int i=1;i<=m;i++)    //建图,储存边
    {
        cnt++;
        scanf("%d%d%d",&edge[cnt].from,&edge[cnt].to,&edge[cnt].value);
        int from=edge[cnt].from;
        int to=edge[cnt].to;
        edge[cnt].next=head[from];
        head[from]=cnt;
    }

    int start=s;            //以s为起点
    dis[start]=0;           //起点到自己的距离为0


    while(visited[start]!=1)    //开始模拟查找过程
    {
        visited[start]=1;    //标记该起点,表示已访问过

        for(int i=head[start];i!=0;i=edge[i].next)    //读取与起点相连的边
        {
            if(dis[edge[i].to]>dis[start]+edge[i].value)    //算法的核心
                dis[edge[i].to]=dis[start]+edge[i].value;
        }

        int minn=MAX;            //每一次都寻找所有点中距离最小的点
        for(int i=1;i<=n;i++)
        {
            if(visited[i]!=1 && minn>dis[i])
            {
                minn=dis[i];
                start=i;        //以距离最小的点为新的起点,开始查找
            }
        }

    }
    for(int i=1;i<=n;i++)
    {
        if(i==n) printf("%d\n",dis[i]);
        else printf("%d ",dis[i]);
    }
}

2. 单源最短路径(标准版):https://www.luogu.org/problemnew/show/P4779

这题数据要比上一题大,所以要进行优化。

上一题的写法每一次都要查找距离最短的点,占用了大量时间,我们用优先队列优化这个过程。

有关优先队列可参考:https://blog.csdn.net/qq_19656301/article/details/82490601

#include <bits/stdc++.h>

using namespace std;

const int N=1e5+5;
const int MAX=2147483647;
typedef long long LL;

struct node{        //建图部分还是一样的
    int from;
    int to;
    int value;
    int next;
}edge[2*N];
int head[N];

LL dis[N];
int visited[N];

priority_queue<pair<int,int>,vector<pair<int,int> >,greater<pair<int,int> > >vis;    //优先队列

int main()
{
    int n,m,s;
    int cnt=0;
    cin>>n>>m>>s;
    for(int i=1;i<=n;i++) dis[i]=MAX;
    for(int i=1;i<=m;i++)
    {
        cnt++;
        scanf("%d%d%d",&edge[cnt].from,&edge[cnt].to,&edge[cnt].value);
        edge[cnt].next=head[edge[cnt].from];
        head[edge[cnt].from]=cnt;
    }


    dis[s]=0;
    vis.push(make_pair(0,s));    //储存某点距离和该点
                                 //make_pair是c++自带的二元组定义方式,可以通过first、second访问第一个,第二个元素(小根堆以第一个值排序)

    while(!vis.empty())        //判断所有点是否都查找过一遍
    {
        int start=vis.top().second;    //优先队列队首即最小的元素
        vis.pop();

        if(visited[start]==1) continue;    //如果该点查找过,跳过
        visited[start]=1;

        for(int i=head[start];i!=0;i=edge[i].next)
        {
            if(dis[edge[i].to]>dis[start]+edge[i].value)
            {
                dis[edge[i].to]=dis[start]+edge[i].value;
                vis.push(make_pair(dis[edge[i].to],edge[i].to));     
            }
        }
    }
    for(int i=1;i<=n;i++)
    {
        if(i==n) printf("%d\n",dis[i]);
        else printf("%d ",dis[i]);
    }
}
发布了34 篇原创文章 · 获赞 26 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/sinat_40471574/article/details/92411004