最短路径笔记,记录一下,防止忘记!(2.25 更新,补充更全,绝对能看懂!)


一、最短路是什么?

最短路算法其实有很多种:

1.Dijkstra(迪杰斯特拉) 算法

2.多源最短路

鄙人不才,今天先收录使用临接矩阵和队列实现Dij算法,其他的算法以后再做补充······


2021.2.25 更新笔记(更新具体最短路的注释和图解)

今天有好多同学反应说是最短路代码单看看不懂,确实是这样,当初我学习的时候也看了一晚上一头雾水,今天添加一些便于理解的注释,希望能帮助到大家少走弯路


二、算法介绍

(狄克斯特拉1930年5月11日生于荷兰鹿特丹的一个知识分子家庭,在兄弟姊妹4人中排行第三。他的父亲是一名化学家和发明家,曾担任荷兰化学会主席。他母亲则是一位数学家。他成功地设计并实现了在有障碍物的两个地点之间找出一条最短路径的高效算法,这个算法被命名为“狄克斯特拉算法”,解决了机器人学中的一个十分关键的问题,即运动路径规划问题,至今仍被广泛应用,被认为是利用“贪心法”(greedy method)设计算法的一个成功范例。)在这里插入图片描述
完整算法的图:
在这里插入图片描述
我们这里定义图的编号为:

1 2 3

4 5 6

7 8 9

图1:初始化的图,其中包含边的权值(耗时)。(这里图是有向图)。

图2:确定起点,然后向能直接走到的点走一下,记录此时的估计值:2 6 9.。

图3:找到距离起点最近的点,是正东边的那个点,这时候我们耗费权值为2。然后我们进行松弛操作,从起点到其东南方的点直接到的权值耗费为6,但是我们通过刚刚选定的点,我们找到了到这个点更近的方式,所以这个时候我们说从起点到其东南方向的点的权值更新值从6变成了5。这个时候我们就完成了第一次松弛操作。

图4:依旧是找距离起点最近的点。然后松弛我们发现这个时候从起点到其东南方的点的耗费权值从5又变成了4.这个时候我们完成了第二个松弛。

之后的方式同上:选定距离起点最近的点v。然后通过点v进行松弛操作。我们发现能够通过增加走到目的地方式的复杂度(多转弯)的方式我们能够松弛掉权值,使得耗费的权值更小。

三、算法演示

在这里插入图片描述
首先第一步,我们先要把所有的数据读到程序里,那么用什么办法来存储路径数据呢?
聪明的你肯定想到了(maybe?),我们可以用临阶矩阵实现,也就是说,建立一个二维的数组。
存数据的时候,比如说这道题,给你的数据会是:

6 8
1 6 100
1 5 30 
4 6 10
1 3 10
2 3 5
3 4 50
5 4 20
5 6 60

这里的6表示6个结点,8表示会有8组数据
下面的8组数据前两个是节点,第三个是权值
我们把前两个数据作为map(二维数据)的横纵坐标,第三个数据作为存的值
(这里有有向图和无向图的区别,如果是无向图我们需要双向导通,具体操作看代码)
另外,记得把i==j的数据标为0(本身到本身距离为0)

第二步,我们申明一个dis数组,用来存放从V1到各个顶点距离的数据
(这里注意,题目不一样需求也不一样,可能问的是其他点到某点的距离,这个灵活变通好伐)

显然呢,我们可以根据本题得到dis初始值为[0,INF,10,INF,30,100]

这里有几点可能容易引起困惑,第一个0指的是从1到1的距离,显然是0(本身到本身),你可以把这个看成是一个1*6的矩阵,纵坐标表示的就是1,2,3,4,5,6,这样我们就将能从1直接到某处的距离标记了下来

接下来进行查找的操作,我们要从这个dis里找到最短的路径,显然是从1到3的距离是10,我们把这个距离送到min里,如何flag=3,作为之后vis访问过的依据(vis[flag] = 1)

之后呢,回到我们刚才建立好的图,我们从flag行开始寻找,看一下此时flag行的数据,我们可以知道是[10,5,0,50,0,0],然后列从1到6跑一个循环,判断条件是(map[flag][j]+min<dis[j]),这个条件就是判断从3开始到别的地方的路程加上从v1到v3的路程是不是小于从v1到某处的路,如果是的话很显然将dis[j]用map[flag][j]+min<dis[j]覆盖掉就可以了
跑完这个循环,dis也更新成了[0,15,10,60,30,100]

就这样,dis的值不断的变化,最终我们得到的结果就是从v1到任意一点的最短路径,是不是很简单呢?

四、题目示例

1.模版题(ZCMU-1624)

链接: ZCMU-1624.
1624: 最短路
Time Limit: 1 Sec Memory Limit: 128 MB
Submit: 368 Solved: 99
[Submit][Status][Web Board]
Description
有n个城市编号为1—n,m条路,告诉你每条路的长度,求给定两点的最短路径长度。

Input
T 组数据

每组数据第一行有两个正整数n,m(n<=1000,m<=10000)分别表示城市的数量和路的条数,接下来m行,每行3个整数a,b,c,表示城市a和城市b之间有一条c长度的路

最后一行输入两个整数x,y。

Output
输出城市 x 到城市 y 的最短路径。如果不存在输出-1。

Sample Input
2
3 2
1 2 2
2 3 3
1 3
4 2
1 2 3
1 3 2
1 4
Sample Output
5
-1

2.邻接矩阵实现代码

代码如下(示例):

#include<bits/stdc++.h>
using namespace std;
const int INF=0x3f3f3f3f;
typedef long long ll;
int main(){
    int map[1005][1005],vis[1005],dis[1005];
    int T;
    cin >> T;
    int n,m;
    int a,b,h;
    while(T--){
        memset(vis, 0, sizeof vis);
        cin >> n >> m;
        //这里在初始化,就是将map的任意一点的距离都设置为无穷
        for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
        map[i][j] = INF;
            
        //这一步很重要!
        for(int i =1;i<=n;i++)
        map[i][i] = 0;
        for(int i =1;i<=m;i++){
            cin >> a >> b >> h;
            if(h<map[a][b]){
            //双向导通
            map[a][b] = h;
            map[b][a] = h;
            }
        }

//        cout << endl;
//        for(int i=1;i<=n;i++){
//        for(int j=1;j<=n;j++)
//        cout << map[i][j] << " ";
//        cout << endl;
//        }


        int st,nd;
        scanf("%d %d",&st,&nd);
        for(int i =1;i<=n;i++)dis[i] = map[st][i];
        for(int i=1;i<n;i++){
            int min = INF;
            int flag = 0;
            for(int j=1;j<=n;j++){
            //这里就是查找最短的距离
                if(min>dis[j]&&!vis[j]){
                    flag = j;
                    min = dis[j];
                }
            }
            vis[flag] = 1;
            for(int j=1;j<=n;j++){
            //判断并且覆盖
                if(min+map[flag][j]<dis[j]&&!vis[j])
                    dis[j] = min + map[flag][j];
            }
        }
        int temp = dis[nd];
        if(temp!=INF)
            printf("%d\n",temp);
        else
            printf("-1\n");
//        for(int i=2;i<=n;i++)
//        cout << dis[i] << " ";
//        cout << endl;
    }
    return 0;
}

封装为函数

#include<bits/stdc++.h>
using namespace std;
#define rep(i,a,b) for(int i=a;i<=b;++i)
#define pre(i,a,b) for(int i=a;i>=b;--i)
#define m(x) memset(x,0,sizeof x)
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
#define PI acos(-1)
typedef long long ll;
const int INF =  0x3f3f3f3f;
const int mod = 1e5+7;
const int maxn = 1e3+10;
int n,m,T,ans;
int st,nd;
int mp[maxn][maxn],dis[maxn],vis[maxn];
void dij()
{
    rep(i, 1, n)dis[i] = mp[st][i];
    
//    rep(i, 1, n)printf("%d ",dis[i]);
//    cout << endl;
    
    rep(i, 1, n)
    {
        int min = INF,flag;
        flag = 0;
        rep(j, 1, n)
        {
            if(dis[j]<min&&!vis[j])
            {
                min = dis[j];
                flag = j;
            }
        }
        vis[flag] = 1;
        for(int j=1;j<=n;j++)
        {
            if(!vis[j]&&min+mp[flag][j]<dis[j])
                dis[j] = min+mp[flag][j];
        }
    }
    
}
int main()
{
    scanf("%d",&T);
    while(T--)
    {
        m(dis);
        m(vis);
        ans = INF;
//        memset(mp, INF, sizeof mp);
        int u,v,w;
        scanf("%d%d",&n,&m);
        rep(i, 1, n)
        rep(j, 1, n)
        mp[i][j] = INF;
        rep(i, 1, n)
        mp[i][i] = 0;
        while(m--){
            scanf("%d%d%d",&u,&v,&w);
            if(w<mp[u][v])
            {
                mp[u][v] = w;
                mp[v][u] = w;
            }
        }
        scanf("%d%d",&st,&nd);
        dij();
        ans = dis[nd];
//        rep(i, 1, n)printf("%d ",dis[i]);
//        cout << endl;
        if(ans>=INF)puts("-1");
        else printf("%d\n",ans);
    }
    return 0;
}

下面是一个从别人博客转载的利用队列实现的,贴在这里便于以后查看:

void spfa()
{
	memset(dis, 0x3f, sizeof dis);
	dis[1] = 0; vis[1] = 1;//vis记录是否在队列里 
	queue<int> q;
	q.push(1);
	register int u, v;
	while(q.size())
	{
		u = q.front(); q.pop(); vis[u] = 0;
		for(int i = head[u]; ~i; i = e[i].nxt)//向外发散 
		{
			v = e[i].to;
			if(dis[u] + e[i].w < dis[v])
			{
				dis[v] = dis[u] + e[i].w;
				if(!vis[v]) q.push(v), vis[v] = 1; //不在队列中,那就放进去更新别的点 
			}
		}
	}
}

几个需要注意的小点:
1.解题前需要判断这是一个有向图还是一个无向图,在读入数据的时候还是有差距的
2.值得注意的是,可能会出现找不到最短路径的情况,也就是说,某地到某地的路程为无穷(maxn),此时记得输出-1即可
3.这题有几个变化,比如现在模版题是输出x到y的最短路径,有些时候可能只是叫你输出从1到任意城市的最短路径,这点知道一下就可以了


3.优先队列实现代码

#include<stdio.h>
#include<string.h>
#include<queue>
#define INF 0x3f3f3f3f
using namespace std;
struct mp{ int to, nt, v;}p[20005];
int hd[1005], dis[1005], vis[1005], ct;
struct node
{
    int index, dist;
    friend bool operator <(node a,node b)//运算符重载
        {
            return a.dist>b.dist;
        }

};
priority_queue<node>q;
void add(int a, int b, int c)
{
    p[ct].to = b, p[ct].v = c;
    p[ct].nt = hd[a], hd[a] = ct++;
}
int main()
{
    int T, n, m, i, a, b, c, u, s, e;
    scanf("%d", &T);
    while (T--)
    {
        ct = 1;
        scanf("%d%d", &n, &m);
        q = priority_queue<node>();
        memset(p, 0, sizeof p);
        memset(hd, 0, sizeof hd);
        memset(vis, 0, sizeof vis);
        memset(dis, 0x3f, sizeof dis);
        while (m--)
        {
            scanf("%d%d%d", &a, &b, &c);
            add(a, b, c), add(b, a, c);
        }
        scanf("%d%d", &s, &e);
        q.push(node{ s,0 }); dis[s] = 0;
        while (!q.empty())
        {
            u = q.top().index;
            q.pop();
            if (vis[u])continue;
            vis[u] = 1;
            for (i = hd[u]; i; i = p[i].nt)
                if (!vis[p[i].to] && dis[p[i].to] > dis[u] + p[i].v)
                {
                    dis[p[i].to] = dis[u] + p[i].v;
                    q.push(node{ p[i].to,dis[p[i].to] });
                }
        }
        printf("%d\n", dis[e] == INF ? -1 : dis[e]);
    }
    return 0;
}

总结

提示:这里对文章进行总结:
最短路Dij算法在ACM竞赛中占重要地位,是一个需要熟记熟练使用的算法,不熟的话多打几次模版题就可以了。

  • 12
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值