最短路径问题(Dijkstra常用用法总结)

最短路径问题(单源+多源)

Dijkstra简介:

迪杰斯特拉算法(Dijkstra)是从一个顶点到其余各顶点的最短路径算法,解决的是有权图中最短路径问题。迪杰斯特拉算法主要特点是从起始点开始,采用贪心算法的策略,每次遍历到始点距离最近且未访问过的顶点的邻接节点,直到扩展到终点为止
注意该算法要求图中不存在负权边。

核心思想:

找离自己相邻的最近的点,更新距离,重复操作。

其实这和人的思维方式有些相似。

模拟过程更利于理解
在这里插入图片描述

在理解Dijkstra的基础上,我们可以用
Dijkstra可解决以下几种最短路问题:

  1. 单源最短路问题(一个起点到一个终点 )
  2. 延伸到—>任意起点到终点
  3. 多源最短路问题( 多起点多终点

单源问题
例如:
Dijkstra求最短路I–>简单入门的模板

给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环,所有边权均为正值。
请你求出 1 号点到 n 号点的最短距离,如果无法从 1 号点走到 n 号点,则输出 −1。

输入格式

第一行包含整数 n 和 m。
接下来 m 行每行包含三个整数 x,y,z,表示存在一条从点 x 到点 y 的有向边,边长为 z。

输出格式

输出一个整数,表示 1 号点到 n 号点的最短距离。

如果路径不存在,则输出 −1。

数据范围
1≤n≤500,
1≤m≤105,
图中涉及边长均不超过10000。

输入样例:

3 3
1 2 2
2 3 1
1 3 4

输出样例:

3

模板,直接记。

#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 510;

int n, m;
int g[N][N];
int dist[N];
bool st[N];

int dijkstra()
{
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;

    for (int i = 0; i < n - 1; i ++ )
    {
        int t = -1;
        for (int j = 1; j <= n; j ++ )
            if (!st[j] && (t == -1 || dist[t] > dist[j]))
                t = j;

        for (int j = 1; j <= n; j ++ )
            dist[j] = min(dist[j], dist[t] + g[t][j]);

        st[t] = true;
    }

    if (dist[n] == 0x3f3f3f3f) return -1;
    return dist[n];
}

int main()
{
    scanf("%d%d", &n, &m);

    memset(g, 0x3f, sizeof g);
    while (m -- )
    {
        int a, b, c;
        scanf("%d%d%d", &a, &b, &c);

        g[a][b] = min(g[a][b], c);
    }

    printf("%d\n", dijkstra());

    return 0;
}

自问自答:
int t = -1;
t 是用来干嘛的?
t 是用来找临近的最近的点的。

这是朴素版的Dijkstra的算法,适用于稠密图。

那稀疏图怎么办?
堆优化版的Dijkstra!

Dijkstra求最短路 II–>优化版

给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环,所有边权均为非负值。 请你求出 1 号点到 n 号点的最短距离,如果无法从 1
号点走到 n 号点,则输出 −1。

输入格式

第一行包含整数 n 和 m。 接下来 m 行每行包含三个整数 x,y,z,表示存在一条从点 x 到点 y 的有向边,边长为 z。

输出格式

输出一个整数,表示 1 号点到 n 号点的最短距离。 如果路径不存在,则输出 −1。

数据范围
1≤n,m≤1.5×105,
图中涉及边长均不小于 0,且不超过 10000。

输入样例:

3 3
1 2 2
2 3 1
1 3 4

输出样例:

3

#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
const int N=1e6+10;
typedef pair<int,int> PII;
int n,m;
int e[N],ne[N],h[N],idx;
int w[N],dist[N];
bool st[N];
void add(int a,int b,int c)
{
    w[idx]=c;e[idx]=b;ne[idx]=h[a];h[a]=idx++;
}

int Dijkstra()
{
    memset(dist,0x3f,sizeof dist);
    
    dist[1]=0;
    priority_queue<PII,vector<PII>,greater<PII>> heap;//小根堆
    
    heap.push({0,1}); //第一个元素存距离,第二个元素存点
    
    while(heap.size())
    {
        auto t=heap.top();
        heap.pop();
        
        int ver=t.second;
        if(st[ver]) continue;
            st[ver]=true;
        
        for(int i=h[ver];i!=-1;i=ne[i])
        {
            int j=e[i];

            if(dist[j] > dist[ver] + w[i])
            {
                dist[j] =dist[ver] + w[i];
                heap.push({dist[j],j});
            }
        }
    }
    if(dist[n]==0x3f3f3f3f) return -1;
    return dist[n];
}
int main()
{
    memset(h,-1,sizeof h);
    cin>>n>>m;
    while(m--)
    {
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        add(a,b,c);
    }
    cout<<Dijkstra();
    return 0;
}

搜索一次使用好像不太够,能不能多次搜索?
上例题!
畅通工程续–> 开工!

某省自从实行了很多年的畅通工程计划后,终于修建了很多路。不过路多了也不好,每次要从一个城镇到另一个城镇时,都有许多种道路方案可以选择,而某些方案要比另一些方案行走的距离要短很多。这让行人很困扰。
现在,已知起点和终点,请你计算出要从起点到终点,最短需要行走多少距离。

Input

本题目包含多组数据,请处理到文件结束。
每组数据第一行包含两个正整数N和M(0<N<200,0<M<1000),分别代表现有城镇的数目和已修建的道路的数目。城镇分别以0~N-1编号。
接下来是M行道路信息。每一行有三个整数A,B,X(0<=A,B<N,A!=B,0<X<10000),表示城镇A和城镇B之间有一条长度为X的双向道路。
再接下一行有两个整数S,T(0<=S,T<N),分别代表起点和终点。

Output

对于每组数据,请在一行里输出最短需要行走的距离。如果不存在从S到T的路线,就输出-1.

Sample Input

3 3
0 1 1
0 2 3
1 2 1
0 2
3 1
0 1 1
1 2

Sample Output

2
-1

代码如下:

#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 510;

int n, m;
int x, y;
int g[N][N];
int dist[N];
bool st[N];

void dijkstra()
{
    memset(dist, 0x3f, sizeof dist);
    memset(st, false, sizeof st);  //搜索前初始化,就可多次搜索
    dist[x] = 0;

    for (int i = 0; i <= n-1; i ++ )
    {
        int t = -1;
        for (int j = 0; j <= n-1; j ++ )
            if (!st[j] && (t == -1 || dist[t] > dist[j]))
                t = j;

        for (int j = 0; j <= n-1; j ++ )
            dist[j] = min(dist[j], dist[t] + g[t][j]);

        st[t] = true;
    }

    if (dist[y] == 0x3f3f3f3f)  dist[y] =-1;
}

int main()
{
    while( scanf("%d%d",&n,&m) ! = EOF )
    {
        memset(g, 0x3f, sizeof g);
        while (m -- )
        {
            int a, b, c;
            scanf("%d%d%d", &a, &b, &c);

            g[a][b] = g[b][a] = min(g[a][b], c);
        }
        
        cin>> x >> y;
        
        dijkstra();
        
        printf("%d\n", dist[y]);
    }

    return 0;
}

能不能多次搜索关键在于处理st[ ](初始化为 false)。因为st[ ]用来判断相邻的点是否搜过,标记当前的状态,所以一次搜索过后想要再次搜索,只要把st[ ]初始化即可。

单源问题已解决,那多源问题怎么解决?
多源问题( 多起点多终点

初学者遇到多源问题可能会很茫然,认为会很复杂,其实很简单,看下面的例题就懂了。

一个人的旅行 -->来一场说走就走的旅行

虽然草儿是个路痴(就是在杭电待了一年多,居然还会在校园里迷路的人,汗~),但是草儿仍然很喜欢旅行,因为在旅途中
会遇见很多人(白马王子,0),很多事,还能丰富自己的阅历,还可以看美丽的风景……草儿想去很多地方,她想要去东京铁塔看夜景,去威尼斯看电影,去阳明山上看海芋,去纽约纯粹看雪景,去巴黎喝咖啡写信,去北京探望孟姜女……眼看寒假就快到了,这么一大段时间,可不能浪费啊,一定要给自己好好的放个假,可是也不能荒废了训练啊,所以草儿决定在要在最短的时间去一个自己想去的地方!因为草儿的家在一个小镇上,没有火车经过,所以她只能去邻近的城市坐火车(好可怜啊~)。

Input

输入数据有多组,每组的第一行是三个整数T,S和D,表示有T条路,和草儿家相邻的城市的有S个,草儿想去的地方有D个;
接着有T行,每行有三个整数a,b,time,表示a,b城市之间的车程是time小时;(1=<(a,b)<=1000;a,b
之间可能有多条路) 接着的第T+1行有S个数,表示和草儿家相连的城市; 接着的第T+2行有D个数,表示草儿想去地方。

Output

输出草儿能去某个喜欢的城市的最短时间。

Sample Input

6 2 3
1 3 5
1 4 7
2 8 12
3 8 4
4 9 12
9 10 2
1 2
8 9 10

Sample Output

9

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1200;
const int MAX = 0x3f3f3f3f;
int t, s, d;
int g[N][N], dist[N], f[N], w[N];
bool st[N];
int dijkstra(int x)
{
    memset(st, false, sizeof st);
    memset(dist,0x3f,sizeof dist); 
    dist[x] = 0;
    
    for(int i = 0; i < N; i++)
    {
        int t=-1;
        for(int j = 0; j < N; j++)
            if(!st[j] && (t==-1 || dist[t] > dist[j] ))
                t = j;
        
        st[t] = true;
        for(int j = 0; j < N; j++)
            dist[j] = min( dist[j] , g[t][j] + dist[t] );
    }
    int res = MAX;
    for(int i = 0; i < d; i++)
        res = min( res, dist[w[i]] );  //找当前起点到所有终点的最短距离
        
    return res;
}
int main()
{
    while(scanf("%d%d%d", &t, &s, &d) != EOF)
    {
        memset(g,0x3f,sizeof g);
        int x, y, time;
        
        for(int i = 0; i < t; i++)
        {
            scanf("%d%d%d", &x, &y, &time);
            g[x][y] = g[y][x] = min(g[x][y],time);
        }
        
        for(int i = 0; i < s; i++)
            scanf("%d", &f[i]);
            
        for(int i = 0; i < d; i++)
            scanf("%d", &w[i]);
            
        int rmin = MAX;
        
        for(int i = 0; i < s; i++)
            rmin = min(rmin, dijkstra(f[i]));
            //输入一个起点就得到所有终点的最短距离,把所有起点输入,就得到离终点的最短距离
        printf("%d\n", rmin);
    }
    return 0;
}

看完后是不是发现只加了几行代码,就有不一样的效果!
Dijkstra搜索一次顺便求出一个起点到多终点的最短距离,多次搜索便可得出最短距离。

个人认为Dijkstra多源最短路的解决方法是:将多起点到多终点拆分成两步。

  1. 求单个起点到所有终点的最短距离;
  2. 遍历所有起点,记录最小的距离;

用Dijkstra大多数情况下用于解决单源最短路,少数情况下解决多源最短路。

不足之处,请指正。
欢迎点赞与评论~
记得收藏

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值