代码随想录算法训练营Day50 | 拓扑排序、dijkstra(朴素版)

拓扑排序

117. 软件构建

1.思路

当我们做拓扑排序的时候,应该优先找入度为 0 的节点,只有入度为 0,它才是出发节点。

拓扑排序的过程,其实就两步:

  1. 找到入度为 0 的节点,加入结果集
  2. 将该节点从图中移除

循环以上两步,直到 所有节点都在图中被移除了。

如果我们发现结果集元素个数 不等于 图中节点个数,我们就可以认定图中一定有有向环!

这也是拓扑排序判断有向环的方法。

cpp 复制代码
#include <iostream>
#include <vector>
#include <unordered_map>
#include <queue>
using namespace std;

int main(){
    int n,m;cin>>n>>m;
    vector<int>indegree(n,0);    // 记录每个文件的入度
    unordered_map<int,vector<int>>ump;    // 记录文件依赖关系
    for(int i=0;i<m;i++){
        int s,t;cin>>s>>t;
        indegree[t]++;        // t的入度加一
        ump[s].push_back(t);  // 记录s指向哪些文件
    }
    queue<int>que;
    vector<int>res;    // 记录结果
    for(int i=0;i<n;i++){
        // 入度为0的文件,可以作为开头,先加入队列
        if(indegree[i]==0){
            que.push(i);
        }
    }
    while(!que.empty()){
        int cur=que.front();que.pop();    // 当前选中的文件
        res.push_back(cur);
        vector<int> nodes=ump[cur];       //获取该文件指向的文件
        for(int i:nodes){
            indegree[i]--;                // cur的指向的文件入度-1
            if(indegree[i]==0){
                que.push(i);
            }
        }
    }
    // 如果结果集元素个数不等于图中节点个数,图中一定有有向环
    if(res.size()==n){    
        for(int i=0;i<n-1;i++){
            cout<<res[i]<<" ";
        }
        cout<<res[n-1]<<endl;
    }
    else cout<<-1<<endl;

    return 0;
}

2.思考

看到拓扑排序,第一想法可能以为是排序,不过它却是图论算法。

拓扑排序:把有向图转成线性的排序,也是图论中判断有向无环图的常用方法。

拓扑排序的过程也就两步:

  1. 找到入度为0 的节点,加入结果集
  2. 将该节点从图中移除

循环以上两步,直到 所有节点都在图中被移除了。

3.Reference:拓扑排序精讲 | 代码随想录


dijkstra(朴素版)

47. 参加科学大会(第六期模拟笔试)

1.思路

dijkstra 算法和 prim算法思路非常接近。

  • dijkstra 算法可以同时求起点到所有节点的最短路径
  • 权值不能为负数

dijkstra 三部曲:

  1. 第一步,选源点到哪个节点近且该节点未被访问过
  2. 第二步,该最近节点被标记访问过
  3. 第三步,更新非访问节点到源点的距离(即更新minDist数组)

解题思路步骤:

  1. 初始化

    • 距离数组:创建一个数组 minDist,用来记录从源点到每个节点的最短距离。初始时,将源点(节点1)的距离设为 0,其他所有节点的距离设为"无穷大"。

    • 访问标记:创建一个 visited 数组,用来标记该节点是否被访问。

  2. 找最近的未标记节点

    • 因为所有边的权重都是非负的,所以当前找到的未标记节点中,距离最小的那个,其路径不可能再通过其他未标记的节点来缩短了。
  3. 标记节点并更新非访问节点到源点的距离

    • 将上一步找到的节点 cur 标记。

    • 用节点 cur 去更新它所有邻居 j 的距离。检查"从源点到 cur 的距离" + "从 cur 到 j 的边权"是否小于"当前记录的从源点到 j 的距离"。

    • 如果更小,就更新 j 的距离。

  4. 循环与结果

    • 重复步骤 2 和 3,共 n 次,直到所有节点的最短路径都被确定。

    • 最后,查看 minDist 数组中目标节点(节点 n)的值。如果值仍是"无穷大",说明从源点无法到达该节点;否则,该值就是从源点到目标节点的最短路径长度。

cpp 复制代码
#include <iostream>
#include <vector>
#include <climits>
using namespace std;

int main(){
    int n,m;cin>>n>>m;
    vector<vector<int>>graph(n+1,vector<int>(n+1,INT_MAX));
    vector<bool>visited(n+1,false);     // 记录顶点是否被访问过
    vector<int>minDist(n+1,INT_MAX);    // 存储从源点到每个节点的最短距离
    for(int i=0;i<m;i++){
        int s,t,val;cin>>s>>t>>val;
        graph[s][t]=val;
    }
    minDist[1]=0;            // 起始点到自身的距离为0
    for(int i=1;i<=n;i++){
        int mind=INT_MAX;
        int cur=1;
        // 1、选距离源点最近且未访问过的节点
        for(int j=1;j<=n;j++){
            if(!visited[j] && minDist[j]<mind){
                mind=minDist[j];
                cur=j;
            }
        }
        // 2、标记该节点已被访问
        visited[cur]=true;
        // 3、第三步,更新非访问节点到源点的距离(即更新minDist数组)
        for(int j=1;j<=n;j++){
            if(!visited[j] && graph[cur][j]!=INT_MAX && graph[cur][j]+minDist[cur]<minDist[j]){
                minDist[j]=graph[cur][j]+minDist[cur];
            }
        }
    }
    // 到达终点最短路径
    if(minDist[n]!=INT_MAX){
        cout<<minDist[n]<<endl;
    }
    // 不能到达终点
    else cout<<-1<<endl;

    return 0;
}

2.拓展

在上述基础上把最短路的路径打印出来。

cpp 复制代码
#include <iostream>
#include <vector>
#include <climits>
using namespace std;

int main(){
    int n,m;cin>>n>>m;
    vector<vector<int>>graph(n+1,vector<int>(n+1,INT_MAX));
    vector<bool>visited(n+1,false);
    vector<int>minDist(n+1,INT_MAX);
    // 初始化
    vector<int>father(n+1,-1);
    for(int i=0;i<m;i++){
        int s,t,val;cin>>s>>t>>val;
        graph[s][t]=val;
    }
    minDist[1]=0;
    for(int i=1;i<=n;i++){
        int mind=INT_MAX;
        int cur=1;
        for(int j=1;j<=n;j++){
            if(!visited[j] && minDist[j]<mind){
                mind=minDist[j];
                cur=j;
            }
        }
        visited[cur]=true;
        for(int j=1;j<=n;j++){
            if(!visited[j] && graph[cur][j]!=INT_MAX && graph[cur][j]+minDist[cur]<minDist[j]){
                minDist[j]=graph[cur][j]+minDist[cur];
                father[j]=cur;    // 记录边
            }
        }
    }
    if(minDist[n]!=INT_MAX){
        cout<<minDist[n]<<endl;
    }
    else cout<<-1<<endl;
    // 输出最短情况
    for(int i=1;i<=n;i++){
        cout<<father[i]<<"->"<<i<<endl;
    }

    return 0;
}

3.思考

prim 是求非访问节点到最小生成树的最小距离,而 dijkstra 是求非访问节点到源点的最小距离。

特性 拓扑排序 (卡恩算法) 最短路径(dijkstra)
解决问题 对有向无环图的节点进行线性排序。 求解带权图中从一个源点到其他所有节点的最短路径。
核心原理 依赖关系。一个节点必须在其所有前驱节点之后。 距离。一个节点的最短路径必须通过比它更短的节点来更新。
处理顺序 每次选择入度为 0 的节点进行处理。 每次选择距离源点最近的未访问节点进行处理。
关键数据结构 indegree, queue minDist (最短距离数组), visited (访问标记)
前提 图必须是有向无环图 图中所有边的权重必须非负
结果输出 一个拓扑排序序列 ,或者 -1(表示有环)。 一个最短距离数组,从中可以查到任意目标点的最短距离。如果节点不可达,其距离仍为"无穷大"。

4.Reference:dijkstra(朴素版)精讲 | 代码随想录

相关推荐
初晴や1 小时前
【C++】图论:基础理论与实际应用深入解析
c++·算法·图论
李泽辉_1 小时前
深度学习算法学习(五):手动实现梯度计算、反向传播、优化器Adam
深度学习·学习·算法
李泽辉_1 小时前
深度学习算法学习(一):梯度下降法和最简单的深度学习核心原理代码
深度学习·学习·算法
꧁Q༒ོγ꧂1 小时前
算法详解---大纲
算法
m0_603888711 小时前
Scaling Trends for Multi-Hop Contextual Reasoning in Mid-Scale Language Models
人工智能·算法·ai·语言模型·论文速览
Xの哲學1 小时前
Linux io_uring 深度剖析: 重新定义高性能I/O的架构革命
linux·服务器·网络·算法·边缘计算
comli_cn1 小时前
残差链接(Residual Connection)
人工智能·算法
Aaron15882 小时前
基于VU13P在人工智能高速接口传输上的应用浅析
人工智能·算法·fpga开发·硬件架构·信息与通信·信号处理·基带工程
予枫的编程笔记2 小时前
【论文解读】DLF:以语言为核心的多模态情感分析新范式 (AAAI 2025)
人工智能·python·算法·机器学习
im_AMBER2 小时前
Leetcode 99 删除排序链表中的重复元素 | 合并两个链表
数据结构·笔记·学习·算法·leetcode·链表