Dijkstra算法

朴素版

模板

稠密图用邻接矩阵存储

int g[N][N];  // 存储每条边(**邻接矩阵存储)**
int dist[N];  // 存储1号点到每个点的最短距离
bool st[N];   // 存储每个点的最短路是否已经确定

// 求1号点到n号点的最短路,如果不存在则返回-1
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;

        // 用t更新其他点的距离
        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];
}

优化

模板

稀疏图用邻接表存储

typedef pair<int, int> PII;

int n;      // 点的数量
int h[N], w[N], e[N], ne[N], idx;       // 邻接表存储所有边
int dist[N];        // 存储所有点到1号点的距离
bool st[N];     // 存储每个点的最短距离是否已确定

// 求1号点到n号点的最短距离,如果不存在,则返回-1
int dijkstra()
{
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;
    priority_queue<PII, vector<PII>, greater<PII>> heap;
    heap.push({0, 1});      // first存储距离,second存储节点编号

    while (heap.size())
    {
        auto t = heap.top();
        heap.pop();

        int ver = t.second, distance = t.first;

        if (st[ver]) continue;
        st[ver] = true;

        for (int i = h[ver]; i != -1; i = ne[i])
        {
            int j = e[i];
            if (dist[j] > distance + w[i])
            {
                dist[j] = distance + w[i];
                heap.push({dist[j], j});//**注意入队位置**
            }
        }
    }

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

问题

Dijkstra求最短路 I

代码

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=510;
int n,m;
int g[N][N];//**g[1][2]是指从1节点指向2节点的距离**,也可以表示不存在
int dist[N];//distance(距离)的缩写,代表每一个点到源点(起点)的距离
bool st[N];//state(状态)的缩写,当st[n]为true时说明这个点到源点的距离最小值就已经确定了
int dijkstra(){
    memset(dist,0x3f,sizeof(dist));//存储每一个点到源点的距离//设置为一个无穷大的值,以便求最小值
    dist[1]=0;//源点到自己的距离为0
    for(int i=0;i<n-1;i++){//其实这条语句唯一的作用就是循环n-1次(优化了)
    //所以写成for(int i=0;i<n;i++)也可以,因为如果下面的语句循环了n-1次的话,那么所有点都能得到最小值了
    //可以这么理解,每次循环都会确定一个最小值,还会再创造一个最小值(留给下一次循环去确定)
    //当循环n-1次时,情况是已经确定了n-1个点的最小值了,还创造了一个最小值(此时还有1个点等着下一次去确定)
    //那么就不需要下一次循环了,毕竟剩下的就一个点,在1个点的集合中知道有一个点是最小值,顺理成章了
    //当然你想写成for(int i=0;i<n;i++)也能AC~~小声说~~
        int t=-1;//**找到离源点最近的未确定最小距离的点**
        for(int j=1;j<=n;j++){//一个一个遍历寻找
            if(!st[j] and (t==-1 or **dist[t]>dist[j]**)){//**出错点**
                t=j;//!st[j]指的是最近距离还没有确定的点,and后面就是找符合!st[j]条件的距离最小的点
                //这一个操作就是找到未确定最小值的 `点集`中的最小点,t==-1是当第一次遇到未确定~的点时能够被初始化
            }
        }
        //(1)
        for(int j=1;j<=n;j++){//现在找到t了,遍历一遍所有点,有一下几种情况
        //1.j点和t点之间无连接,那么g[t][j]=0x3f3f3f3f,特别大,会被pass
        //2.dist[j]<=dist[t]+g[t][j],源点到j点的距离,如果经过t后距离更长了,那么不考虑
        //3.dist[j]<=dist[t]+g[t][j],,~~,经过t点距离更短了,那么修改dist[j]的值
            dist[j]=min(dist[j],dist[t]+g[t][j]);  
        }
        **st[t]=true**;//**当前t点已经把其余点全部遍历了一遍,此点变成确定距离为最小的点了**,这条语句放在(1)处也能AC//**出错点**
    }
    if(dist[n]==0x3f3f3f3f){//当前点n没被修改,说明到不了点n,输出-1
        return -1;
    }else{
        return dist[n];//易证
    }
}
int main(){
    cin>>n>>m;//n存点数,m存边数
    memset(g,0x3f,sizeof(g));//将点之间的距离的每一个值设置成很大的数,此知识点之前讲过
    while(m--){
        int a,b,c;
        cin>>a>>b>>c;
        g[a][b]=min(g[a][b],c);//有效解决多条边的问题,保留最短边//**题目中明确说可能有重边**
    }
    cout<<dijkstra()<<endl;
    return 0;
}

Dijkstra求最短路 II

解决

我们依然沿用朴素dijkstra解法的思路,我们找到离源点最近的未被确定最小距离的点,让他来更新其他所有点

我们把找到最近的点的这个过程利用小根堆(优先队列)实现。

我们通过将找最小的这个过程使用堆来进行优化

错点

  • 不能正确理解int h[N], e[N], ne[N], idx;的含义

代码

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

using namespace std;

typedef pair<int, int> PII;

const int N = 1e6 + 10;

int n, m;
int h[N], e[N], ne[N], idx;//使用邻接表存储
int w[N];//存储边权
int dist[N];
bool st[N];

void add(int a, int b, int c)//如何处理重边和自环呢
                            //实际上并不需要刻意去处理,因为已经排好序了
                            //距离更近一定会优先排,然后st就被打上标记,会被continue掉

{
    e[idx] = b, w[idx] = c/*这种存储方式是合理的*/, 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;//优先队列就是堆
    //这个优先队列是我们用来查找最小距离的辅助工具
    // 数据类型, 容器, 排序方式
		//greater 是小根堆排序
    heap.push({0, 1});// 放入**距离, 节点编号**
    while (heap.size()) //什么时候结束循环?
                        //从循环条件来看是当heap里面元素为空时结束循环
                        //那么什么时候heap为空呢?
                        //当所有点都已经访问完了,并且离原点更近的点一定会更优先进入循环
                        //因为小根堆排序,离原点近的放在前面就算不是与原点直接相连的点
                        //也会优先放进来
    {
        auto t = heap.top();//取出堆里面最小距离的点,然后释放掉
        heap.pop();

        int ver = t.second, distance = t.first;// ver节点编号

        if (**st[ver]**) continue; //这时候已经取到了距离最近的点,如果已经
                                //访问过就结束此次循环,未访问过打上标记
                                //他是如何判断该点一定是他的距离最优点呢?
                                //转【1】
        **st[ver] = true;//出错点,位置
        当一个点的最短距离确定,就不需要更新,直接跳过**

        for (int i = h[ver]; i != -1; i = ne[i]),
        {
            **int j = e[i];//j是节点编号**
            if (dist[j] > dist[ver] + w[i])//【1】通过for循环遍历其指向的点
                                            //当存在从起始点到j点更短的路径就进行更新
                                            //为什么说他一定是最优距离呢?
                                            //假设有其他点到ver的距离+其到原点的距离小于
                                            //现有的距离,那么他一定会优先考虑,也就不会
                                            //有该操作
            {
                dist[j] = dist[ver] + w[i];
                heap.push({dist[j], j});// 为什么要将其放入堆中?
                                        //应为存在从起始点到j点更短的路径
                                        //同时也会对节点距离进行排序
                                        //短距离放在前面
            }
        }
    }

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

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

    memset(h, -1, sizeof h);
    while (m -- )
    {
        int a, b, c;
        scanf("%d%d%d", &a, &b, &c);
        add(a, b, c);
    }

    cout << dijkstra() << endl;

    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值