代码随想录算法训练营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(朴素版)精讲 | 代码随想录

相关推荐
业精于勤的牙2 小时前
浅谈:算法中的斐波那契数(四)
算法
一直都在5722 小时前
数据结构入门:二叉排序树的删除算法
数据结构·算法
白云千载尽2 小时前
ego_planner算法的仿真环境(主要是ros)-算法的解耦实现.
算法·无人机·规划算法·后端优化·ego·ego_planner
hweiyu002 小时前
排序算法简介及分类
数据结构
Swizard2 小时前
别再只会算直线距离了!用“马氏距离”揪出那个伪装的数据“卧底”
python·算法·ai
flashlight_hi2 小时前
LeetCode 分类刷题:199. 二叉树的右视图
javascript·算法·leetcode
LYFlied2 小时前
【每日算法】LeetCode 46. 全排列
前端·算法·leetcode·面试·职场和发展
2301_823438022 小时前
【无标题】解析《采用非对称自玩实现强健多机器人群集的深度强化学习方法》
数据库·人工智能·算法
oscar9993 小时前
CSP-J教程——第二阶段第十二、十三课:排序与查找算法
数据结构·算法·排序算法