当前位置: 首页 > news >正文

【算法入门图论】【模板】拓扑排序|【模板】单源最短路2 |最小生成树

✅作者简介:热爱后端语言的大学生,CSDN内容合伙人
✨精品专栏:C++面向对象
🔥系列专栏:算法百炼成神

文章目录

  • 🔥前言
  • 1、AB13 【模板】拓扑排序
    • 1.1、解题思路
    • 1.2、代码实现与注释
  • 2、AB14 最小生成树
    • 2.1、解题思路
    • 2.2、代码实现与注释
  • 3、AB15 单源最短路2
    • 3.1、解题思路
    • 3.2、代码实现与注释


🔥前言

本专栏收录的均为牛客网的算法题目,内含链表、双指针、递归、动态规划、基本数据结构等算法思想的具体运用。牛客网不仅有大量的经典算法题目,也有大厂的面试真题,面试、找工作完全可以来这里找机会。此外,网站内的编码主题多样化,调试功能可运用性强,可谓是非常注重用户体验。这么好的免费刷题网站还不快入手吗,快去注册开启算法百炼成神之路吧!

1、AB13 【模板】拓扑排序

学会使用邻接表解决图论问题,巧妙利用vector容器

题目链接:拓朴排序

在这里插入图片描述

1.1、解题思路

解决拓扑排序之前要先认识什么是拓扑排序:

对一个有向无环图(Directed Acyclic Graph简称DAG)图G进行拓扑排序,是将G中所有顶点排成一个线性序列,使得图中任意一对顶点uv,若边<u,v>∈E(G)则u在线性序列中出现在v之前。通常,这样的线性序列称为满足拓扑次序(Topological Order)的序列,简称拓扑序列。简单的说,由某个集合上的一个偏序得到该集合上的一个全序,这个操作称之为拓扑排序

解决步骤:

  1. 使用邻接表将顶点联系起来,辅助数组inDegree表示每个顶点的入度。
  2. 借助队列和计数器变量来判断该有向图是否有环:
    • 将入度为零的顶点入队(也就是拓扑图第一个顶点)
    • 取队首,遍历与之相邻的顶点,若该顶点入度减一后为零就将其入队
    • 只要队列非空就循环操作,计算器循环加一,与顶点数比较是否相等
  3. 本题末尾也不能输出空格,因此输出拓扑序列时要加限制条件

1.2、代码实现与注释

本题源码:

#include<iostream>
#include<vector>
#include<queue>
#define M 200001
using namespace std;

int main() {
    int n, m;
    cin >> n >> m;
    vector<int> adjList[M]; // 模拟邻接表
    int inDegree[M] = { 0 };// 记录每个顶点的入度
    int a, b;
    for (int i = 0; i < m; i++) {
        cin >> a >> b;
        adjList[a].push_back(b);
        inDegree[b]++;
    }
    queue<int> que; // 将初始入度为零的顶点入队
    for (int i = 1; i <= n; i++) {
        if (inDegree[i] == 0)
            que.push(i);
    }

    int cnt = 0; // 用来计数,判断改图是否有环
    vector<int> res; // 用来输出顶点序列
    while (!que.empty()) {
        int u = que.front();
        que.pop();
        res.push_back(u);
        for (int i = 0; i < adjList[u].size(); i++) { // 遍历u的相邻顶点
            int v = adjList[u][i];
            if (--inDegree[v] == 0)
                que.push(v);
        }
        cnt++;
    }
    // 若计数器与顶点数相同则图无环,存在拓扑排序
    if (cnt == n) {
        for (int i = 0; i < res.size(); i++) {
            cout << res[i];
            // 限制输出空格的条件
            if (i != res.size() - 1) {
                cout << " ";
            }
        }
    } else {
        cout << -1;
    }
    return 0;
}

重要注释:

  • adjList数组是vector类型的,用来模拟邻接表
    • 使用每个元素为一个数组的vector容器模拟邻接表进行建图
    • vector[a]所对应的数组中存储着该顶点所指向的其他顶点
    • inDegree数组代表每一个顶点的入度情况
  • 使用一个队列,初始时将所有入度为0的顶点全部入队,之后采用BFS的思想:
    • 依次取出队头元素并存入结果数组中,然后在邻接表中遍历该队头元素所指向的其他顶点
    • 将这些顶点的入度全部减一,若减一后某顶点的入度变为0,则将该顶点进行入队操作,
      重复此步骤直至队列为空为止。
  • 设置一个用于判断图中是否存在环(是否可以得到拓扑序列)的计数器,在弹出队头元素后要将计数器加一,最后队列为空后,若计数器的值与顶点数相同,则说明图不存在环,可以得到拓扑序列。

2、AB14 最小生成树

题目链接:最小生成树

在这里插入图片描述

2.1、解题思路

本题要求在最小花费下将 n 户人家连接起来,很显然是最小生成树的问题,我采用prim算法:

  1. 将二维数组cost按权升序排序,那么cost[0][2]就是最小的一个权值
  2. 将连接这条边的两个顶点放入unordered_set容器:
    • unordered_set 容器的元素是无序的,可以用来给顶点去重
    • 内置的 find 方法也很好用
  3. 接下来遍历cost二维数组,直到所有顶点全部放入容器中,遍历结束
  4. 将遍历中权值的和返回,程序结束

2.2、代码实现与注释

本题源码:

class Solution {
  public:
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * 返回最小的花费代价使得这n户人家连接起来
     * @param n int n户人家的村庄
     * @param m int m条路
     * @param cost intvector<vector<>> 一维3个参数,表示连接1个村庄到另外1个村庄的花费的代价
     * @return int
     */

    // 自定义排序规则:按权递增
    static bool cmp(vector<int>& x, vector<int>& y) {
        return x[2] < y[2];
    }
    int miniSpanningTree(int n, int m, vector<vector<int> >& cost) {
        unordered_set<int> points; // 记录不重复的点
        int res = 0;
        sort(cost.begin(), cost.end(), cmp);
        res += cost[0][2]; // 此时res 为最小权值
        // 将最小边加入
        points.insert(cost[0][0]);
        points.insert(cost[0][1]);
        while (1) {
            if (points.size() == n)
                break; // 所有的点连同后退出循环
            // 遍历剩余的边
            for (auto it = cost.begin(); it != cost.end(); it++) {
                // 如果边仅有一个点在集合内就加入
                if ((points.find((*it)[0]) != points.end() &&
                    points.find((*it)[1]) == points.end()) ||
                    (points.find((*it)[1]) != points.end() &&
                    points.find((*it)[0]) == points.end()))
                    {
                        res += (*it)[2];
                        points.insert((*it)[0]);
                        points.insert((*it)[1]);
                        cost.erase(it); // 删除该边
                        break;
                    }
            }
        }
        return res;
    }
};

重要注释:

  • cmp 是自定义的一个按权递增的排序函数,配合sort函数来将cost排序
    • 相关知识点可以参考我的博文:自定义排序规则
  • auto关键字可以自动推导表达式类型,在这里就相当于vector<vector<int>>::iterator
  • if的条件很长,但其实就是将有且仅有一个顶点在points中的边找到:
    • 获取该边的权值并求和,将另一顶点插入到points
    • 将该边删除,重新遍历cost,直到全部顶点被插入到points
  • 最终的 res 就是该题的结果,即最小花费。

3、AB15 单源最短路2

题目链接:单源最短路2

在这里插入图片描述在这里插入图片描述

3.1、解题思路

使用Dijkstra算法(即不断从未处理集合中找当前距离源点最近的顶点以添加到已处理集合中,直至未处理集合为空的算法思想)

  1. 对于无向图,采用邻接矩阵表示点与点的连接关系以及距离
  2. 使用数组dist记录每个顶点与源点的距离:
    • 本题中就是记录顶点1与其他顶点的距离
  3. 用一个布尔类型的数组记录顶点的处理情况:
    • 初始状态全部设为false,一经处理就设为true
  4. 最终dist[n]就是顶点n到源点的最短距离

3.2、代码实现与注释

本题源码

#include<iostream>
#include<climits> // 使用INT_MAX所需要引入的头文件
using namespace std;
const int N = 5000; // 注意题干,图的点数是固定值5000

int main() {

    int G[N + 1][N + 1]; // 用于模拟邻接矩阵进行建图
    for (int i = 1; i <= N; i++) {
        for (int j = 1; j <= N; j++) {
            G[i][j] = INT_MAX; // 先将邻接矩阵全部初始化为无穷大
        }
    }
    int n, m;
    cin >> n >> m;
    int u, v, w;
    for (int i = 1; i <= m; i++) {
        cin >> u >> v >> w;
        G[u][v] = w;
        G[v][u] = w; // 需要关于主对角线对称,因此两边都需要存储
    }
    int dist[N + 1]; // 用于存储每个顶点当前与源点的最短距离

    bool flag[N + 1]; // 用于记录每个顶点是否已经完成与源点最短距离的计算处理

    for (int i = 1; i <= N; i++) {
        dist[i] = G[1][i]; // 初始设置为邻接矩阵中源点所在 行 的权值
        flag[i] = false;
    }
    
    dist[1] = 0;
    flag[1] = true; // 将源点加入已处理集合
    
    for (int i = 2; i <= N; i++) {
        int tmp = INT_MAX, index = 1;
        for (int j = 1; j <= N; j++) { // 遍历寻找与源点的最短距离
            if (flag[j] == false && dist[j] < tmp) {
                tmp = dist[j];
                index = j;
            }
        }
        if (index != 1) {
            flag[index] = true; // 找到后将其加入已处理集合
        }
        for (int j = 2; j <= N; j++) {
            if (flag[j] == false && G[index][j] != INT_MAX) {
                if (G[index][j] + dist[index] < dist[j]) {
                    dist[j] = G[index][j] + dist[index]; // 更新最短路径
                }
            }
        }
    }
    if (dist[n] != INT_MAX) {
        cout << dist[n];
    } else {
        cout << -1;
    }
    return 0;
}

重要注释:

  • 二维数组G是具化出的邻接矩阵,将元素值全部初始化为无穷大:INT_MAX
  • u,v记录两个顶点,w记录顶点间距离,全部存入G
  • dist数组用来存放各顶点到源点的距离,flag数组记录顶点是否被处理过
  • index代表着与源点连接且距离最短的未经处理的顶点:
    • 随后将该顶点设为已处理
  • 然后index寻找与之相连且未经处理的顶点:
    • dist[index]index到源点的最短距离,如果加上与之相连的顶点的距离较小,那么就更新最短路径
  • 最终的dist数组的值就是各点到源点的最短路径

当自己动手去做的时候才发现原来我以为的难题,不过如此,将这份自信传达给大家,奋斗下去!

相关文章:

  • 第31讲:MySQL事务的并发问题以及事务的隔离级别
  • JavaScript 2 JavaScript 简介
  • 【目标检测】YOLOv5在Android上的部署
  • 攻防世界WEB练习 | easyphp
  • Java数据结构:栈与综合计算器的实现(图解+完整代码)
  • dolphinscheduler 3.0.1数据质量
  • 【vue后台管理系统】基于Vue+Element-UI+ECharts开发通用管理后台(上)
  • 【产品新体验】CSDN. 云IDE体验与使用教程
  • 毕业设计 基于51单片机老人防跌倒GSM短信报警系统
  • 实验十四:雨滴传感器实验
  • 【面试官说实现一个顺序表,但听到要求后我沉默了】
  • jquery导航图片全屏滚动、首页全屏轮播图,各式相册
  • 相对于java,C++中的那些神奇语法
  • 元宇宙系列之AI虚拟人:“人”潮汹涌 探路未来
  • Selenium基础 — Selenium操作浏览器窗口滚动条
  • 2018一半小结一波
  • Docker 1.12实践:Docker Service、Stack与分布式应用捆绑包
  • JAVA SE 6 GC调优笔记
  • JWT究竟是什么呢?
  • MQ框架的比较
  • MySQL主从复制读写分离及奇怪的问题
  • Python socket服务器端、客户端传送信息
  • Python进阶细节
  • 从输入URL到页面加载发生了什么
  • 分享几个不错的工具
  • 前端技术周刊 2019-01-14:客户端存储
  • 吐槽Javascript系列二:数组中的splice和slice方法
  • 在Docker Swarm上部署Apache Storm:第1部分
  • 在Mac OS X上安装 Ruby运行环境
  • 在weex里面使用chart图表
  • 哈罗单车融资几十亿元,蚂蚁金服与春华资本加持 ...
  • ​​​​​​​ubuntu16.04 fastreid训练过程
  • ​​​【收录 Hello 算法】9.4 小结
  • (BAT向)Java岗常问高频面试汇总:MyBatis 微服务 Spring 分布式 MySQL等(1)
  • (图)IntelliTrace Tools 跟踪云端程序
  • .NET CORE 第一节 创建基本的 asp.net core
  • .net core控制台应用程序初识
  • .net on S60 ---- Net60 1.1发布 支持VS2008以及新的特性
  • .NET的数据绑定
  • .NET建议使用的大小写命名原则
  • .net快速开发框架源码分享
  • .sys文件乱码_python vscode输出乱码
  • @Autowired标签与 @Resource标签 的区别
  • [ 云计算 | AWS 实践 ] Java 如何重命名 Amazon S3 中的文件和文件夹
  • [].slice.call()将类数组转化为真正的数组
  • [Bug]使用gradio创建应用提示AttributeError: module ‘gradio‘ has no attribute ‘inputs‘
  • [DAU-FI Net开源 | Dual Attention UNet+特征融合+Sobel和Canny等算子解决语义分割痛点]
  • [hadoop读书笔记] 第十五章 sqoop1.4.6小实验 - 将mysq数据导入HBASE
  • [Linux] Ubuntu install Miniconda
  • [Linux]——彻底学通权限
  • [LOJ 6213]「美团 CodeM 决赛」radar
  • [Real world Haskell] 中文翻译:第一章 快速上手
  • [Redis]Redis高级特性的配置及使用
  • [SWPUCTF 2021 新生赛]crypto8
  • [SystemC]SystemC中的模块和程序